mirror of https://github.com/astral-sh/ruff
Merge branch 'main' into evanrittenhouse_5073
This commit is contained in:
commit
dbe62cc741
|
|
@ -42,7 +42,7 @@ jobs:
|
||||||
- "!crates/ruff_formatter/**"
|
- "!crates/ruff_formatter/**"
|
||||||
- "!crates/ruff_dev/**"
|
- "!crates/ruff_dev/**"
|
||||||
- "!crates/ruff_shrinking/**"
|
- "!crates/ruff_shrinking/**"
|
||||||
- scripts/check_ecosystem.py
|
- scripts/*
|
||||||
|
|
||||||
formatter:
|
formatter:
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
|
|
@ -56,6 +56,7 @@ jobs:
|
||||||
- crates/ruff_text_size/**
|
- crates/ruff_text_size/**
|
||||||
- crates/ruff_python_parser/**
|
- crates/ruff_python_parser/**
|
||||||
- crates/ruff_dev/**
|
- crates/ruff_dev/**
|
||||||
|
- scripts/*
|
||||||
|
|
||||||
cargo-fmt:
|
cargo-fmt:
|
||||||
name: "cargo fmt"
|
name: "cargo fmt"
|
||||||
|
|
@ -327,7 +328,7 @@ jobs:
|
||||||
name: "Formatter ecosystem and progress checks"
|
name: "Formatter ecosystem and progress checks"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: determine_changes
|
needs: determine_changes
|
||||||
if: needs.determine_changes.outputs.formatter == 'true'
|
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
|
|
@ -337,9 +338,6 @@ jobs:
|
||||||
- name: "Formatter progress"
|
- name: "Formatter progress"
|
||||||
run: scripts/formatter_ecosystem_checks.sh
|
run: scripts/formatter_ecosystem_checks.sh
|
||||||
- name: "Github step summary"
|
- name: "Github step summary"
|
||||||
run: grep "similarity index" target/progress_projects_log.txt | sort > $GITHUB_STEP_SUMMARY
|
run: cat target/progress_projects_stats.txt > $GITHUB_STEP_SUMMARY
|
||||||
# CPython is not black formatted, so we run only the stability check
|
- name: "Remove checkouts from cache"
|
||||||
- name: "Clone CPython 3.10"
|
run: rm -r target/progress_projects
|
||||||
run: git clone --branch 3.10 --depth 1 https://github.com/python/cpython.git crates/ruff/resources/test/cpython
|
|
||||||
- name: "Check CPython stability"
|
|
||||||
run: cargo run --bin ruff_dev -- format-dev --stability-check crates/ruff/resources/test/cpython
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ jobs:
|
||||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||||
- name: "Deploy to Cloudflare Pages"
|
- name: "Deploy to Cloudflare Pages"
|
||||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ jobs:
|
||||||
working-directory: playground
|
working-directory: playground
|
||||||
- name: "Deploy to Cloudflare Pages"
|
- name: "Deploy to Cloudflare Pages"
|
||||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
# Breaking Changes
|
# Breaking Changes
|
||||||
|
|
||||||
|
## 0.0.283 / 0.284
|
||||||
|
|
||||||
|
### The target Python version now defaults to 3.8 instead of 3.10 ([#6397](https://github.com/astral-sh/ruff/pull/6397))
|
||||||
|
|
||||||
|
Previously, when a target Python version was not specified, Ruff would use a default of Python 3.10. However, it is safer to default to an _older_ Python version to avoid assuming the availability of new features. We now default to the oldest supported Python version which is currently Python 3.8.
|
||||||
|
|
||||||
|
(We still support Python 3.7 but since [it has reached EOL](https://devguide.python.org/versions/#unsupported-versions) we've decided not to make it the default here.)
|
||||||
|
|
||||||
|
Note this change was announced in 0.0.283 but not active until 0.0.284.
|
||||||
|
|
||||||
## 0.0.277
|
## 0.0.277
|
||||||
|
|
||||||
### `.ipynb_checkpoints`, `.pyenv`, `.pytest_cache`, and `.vscode` are now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))
|
### `.ipynb_checkpoints`, `.pyenv`, `.pytest_cache`, and `.vscode` are now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,6 @@ At time of writing, the repository includes the following crates:
|
||||||
- `crates/ruff_macros`: proc macro crate containing macros used by Ruff.
|
- `crates/ruff_macros`: proc macro crate containing macros used by Ruff.
|
||||||
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
|
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
|
||||||
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
|
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
|
||||||
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
|
|
||||||
- `crates/ruff_python_formatter`: library crate implementing the Python formatter. Emits an
|
- `crates/ruff_python_formatter`: library crate implementing the Python formatter. Emits an
|
||||||
intermediate representation for each node, which `ruff_formatter` prints based on the configured
|
intermediate representation for each node, which `ruff_formatter` prints based on the configured
|
||||||
line length.
|
line length.
|
||||||
|
|
@ -572,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`):
|
[flamegraph](https://github.com/flamegraph-rs/flamegraph) (`cargo install flamegraph`):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
flamegraph --perfdata perf.data
|
flamegraph --perfdata perf.data --no-inline
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Mac
|
#### Mac
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,18 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
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]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.20"
|
version = "0.7.20"
|
||||||
|
|
@ -800,7 +812,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flake8-to-ruff"
|
name = "flake8-to-ruff"
|
||||||
version = "0.0.282"
|
version = "0.0.284"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
@ -991,6 +1003,16 @@ dependencies = [
|
||||||
"winapi-util",
|
"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]]
|
[[package]]
|
||||||
name = "imperative"
|
name = "imperative"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -2042,7 +2064,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.0.282"
|
version = "0.0.284"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"annotate-snippets 0.9.1",
|
"annotate-snippets 0.9.1",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -2119,6 +2141,7 @@ dependencies = [
|
||||||
"ruff",
|
"ruff",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
"ruff_python_formatter",
|
"ruff_python_formatter",
|
||||||
|
"ruff_python_index",
|
||||||
"ruff_python_parser",
|
"ruff_python_parser",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -2141,7 +2164,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_cli"
|
name = "ruff_cli"
|
||||||
version = "0.0.282"
|
version = "0.0.284"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"annotate-snippets 0.9.1",
|
"annotate-snippets 0.9.1",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -2197,6 +2220,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"ignore",
|
"ignore",
|
||||||
|
"imara-diff",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
|
@ -2330,6 +2354,7 @@ dependencies = [
|
||||||
"similar",
|
"similar",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2353,7 +2378,6 @@ dependencies = [
|
||||||
"is-macro",
|
"is-macro",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lexical-parse-float",
|
"lexical-parse-float",
|
||||||
"num-bigint",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rand",
|
"rand",
|
||||||
"unic-ucd-category",
|
"unic-ucd-category",
|
||||||
|
|
@ -2400,6 +2424,7 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"ruff_index",
|
"ruff_index",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
|
"ruff_python_parser",
|
||||||
"ruff_python_stdlib",
|
"ruff_python_stdlib",
|
||||||
"ruff_source_file",
|
"ruff_source_file",
|
||||||
"ruff_text_size",
|
"ruff_text_size",
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ toml = { version = "0.7.2" }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-indicatif = "0.3.4"
|
tracing-indicatif = "0.3.4"
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
|
unicode-width = "0.1.10"
|
||||||
wsl = { version = "0.1.0" }
|
wsl = { version = "0.1.0" }
|
||||||
|
|
||||||
# v1.0.1
|
# v1.0.1
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -30,7 +30,7 @@ An extremely fast Python linter, written in Rust.
|
||||||
- 🤝 Python 3.11 compatibility
|
- 🤝 Python 3.11 compatibility
|
||||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||||
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
|
- 📏 Over [600 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
|
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
|
||||||
built-in Flake8 rule set
|
built-in Flake8 rule set
|
||||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
- 🔌 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
|
```yaml
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.0.282
|
rev: v0.0.284
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
```
|
```
|
||||||
|
|
@ -211,8 +211,8 @@ line-length = 88
|
||||||
# Allow unused variables when underscore-prefixed.
|
# Allow unused variables when underscore-prefixed.
|
||||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
# Assume Python 3.10.
|
# Assume Python 3.8
|
||||||
target-version = "py310"
|
target-version = "py38"
|
||||||
|
|
||||||
[tool.ruff.mccabe]
|
[tool.ruff.mccabe]
|
||||||
# Unlike Flake8, default to a complexity level of 10.
|
# Unlike Flake8, default to a complexity level of 10.
|
||||||
|
|
@ -233,7 +233,7 @@ linting command.
|
||||||
|
|
||||||
<!-- Begin section: Rules -->
|
<!-- Begin section: Rules -->
|
||||||
|
|
||||||
**Ruff supports over 500 lint rules**, many of which are inspired by popular tools like Flake8,
|
**Ruff supports over 600 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
|
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
|
||||||
Rust as a first-party feature.
|
Rust as a first-party feature.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "flake8-to-ruff"
|
name = "flake8-to-ruff"
|
||||||
version = "0.0.282"
|
version = "0.0.284"
|
||||||
description = """
|
description = """
|
||||||
Convert Flake8 configuration files to Ruff configuration files.
|
Convert Flake8 configuration files to Ruff configuration files.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.0.282"
|
version = "0.0.284"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|
@ -62,8 +62,6 @@ quick-junit = { version = "0.3.2" }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
result-like = { version = "0.4.6" }
|
result-like = { version = "0.4.6" }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
semver = { version = "1.0.16" }
|
semver = { version = "1.0.16" }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|
@ -77,7 +75,7 @@ strum_macros = { workspace = true }
|
||||||
thiserror = { version = "1.0.43" }
|
thiserror = { version = "1.0.43" }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
typed-arena = { version = "2.0.2" }
|
typed-arena = { version = "2.0.2" }
|
||||||
unicode-width = { version = "0.1.10" }
|
unicode-width = { workspace = true }
|
||||||
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
|
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
|
||||||
wsl = { version = "0.1.0" }
|
wsl = { version = "0.1.0" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,19 @@ with open("/dev/shm/unit/test", "w") as f:
|
||||||
# not ok by config
|
# not ok by config
|
||||||
with open("/foo/bar", "w") as f:
|
with open("/foo/bar", "w") as f:
|
||||||
f.write("def")
|
f.write("def")
|
||||||
|
|
||||||
|
# Using `tempfile` module should be ok
|
||||||
|
import tempfile
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(dir="/tmp") as f:
|
||||||
|
f.write(b"def")
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(dir="/var/tmp") as f:
|
||||||
|
f.write(b"def")
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(dir="/dev/shm") as d:
|
||||||
|
pass
|
||||||
|
|
||||||
|
with TemporaryDirectory(dir="/tmp") as d:
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,20 @@ 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()):
|
def and_this(value=set()):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
@ -261,3 +275,32 @@ def mutable_annotations(
|
||||||
d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
):
|
):
|
||||||
pass
|
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"""
|
||||||
|
|
|
||||||
|
|
@ -74,3 +74,10 @@ try:
|
||||||
except (ValueError, binascii.Error):
|
except (ValueError, binascii.Error):
|
||||||
# binascii.Error is a subclass of ValueError.
|
# binascii.Error is a subclass of ValueError.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# https://github.com/astral-sh/ruff/issues/6412
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except (ValueError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
|
import typing
|
||||||
|
|
||||||
# Shouldn't affect non-union field types.
|
# Shouldn't affect non-union field types.
|
||||||
field1: str
|
field1: str
|
||||||
|
|
||||||
# Should emit for duplicate field types.
|
# Should emit for duplicate field types.
|
||||||
field2: str | str # PYI016: Duplicate union member `str`
|
field2: str | str # PYI016: Duplicate union member `str`
|
||||||
|
|
||||||
|
|
||||||
# Should emit for union types in arguments.
|
# Should emit for union types in arguments.
|
||||||
def func1(arg1: int | int): # PYI016: Duplicate union member `int`
|
def func1(arg1: int | int): # PYI016: Duplicate union member `int`
|
||||||
print(arg1)
|
print(arg1)
|
||||||
|
|
||||||
|
|
||||||
# Should emit for unions in return types.
|
# Should emit for unions in return types.
|
||||||
def func2() -> str | str: # PYI016: Duplicate union member `str`
|
def func2() -> str | str: # PYI016: Duplicate union member `str`
|
||||||
return "my string"
|
return "my string"
|
||||||
|
|
||||||
|
|
||||||
# Should emit in longer unions, even if not directly adjacent.
|
# Should emit in longer unions, even if not directly adjacent.
|
||||||
field3: str | str | int # PYI016: Duplicate union member `str`
|
field3: str | str | int # PYI016: Duplicate union member `str`
|
||||||
field4: int | int | str # PYI016: Duplicate union member `int`
|
field4: int | int | str # PYI016: Duplicate union member `int`
|
||||||
|
|
@ -33,3 +32,55 @@ field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||||
|
|
||||||
# Should emit for nested unions.
|
# Should emit for nested unions.
|
||||||
field11: dict[int | int, str]
|
field11: dict[int | int, str]
|
||||||
|
|
||||||
|
# Should emit for unions with more than two cases
|
||||||
|
field12: int | int | int # Error
|
||||||
|
field13: int | int | int | int # Error
|
||||||
|
|
||||||
|
# Should emit for unions with more than two cases, even if not directly adjacent
|
||||||
|
field14: int | int | str | int # Error
|
||||||
|
|
||||||
|
# Should emit for duplicate literal types; also covered by PYI030
|
||||||
|
field15: typing.Literal[1] | typing.Literal[1] # Error
|
||||||
|
|
||||||
|
# Shouldn't emit if in new parent type
|
||||||
|
field16: int | dict[int, str] # OK
|
||||||
|
|
||||||
|
# Shouldn't emit if not in a union parent
|
||||||
|
field17: dict[int, int] # OK
|
||||||
|
|
||||||
|
# Should emit in cases with newlines
|
||||||
|
field18: typing.Union[
|
||||||
|
set[
|
||||||
|
int # foo
|
||||||
|
],
|
||||||
|
set[
|
||||||
|
int # bar
|
||||||
|
],
|
||||||
|
] # Error, newline and comment will not be emitted in message
|
||||||
|
|
||||||
|
# Should emit in cases with `typing.Union` instead of `|`
|
||||||
|
field19: typing.Union[int, int] # Error
|
||||||
|
|
||||||
|
# Should emit in cases with nested `typing.Union`
|
||||||
|
field20: typing.Union[int, typing.Union[int, str]] # Error
|
||||||
|
|
||||||
|
# Should emit in cases with mixed `typing.Union` and `|`
|
||||||
|
field21: typing.Union[int, int | str] # Error
|
||||||
|
|
||||||
|
# Should emit only once in cases with multiple nested `typing.Union`
|
||||||
|
field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
|
||||||
|
|
||||||
|
# Should emit in cases with newlines
|
||||||
|
field23: set[ # foo
|
||||||
|
int] | set[int]
|
||||||
|
|
||||||
|
# Should emit twice (once for each `int` in the nested union, both of which are
|
||||||
|
# duplicates of the outer `int`), but not three times (which would indicate that
|
||||||
|
# we incorrectly re-checked the nested union).
|
||||||
|
field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int`
|
||||||
|
|
||||||
|
# Should emit twice (once for each `int` in the nested union, both of which are
|
||||||
|
# duplicates of the outer `int`), but not three times (which would indicate that
|
||||||
|
# we incorrectly re-checked the nested union).
|
||||||
|
field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||||
|
|
|
||||||
|
|
@ -74,3 +74,13 @@ field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
|
||||||
# Should emit in cases with newlines
|
# Should emit in cases with newlines
|
||||||
field23: set[ # foo
|
field23: set[ # foo
|
||||||
int] | set[int]
|
int] | set[int]
|
||||||
|
|
||||||
|
# Should emit twice (once for each `int` in the nested union, both of which are
|
||||||
|
# duplicates of the outer `int`), but not three times (which would indicate that
|
||||||
|
# we incorrectly re-checked the nested union).
|
||||||
|
field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int`
|
||||||
|
|
||||||
|
# Should emit twice (once for each `int` in the nested union, both of which are
|
||||||
|
# duplicates of the outer `int`), but not three times (which would indicate that
|
||||||
|
# we incorrectly re-checked the nested union).
|
||||||
|
field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import builtins
|
import builtins
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||||
x: type[int] | type[str] | type[float]
|
x: type[int] | type[str] | type[float]
|
||||||
y: builtins.type[int] | type[str] | builtins.type[complex]
|
y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||||
|
|
@ -9,7 +8,9 @@ z: Union[type[float], type[complex]]
|
||||||
z: Union[type[float, int], type[complex]]
|
z: Union[type[float, int], type[complex]]
|
||||||
|
|
||||||
|
|
||||||
def func(arg: type[int] | str | type[float]) -> None: ...
|
def func(arg: type[int] | str | type[float]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
# OK
|
# OK
|
||||||
x: type[int, str, float]
|
x: type[int, str, float]
|
||||||
|
|
@ -17,4 +18,14 @@ y: builtins.type[int, str, complex]
|
||||||
z: Union[float, complex]
|
z: Union[float, complex]
|
||||||
|
|
||||||
|
|
||||||
def func(arg: type[int, float] | str) -> None: ...
|
def func(arg: type[int, float] | str) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
# OK
|
||||||
|
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
# PYI055
|
||||||
|
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
import builtins
|
import builtins
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||||
x: type[int] | type[str] | type[float]
|
x: type[int] | type[str] | type[float]
|
||||||
y: builtins.type[int] | type[str] | builtins.type[complex]
|
y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||||
z: Union[type[float], type[complex]]
|
z: Union[type[float], type[complex]]
|
||||||
z: Union[type[float, int], type[complex]]
|
z: Union[type[float, int], type[complex]]
|
||||||
|
|
||||||
|
|
||||||
def func(arg: type[int] | str | type[float]) -> None: ...
|
def func(arg: type[int] | str | type[float]) -> None: ...
|
||||||
|
|
||||||
# OK
|
# OK
|
||||||
|
|
@ -16,5 +14,11 @@ x: type[int, str, float]
|
||||||
y: builtins.type[int, str, complex]
|
y: builtins.type[int, str, complex]
|
||||||
z: Union[float, complex]
|
z: Union[float, complex]
|
||||||
|
|
||||||
|
|
||||||
def func(arg: type[int, float] | str) -> None: ...
|
def func(arg: type[int, float] | str) -> None: ...
|
||||||
|
|
||||||
|
# OK
|
||||||
|
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||||
|
|
||||||
|
def func():
|
||||||
|
# PYI055
|
||||||
|
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||||
|
|
|
||||||
|
|
@ -80,3 +80,15 @@ class Test(unittest.TestCase):
|
||||||
|
|
||||||
def test_assert_not_regexp_matches(self):
|
def test_assert_not_regexp_matches(self):
|
||||||
self.assertNotRegex("abc", r"abc") # Error
|
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
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
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), (a, b), (b, c)])
|
||||||
|
def test_error_expr_complex(x):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("x", [1, 2])
|
||||||
|
def test_ok(x):
|
||||||
|
...
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
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
|
||||||
|
|
@ -73,3 +73,7 @@ print(foo.__dict__)
|
||||||
print(foo.__str__())
|
print(foo.__str__())
|
||||||
print(foo().__class__)
|
print(foo().__class__)
|
||||||
print(foo._asdict())
|
print(foo._asdict())
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
os._exit()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
## 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
|
||||||
12
crates/ruff/resources/test/fixtures/flake8_type_checking/runtime_evaluated_base_classes_4.py
vendored
Normal file
12
crates/ruff/resources/test/fixtures/flake8_type_checking/runtime_evaluated_base_classes_4.py
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
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]
|
||||||
|
|
@ -202,3 +202,14 @@ class C:
|
||||||
###
|
###
|
||||||
def f(x: None) -> None:
|
def f(x: None) -> None:
|
||||||
_ = cast(Any, _identity)(x=x)
|
_ = cast(Any, _identity)(x=x)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Unused arguments with `locals`.
|
||||||
|
###
|
||||||
|
def f(bar: str):
|
||||||
|
print(locals())
|
||||||
|
|
||||||
|
|
||||||
|
class C:
|
||||||
|
def __init__(self, x) -> None:
|
||||||
|
print(locals())
|
||||||
|
|
|
||||||
6
crates/ruff/resources/test/fixtures/isort/required_imports/comments_and_newlines.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/isort/required_imports/comments_and_newlines.py
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# A copyright notice could go here
|
||||||
|
|
||||||
|
# A linter directive could go here
|
||||||
|
|
||||||
|
x = 1
|
||||||
|
|
@ -21,3 +21,29 @@ while i < 10:
|
||||||
print("error")
|
print("error")
|
||||||
|
|
||||||
i += 1
|
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")
|
||||||
|
|
|
||||||
|
|
@ -30,3 +30,10 @@ def foo() -> None:
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import g
|
import g
|
||||||
|
|
||||||
|
import h; import i
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import j; \
|
||||||
|
import k
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
a = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
# aaaa
|
||||||
a = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
# aaaaa
|
||||||
|
# a
|
||||||
|
# a
|
||||||
|
# aa
|
||||||
|
# aaa
|
||||||
|
# aaaa
|
||||||
|
# a
|
||||||
|
# aa
|
||||||
|
# aaa
|
||||||
|
|
||||||
b = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
if True: # noqa: E501
|
||||||
b = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
[12]
|
||||||
|
[12 ]
|
||||||
c = """2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
[1,2]
|
||||||
c = """2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
[1, 2]
|
||||||
|
|
||||||
d = """💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
|
||||||
d = """💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
|
||||||
|
|
|
||||||
|
|
@ -61,3 +61,30 @@ if x == types.X:
|
||||||
|
|
||||||
#: E721
|
#: E721
|
||||||
assert type(res) is int
|
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:
|
||||||
|
...
|
||||||
|
|
|
||||||
|
|
@ -92,3 +92,10 @@ match *0, 1, *2:
|
||||||
case 0,:
|
case 0,:
|
||||||
import x
|
import x
|
||||||
import y
|
import y
|
||||||
|
|
||||||
|
|
||||||
|
# Test: access a sub-importation via an alias.
|
||||||
|
import foo.bar as bop
|
||||||
|
import foo.bar.baz
|
||||||
|
|
||||||
|
print(bop.baz.read_csv("test.csv"))
|
||||||
|
|
|
||||||
|
|
@ -70,3 +70,13 @@ import requests_mock as rm
|
||||||
|
|
||||||
def requests_mock(requests_mock: rm.Mocker):
|
def requests_mock(requests_mock: rm.Mocker):
|
||||||
print(rm.ANY)
|
print(rm.ANY)
|
||||||
|
|
||||||
|
|
||||||
|
import sklearn.base
|
||||||
|
import mlflow.sklearn
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
import sklearn
|
||||||
|
|
||||||
|
mlflow
|
||||||
|
|
|
||||||
|
|
@ -145,3 +145,9 @@ def f() -> None:
|
||||||
obj = Foo()
|
obj = Foo()
|
||||||
obj.do_thing()
|
obj.do_thing()
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except Exception as _:
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
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
|
||||||
|
...
|
||||||
|
|
@ -13,6 +13,7 @@ print("foo %(foo)d bar %(bar)d" % {"foo": "1", "bar": "2"})
|
||||||
"%(key)d" % {"key": []}
|
"%(key)d" % {"key": []}
|
||||||
print("%d" % ("%s" % ("nested",),))
|
print("%d" % ("%s" % ("nested",),))
|
||||||
"%d" % ((1, 2, 3),)
|
"%d" % ((1, 2, 3),)
|
||||||
|
"%d" % (1 if x > 0 else [])
|
||||||
|
|
||||||
# False negatives
|
# False negatives
|
||||||
WORD = "abc"
|
WORD = "abc"
|
||||||
|
|
@ -55,3 +56,4 @@ r'\%03o' % (ord(c),)
|
||||||
"%d" % (len(foo),)
|
"%d" % (len(foo),)
|
||||||
'(%r, %r, %r, %r)' % (hostname, address, username, '$PASSWORD')
|
'(%r, %r, %r, %r)' % (hostname, address, username, '$PASSWORD')
|
||||||
'%r' % ({'server_school_roles': server_school_roles, 'is_school_multiserver_domain': is_school_multiserver_domain}, )
|
'%r' % ({'server_school_roles': server_school_roles, 'is_school_multiserver_domain': is_school_multiserver_domain}, )
|
||||||
|
"%d" % (1 if x > 0 else 2)
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ os.getenv("AA", "GOOD" + "BAD")
|
||||||
os.getenv("AA", "GOOD" + 1)
|
os.getenv("AA", "GOOD" + 1)
|
||||||
os.getenv("AA", "GOOD %s" % "BAD")
|
os.getenv("AA", "GOOD %s" % "BAD")
|
||||||
os.getenv("B", Z)
|
os.getenv("B", Z)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ os.getenv(key="foo", default="bar")
|
||||||
os.getenv(key=f"foo", default="bar")
|
os.getenv(key=f"foo", default="bar")
|
||||||
os.getenv(key="foo" + "bar", default=1)
|
os.getenv(key="foo" + "bar", default=1)
|
||||||
os.getenv(key=1 + "bar", default=1) # [invalid-envvar-value]
|
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"
|
AA = "aa"
|
||||||
os.getenv(AA)
|
os.getenv(AA)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
|
||||||
# do not handle keyword arguments
|
# do not handle keyword arguments
|
||||||
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d")
|
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d")
|
||||||
|
|
||||||
|
logging.info(msg="Hello %s")
|
||||||
|
|
||||||
|
logging.info(msg="Hello %s %s")
|
||||||
|
|
||||||
import warning
|
import warning
|
||||||
|
|
||||||
warning.warning("Hello %s %s", "World!")
|
warning.warning("Hello %s %s", "World!")
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@ logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
|
||||||
# do not handle keyword arguments
|
# do not handle keyword arguments
|
||||||
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d", {"objects": 1, "modifications": 1, "errors": 1})
|
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d", {"objects": 1, "modifications": 1, "errors": 1})
|
||||||
|
|
||||||
|
logging.info(msg="Hello")
|
||||||
|
|
||||||
|
logging.info(msg="Hello", something="else")
|
||||||
|
|
||||||
import warning
|
import warning
|
||||||
|
|
||||||
warning.warning("Hello %s", "World!", "again")
|
warning.warning("Hello %s", "World!", "again")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
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.
|
||||||
|
|
@ -32,3 +32,30 @@ print(
|
||||||
)
|
)
|
||||||
|
|
||||||
'{' '0}'.format(1)
|
'{' '0}'.format(1)
|
||||||
|
|
||||||
|
args = list(range(10))
|
||||||
|
kwargs = {x: x for x in range(10)}
|
||||||
|
|
||||||
|
"{0}".format(*args)
|
||||||
|
|
||||||
|
"{0}".format(**kwargs)
|
||||||
|
|
||||||
|
"{0}_{1}".format(*args)
|
||||||
|
|
||||||
|
"{0}_{1}".format(1, *args)
|
||||||
|
|
||||||
|
"{0}_{1}".format(1, 2, *args)
|
||||||
|
|
||||||
|
"{0}_{1}".format(*args, 1, 2)
|
||||||
|
|
||||||
|
"{0}_{1}_{2}".format(1, **kwargs)
|
||||||
|
|
||||||
|
"{0}_{1}_{2}".format(1, 2, **kwargs)
|
||||||
|
|
||||||
|
"{0}_{1}_{2}".format(1, 2, 3, **kwargs)
|
||||||
|
|
||||||
|
"{0}_{1}_{2}".format(1, 2, 3, *args, **kwargs)
|
||||||
|
|
||||||
|
"{1}_{0}".format(1, 2, *args)
|
||||||
|
|
||||||
|
"{1}_{0}".format(1, 2)
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,17 @@ f"{0}".format(1)
|
||||||
print(f"{0}".format(1))
|
print(f"{0}".format(1))
|
||||||
|
|
||||||
''.format(1)
|
''.format(1)
|
||||||
|
|
||||||
|
'{1} {0}'.format(*args)
|
||||||
|
|
||||||
|
"{1}_{0}".format(*args, 1)
|
||||||
|
|
||||||
|
"{1}_{0}".format(*args, 1, 2)
|
||||||
|
|
||||||
|
"{1}_{0}".format(1, **kwargs)
|
||||||
|
|
||||||
|
"{1}_{0}".format(1, foo=2)
|
||||||
|
|
||||||
|
"{1}_{0}".format(1, 2, **kwargs)
|
||||||
|
|
||||||
|
"{1}_{0}".format(1, 2, foo=3, bar=4)
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# These SHOULD change
|
|
||||||
|
|
||||||
args = list(range(10))
|
|
||||||
kwargs = {x: x for x in range(10)}
|
|
||||||
|
|
||||||
"{0}".format(*args)
|
|
||||||
|
|
||||||
"{0}".format(**kwargs)
|
|
||||||
|
|
||||||
"{0}_{1}".format(*args)
|
|
||||||
|
|
||||||
"{0}_{1}".format(1, *args)
|
|
||||||
|
|
||||||
"{1}_{0}".format(*args)
|
|
||||||
|
|
||||||
"{1}_{0}".format(1, *args)
|
|
||||||
|
|
||||||
"{0}_{1}".format(1, 2, *args)
|
|
||||||
|
|
||||||
"{0}_{1}".format(*args, 1, 2)
|
|
||||||
|
|
||||||
"{0}_{1}_{2}".format(1, **kwargs)
|
|
||||||
|
|
||||||
"{0}_{1}_{2}".format(1, 2, **kwargs)
|
|
||||||
|
|
||||||
"{0}_{1}_{2}".format(1, 2, 3, **kwargs)
|
|
||||||
|
|
||||||
"{0}_{1}_{2}".format(1, 2, 3, *args, **kwargs)
|
|
||||||
|
|
@ -5,11 +5,42 @@ from typing import TypeAlias
|
||||||
x: typing.TypeAlias = int
|
x: typing.TypeAlias = int
|
||||||
x: TypeAlias = int
|
x: TypeAlias = int
|
||||||
|
|
||||||
|
# UP040 simple generic
|
||||||
# UP040 with generics (todo)
|
|
||||||
T = typing.TypeVar["T"]
|
T = typing.TypeVar["T"]
|
||||||
x: typing.TypeAlias = list[T]
|
x: typing.TypeAlias = list[T]
|
||||||
|
|
||||||
|
# UP040 call style generic
|
||||||
|
T = typing.TypeVar("T")
|
||||||
|
x: typing.TypeAlias = list[T]
|
||||||
|
|
||||||
|
# UP040 bounded generic (todo)
|
||||||
|
T = typing.TypeVar("T", bound=int)
|
||||||
|
x: typing.TypeAlias = list[T]
|
||||||
|
|
||||||
|
T = typing.TypeVar("T", int, str)
|
||||||
|
x: typing.TypeAlias = list[T]
|
||||||
|
|
||||||
|
# UP040 contravariant generic (todo)
|
||||||
|
T = typing.TypeVar("T", contravariant=True)
|
||||||
|
x: typing.TypeAlias = list[T]
|
||||||
|
|
||||||
|
# UP040 covariant generic (todo)
|
||||||
|
T = typing.TypeVar("T", covariant=True)
|
||||||
|
x: typing.TypeAlias = list[T]
|
||||||
|
|
||||||
|
# UP040 in class scope
|
||||||
|
T = typing.TypeVar["T"]
|
||||||
|
class Foo:
|
||||||
|
# reference to global variable
|
||||||
|
x: typing.TypeAlias = list[T]
|
||||||
|
|
||||||
|
# reference to class variable
|
||||||
|
TCLS = typing.TypeVar["TCLS"]
|
||||||
|
y: typing.TypeAlias = list[TCLS]
|
||||||
|
|
||||||
|
# UP040 wont add generics in fix
|
||||||
|
T = typing.TypeVar(*args)
|
||||||
|
x: typing.TypeAlias = list[T]
|
||||||
|
|
||||||
# OK
|
# OK
|
||||||
x: TypeAlias
|
x: TypeAlias
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# ruff: noqa: RUF100
|
||||||
|
|
||||||
|
import os # noqa: F401
|
||||||
|
|
||||||
|
print(os.sep)
|
||||||
|
|
@ -52,3 +52,7 @@ def good(a: int):
|
||||||
def another_good(a):
|
def another_good(a):
|
||||||
if a % 2 == 0:
|
if a % 2 == 0:
|
||||||
raise GoodArgCantBeEven(a)
|
raise GoodArgCantBeEven(a)
|
||||||
|
|
||||||
|
|
||||||
|
def another_good():
|
||||||
|
raise NotImplementedError("This is acceptable too")
|
||||||
|
|
|
||||||
|
|
@ -179,16 +179,13 @@ fn is_only<T: PartialEq>(vec: &[T], value: &T) -> bool {
|
||||||
fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
|
fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
|
||||||
match parent {
|
match parent {
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
|
Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. })
|
|
||||||
| Stmt::ClassDef(ast::StmtClassDef { body, .. })
|
| Stmt::ClassDef(ast::StmtClassDef { body, .. })
|
||||||
| Stmt::With(ast::StmtWith { body, .. })
|
| Stmt::With(ast::StmtWith { body, .. }) => {
|
||||||
| Stmt::AsyncWith(ast::StmtAsyncWith { body, .. }) => {
|
|
||||||
if is_only(body, child) {
|
if is_only(body, child) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::For(ast::StmtFor { body, orelse, .. })
|
Stmt::For(ast::StmtFor { body, orelse, .. })
|
||||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
|
||||||
| Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
| Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
||||||
if is_only(body, child) || is_only(orelse, child) {
|
if is_only(body, child) || is_only(orelse, child) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -212,14 +209,7 @@ fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
|
||||||
handlers,
|
handlers,
|
||||||
orelse,
|
orelse,
|
||||||
finalbody,
|
finalbody,
|
||||||
range: _,
|
..
|
||||||
})
|
|
||||||
| Stmt::TryStar(ast::StmtTryStar {
|
|
||||||
body,
|
|
||||||
handlers,
|
|
||||||
orelse,
|
|
||||||
finalbody,
|
|
||||||
range: _,
|
|
||||||
}) => {
|
}) => {
|
||||||
if is_only(body, child)
|
if is_only(body, child)
|
||||||
|| is_only(orelse, child)
|
|| is_only(orelse, child)
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,15 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for binding in checker.semantic.bindings.iter() {
|
for binding in &*checker.semantic.bindings {
|
||||||
if checker.enabled(Rule::UnusedVariable) {
|
if checker.enabled(Rule::UnusedVariable) {
|
||||||
if binding.kind.is_bound_exception() && !binding.is_used() {
|
if binding.kind.is_bound_exception()
|
||||||
|
&& !binding.is_used()
|
||||||
|
&& !checker
|
||||||
|
.settings
|
||||||
|
.dummy_variable_rgx
|
||||||
|
.is_match(binding.name(checker.locator))
|
||||||
|
{
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
pyflakes::rules::UnusedVariable {
|
pyflakes::rules::UnusedVariable {
|
||||||
name: binding.name(checker.locator).to_string(),
|
name: binding.name(checker.locator).to_string(),
|
||||||
|
|
|
||||||
|
|
@ -11,22 +11,19 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||||
for snapshot in for_loops {
|
for snapshot in for_loops {
|
||||||
checker.semantic.restore(snapshot);
|
checker.semantic.restore(snapshot);
|
||||||
|
|
||||||
if let Stmt::For(ast::StmtFor {
|
let Stmt::For(ast::StmtFor {
|
||||||
target, iter, body, ..
|
target, iter, body, ..
|
||||||
})
|
}) = checker.semantic.current_statement()
|
||||||
| Stmt::AsyncFor(ast::StmtAsyncFor {
|
else {
|
||||||
target, iter, body, ..
|
unreachable!("Expected Stmt::For");
|
||||||
}) = &checker.semantic.stmt()
|
};
|
||||||
{
|
|
||||||
if checker.enabled(Rule::UnusedLoopControlVariable) {
|
if checker.enabled(Rule::UnusedLoopControlVariable) {
|
||||||
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
|
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::IncorrectDictIterator) {
|
if checker.enabled(Rule::IncorrectDictIterator) {
|
||||||
perflint::rules::incorrect_dict_iterator(checker, target, iter);
|
perflint::rules::incorrect_dict_iterator(checker, target, iter);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
unreachable!("Expected Expr::For | Expr::AsyncFor");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_semantic::analyze::{branch_detection, visibility};
|
use ruff_python_semantic::analyze::{branch_detection, visibility};
|
||||||
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
|
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
|
||||||
|
|
||||||
|
|
@ -112,7 +111,11 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||||
// If the bindings are in different forks, abort.
|
// If the bindings are in different forks, abort.
|
||||||
if shadowed.source.map_or(true, |left| {
|
if shadowed.source.map_or(true, |left| {
|
||||||
binding.source.map_or(true, |right| {
|
binding.source.map_or(true, |right| {
|
||||||
branch_detection::different_forks(left, right, &checker.semantic.stmts)
|
branch_detection::different_forks(
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
checker.semantic.statements(),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}) {
|
}) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -168,17 +171,26 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(statement_id) = shadowed.source else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
// If this is an overloaded function, abort.
|
// If this is an overloaded function, abort.
|
||||||
if shadowed.kind.is_function_definition()
|
if shadowed.kind.is_function_definition() {
|
||||||
&& visibility::is_overload(
|
if checker
|
||||||
cast::decorator_list(
|
.semantic
|
||||||
checker.semantic.stmts[shadowed.source.unwrap()],
|
.statement(statement_id)
|
||||||
),
|
.as_function_def_stmt()
|
||||||
|
.is_some_and(|function| {
|
||||||
|
visibility::is_overload(
|
||||||
|
&function.decorator_list,
|
||||||
&checker.semantic,
|
&checker.semantic,
|
||||||
)
|
)
|
||||||
|
})
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only enforce cross-scope shadowing for imports.
|
// Only enforce cross-scope shadowing for imports.
|
||||||
if !matches!(
|
if !matches!(
|
||||||
|
|
@ -195,7 +207,11 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||||
// If the bindings are in different forks, abort.
|
// If the bindings are in different forks, abort.
|
||||||
if shadowed.source.map_or(true, |left| {
|
if shadowed.source.map_or(true, |left| {
|
||||||
binding.source.map_or(true, |right| {
|
binding.source.map_or(true, |right| {
|
||||||
branch_detection::different_forks(left, right, &checker.semantic.stmts)
|
branch_detection::different_forks(
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
checker.semantic.statements(),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}) {
|
}) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -231,10 +247,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||||
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
|
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(
|
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Lambda(_)) {
|
||||||
scope.kind,
|
|
||||||
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Lambda(_)
|
|
||||||
) {
|
|
||||||
if checker.enabled(Rule::UnusedVariable) {
|
if checker.enabled(Rule::UnusedVariable) {
|
||||||
pyflakes::rules::unused_variable(checker, scope, &mut diagnostics);
|
pyflakes::rules::unused_variable(checker, scope, &mut diagnostics);
|
||||||
}
|
}
|
||||||
|
|
@ -260,10 +273,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(
|
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
|
||||||
scope.kind,
|
|
||||||
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Module
|
|
||||||
) {
|
|
||||||
if enforce_typing_imports {
|
if enforce_typing_imports {
|
||||||
let runtime_imports: Vec<&Binding> = checker
|
let runtime_imports: Vec<&Binding> = checker
|
||||||
.semantic
|
.semantic
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||||
expr.start(),
|
expr.start(),
|
||||||
));
|
));
|
||||||
|
|
||||||
if pydocstyle::helpers::should_ignore_docstring(contents) {
|
if pydocstyle::helpers::should_ignore_docstring(expr) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let location = checker.locator.compute_source_location(expr.start());
|
let location = checker.locator.compute_source_location(expr.start());
|
||||||
warn_user!(
|
warn_user!(
|
||||||
|
|
|
||||||
|
|
@ -80,17 +80,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
Rule::RedundantLiteralUnion,
|
Rule::RedundantLiteralUnion,
|
||||||
Rule::UnnecessaryTypeUnion,
|
Rule::UnnecessaryTypeUnion,
|
||||||
]) {
|
]) {
|
||||||
// Avoid duplicate checks if the parent is an `Union[...]` since these rules
|
// Avoid duplicate checks if the parent is a union, since these rules already
|
||||||
// traverse nested unions.
|
// traverse nested unions.
|
||||||
let is_unchecked_union = checker
|
if !checker.semantic.in_nested_union() {
|
||||||
.semantic
|
|
||||||
.expr_grandparent()
|
|
||||||
.and_then(Expr::as_subscript_expr)
|
|
||||||
.map_or(true, |parent| {
|
|
||||||
!checker.semantic.match_typing_expr(&parent.value, "Union")
|
|
||||||
});
|
|
||||||
|
|
||||||
if is_unchecked_union {
|
|
||||||
if checker.enabled(Rule::UnnecessaryLiteralUnion) {
|
if checker.enabled(Rule::UnnecessaryLiteralUnion) {
|
||||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||||
}
|
}
|
||||||
|
|
@ -206,11 +198,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
ExprContext::Store => {
|
ExprContext::Store => {
|
||||||
if checker.enabled(Rule::NonLowercaseVariableInFunction) {
|
if checker.enabled(Rule::NonLowercaseVariableInFunction) {
|
||||||
if checker.semantic.scope().kind.is_any_function() {
|
if checker.semantic.current_scope().kind.is_function() {
|
||||||
// Ignore globals.
|
// Ignore globals.
|
||||||
if !checker.semantic.scope().get(id).is_some_and(|binding_id| {
|
if !checker
|
||||||
|
.semantic
|
||||||
|
.current_scope()
|
||||||
|
.get(id)
|
||||||
|
.is_some_and(|binding_id| {
|
||||||
checker.semantic.binding(binding_id).is_global()
|
checker.semantic.binding(binding_id).is_global()
|
||||||
}) {
|
})
|
||||||
|
{
|
||||||
pep8_naming::rules::non_lowercase_variable_in_function(
|
pep8_naming::rules::non_lowercase_variable_in_function(
|
||||||
checker, expr, id,
|
checker, expr, id,
|
||||||
);
|
);
|
||||||
|
|
@ -219,7 +216,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
|
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
|
||||||
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
|
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
|
||||||
&checker.semantic.scope().kind
|
&checker.semantic.current_scope().kind
|
||||||
{
|
{
|
||||||
pep8_naming::rules::mixed_case_variable_in_class_scope(
|
pep8_naming::rules::mixed_case_variable_in_class_scope(
|
||||||
checker,
|
checker,
|
||||||
|
|
@ -230,7 +227,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::MixedCaseVariableInGlobalScope) {
|
if checker.enabled(Rule::MixedCaseVariableInGlobalScope) {
|
||||||
if matches!(checker.semantic.scope().kind, ScopeKind::Module) {
|
if matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
|
||||||
pep8_naming::rules::mixed_case_variable_in_global_scope(
|
pep8_naming::rules::mixed_case_variable_in_global_scope(
|
||||||
checker, expr, id,
|
checker, expr, id,
|
||||||
);
|
);
|
||||||
|
|
@ -243,7 +240,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind {
|
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
|
||||||
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
||||||
flake8_builtins::rules::builtin_attribute_shadowing(
|
flake8_builtins::rules::builtin_attribute_shadowing(
|
||||||
checker, class_def, id, *range,
|
checker, class_def, id, *range,
|
||||||
|
|
@ -264,7 +261,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
pylint::rules::load_before_global_declaration(checker, id, expr);
|
pylint::rules::load_before_global_declaration(checker, id, expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Attribute(ast::ExprAttribute { attr, value, .. }) => {
|
Expr::Attribute(attribute) => {
|
||||||
// Ex) typing.List[...]
|
// Ex) typing.List[...]
|
||||||
if checker.any_enabled(&[
|
if checker.any_enabled(&[
|
||||||
Rule::FutureRewritableTypeAnnotation,
|
Rule::FutureRewritableTypeAnnotation,
|
||||||
|
|
@ -326,7 +323,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::CollectionsNamedTuple) {
|
if checker.enabled(Rule::CollectionsNamedTuple) {
|
||||||
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
||||||
}
|
}
|
||||||
pandas_vet::rules::attr(checker, attr, value, expr);
|
pandas_vet::rules::attr(checker, attribute);
|
||||||
}
|
}
|
||||||
Expr::Call(
|
Expr::Call(
|
||||||
call @ ast::ExprCall {
|
call @ ast::ExprCall {
|
||||||
|
|
@ -405,7 +402,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::FormatLiterals) {
|
if checker.enabled(Rule::FormatLiterals) {
|
||||||
pyupgrade::rules::format_literals(checker, &summary, expr);
|
pyupgrade::rules::format_literals(checker, &summary, call);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::FString) {
|
if checker.enabled(Rule::FString) {
|
||||||
pyupgrade::rules::f_strings(
|
pyupgrade::rules::f_strings(
|
||||||
|
|
@ -421,9 +418,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
|
|
||||||
if checker.enabled(Rule::BadStringFormatCharacter) {
|
if checker.enabled(Rule::BadStringFormatCharacter) {
|
||||||
pylint::rules::bad_string_format_character::call(
|
pylint::rules::bad_string_format_character::call(
|
||||||
checker,
|
checker, val, location,
|
||||||
val.as_str(),
|
|
||||||
location,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -668,7 +663,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
flake8_comprehensions::rules::unnecessary_map(
|
flake8_comprehensions::rules::unnecessary_map(
|
||||||
checker,
|
checker,
|
||||||
expr,
|
expr,
|
||||||
checker.semantic.expr_parent(),
|
checker.semantic.current_expression_parent(),
|
||||||
func,
|
func,
|
||||||
args,
|
args,
|
||||||
);
|
);
|
||||||
|
|
@ -678,10 +673,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
checker, expr, func, args, keywords,
|
checker, expr, func, args, keywords,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::BooleanPositionalValueInFunctionCall) {
|
if checker.enabled(Rule::BooleanPositionalValueInCall) {
|
||||||
flake8_boolean_trap::rules::check_boolean_positional_value_in_function_call(
|
flake8_boolean_trap::rules::boolean_positional_value_in_call(checker, args, func);
|
||||||
checker, args, func,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::Debugger) {
|
if checker.enabled(Rule::Debugger) {
|
||||||
flake8_debugger::rules::debugger_call(checker, expr, func);
|
flake8_debugger::rules::debugger_call(checker, expr, func);
|
||||||
|
|
@ -763,9 +756,19 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
checker.diagnostics.push(diagnostic);
|
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) {
|
if checker.enabled(Rule::SubprocessPopenPreexecFn) {
|
||||||
pylint::rules::subprocess_popen_preexec_fn(checker, call);
|
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(&[
|
if checker.any_enabled(&[
|
||||||
Rule::PytestRaisesWithoutException,
|
Rule::PytestRaisesWithoutException,
|
||||||
Rule::PytestRaisesTooBroad,
|
Rule::PytestRaisesTooBroad,
|
||||||
|
|
@ -921,7 +924,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
pylint::rules::await_outside_async(checker, expr);
|
pylint::rules::await_outside_async(checker, expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::JoinedStr(ast::ExprJoinedStr { values, range: _ }) => {
|
Expr::FString(ast::ExprFString { values, .. }) => {
|
||||||
if checker.enabled(Rule::FStringMissingPlaceholders) {
|
if checker.enabled(Rule::FStringMissingPlaceholders) {
|
||||||
pyflakes::rules::f_string_missing_placeholders(expr, values, checker);
|
pyflakes::rules::f_string_missing_placeholders(expr, values, checker);
|
||||||
}
|
}
|
||||||
|
|
@ -948,7 +951,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
range: _,
|
range: _,
|
||||||
}) => {
|
}) => {
|
||||||
if let Expr::Constant(ast::ExprConstant {
|
if let Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Str(value),
|
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||||
..
|
..
|
||||||
}) = left.as_ref()
|
}) = left.as_ref()
|
||||||
{
|
{
|
||||||
|
|
@ -1082,47 +1085,34 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid duplicate checks if the parent is an `|` since these rules
|
// Avoid duplicate checks if the parent is a union, since these rules already
|
||||||
// traverse nested unions.
|
// traverse nested unions.
|
||||||
let is_unchecked_union = !matches!(
|
if !checker.semantic.in_nested_union() {
|
||||||
checker.semantic.expr_parent(),
|
|
||||||
Some(Expr::BinOp(ast::ExprBinOp {
|
|
||||||
op: Operator::BitOr,
|
|
||||||
..
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
if checker.enabled(Rule::DuplicateUnionMember)
|
if checker.enabled(Rule::DuplicateUnionMember)
|
||||||
&& checker.semantic.in_type_definition()
|
&& checker.semantic.in_type_definition()
|
||||||
&& is_unchecked_union
|
|
||||||
{
|
{
|
||||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::UnnecessaryLiteralUnion) && is_unchecked_union {
|
if checker.enabled(Rule::UnnecessaryLiteralUnion) {
|
||||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::RedundantLiteralUnion) && is_unchecked_union {
|
if checker.enabled(Rule::RedundantLiteralUnion) {
|
||||||
flake8_pyi::rules::redundant_literal_union(checker, expr);
|
flake8_pyi::rules::redundant_literal_union(checker, expr);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::UnnecessaryTypeUnion) && is_unchecked_union {
|
if checker.enabled(Rule::UnnecessaryTypeUnion) {
|
||||||
flake8_pyi::rules::unnecessary_type_union(checker, expr);
|
flake8_pyi::rules::unnecessary_type_union(checker, expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::UnaryOp(ast::ExprUnaryOp {
|
}
|
||||||
|
Expr::UnaryOp(
|
||||||
|
unary_op @ ast::ExprUnaryOp {
|
||||||
op,
|
op,
|
||||||
operand,
|
operand,
|
||||||
range: _,
|
range: _,
|
||||||
}) => {
|
},
|
||||||
let check_not_in = checker.enabled(Rule::NotInTest);
|
) => {
|
||||||
let check_not_is = checker.enabled(Rule::NotIsTest);
|
if checker.any_enabled(&[Rule::NotInTest, Rule::NotIsTest]) {
|
||||||
if check_not_in || check_not_is {
|
pycodestyle::rules::not_tests(checker, unary_op);
|
||||||
pycodestyle::rules::not_tests(
|
|
||||||
checker,
|
|
||||||
expr,
|
|
||||||
*op,
|
|
||||||
operand,
|
|
||||||
check_not_in,
|
|
||||||
check_not_is,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::UnaryPrefixIncrementDecrement) {
|
if checker.enabled(Rule::UnaryPrefixIncrementDecrement) {
|
||||||
flake8_bugbear::rules::unary_prefix_increment_decrement(
|
flake8_bugbear::rules::unary_prefix_increment_decrement(
|
||||||
|
|
@ -1147,18 +1137,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
range: _,
|
range: _,
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
let check_none_comparisons = checker.enabled(Rule::NoneComparison);
|
if checker.any_enabled(&[Rule::NoneComparison, Rule::TrueFalseComparison]) {
|
||||||
let check_true_false_comparisons = checker.enabled(Rule::TrueFalseComparison);
|
pycodestyle::rules::literal_comparisons(checker, compare);
|
||||||
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) {
|
if checker.enabled(Rule::IsLiteral) {
|
||||||
pyflakes::rules::invalid_literal_comparison(checker, left, ops, comparators, expr);
|
pyflakes::rules::invalid_literal_comparison(checker, left, ops, comparators, expr);
|
||||||
|
|
@ -1241,13 +1221,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::HardcodedTempFile) {
|
if checker.enabled(Rule::HardcodedTempFile) {
|
||||||
if let Some(diagnostic) = flake8_bandit::rules::hardcoded_tmp_directory(
|
flake8_bandit::rules::hardcoded_tmp_directory(checker, expr, value);
|
||||||
expr,
|
|
||||||
value,
|
|
||||||
&checker.settings.flake8_bandit.hardcoded_tmp_directory,
|
|
||||||
) {
|
|
||||||
checker.diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, kind.as_deref());
|
pyupgrade::rules::unicode_kind_prefix(checker, expr, kind.as_deref());
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,6 @@ use crate::rules::{flake8_bugbear, flake8_pyi, ruff};
|
||||||
|
|
||||||
/// Run lint rules over a [`Parameters`] syntax node.
|
/// Run lint rules over a [`Parameters`] syntax node.
|
||||||
pub(crate) fn parameters(parameters: &Parameters, checker: &mut Checker) {
|
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) {
|
if checker.enabled(Rule::FunctionCallInDefaultArgument) {
|
||||||
flake8_bugbear::rules::function_call_in_argument_default(checker, parameters);
|
flake8_bugbear::rules::function_call_in_argument_default(checker, parameters);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::BreakOutsideLoop) {
|
if checker.enabled(Rule::BreakOutsideLoop) {
|
||||||
if let Some(diagnostic) = pyflakes::rules::break_outside_loop(
|
if let Some(diagnostic) = pyflakes::rules::break_outside_loop(
|
||||||
stmt,
|
stmt,
|
||||||
&mut checker.semantic.parents().skip(1),
|
&mut checker.semantic.current_statements().skip(1),
|
||||||
) {
|
) {
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
@ -63,30 +63,24 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::ContinueOutsideLoop) {
|
if checker.enabled(Rule::ContinueOutsideLoop) {
|
||||||
if let Some(diagnostic) = pyflakes::rules::continue_outside_loop(
|
if let Some(diagnostic) = pyflakes::rules::continue_outside_loop(
|
||||||
stmt,
|
stmt,
|
||||||
&mut checker.semantic.parents().skip(1),
|
&mut checker.semantic.current_statements().skip(1),
|
||||||
) {
|
) {
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
Stmt::FunctionDef(
|
||||||
|
function_def @ ast::StmtFunctionDef {
|
||||||
|
is_async,
|
||||||
name,
|
name,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
returns,
|
returns,
|
||||||
parameters,
|
parameters,
|
||||||
body,
|
body,
|
||||||
type_params,
|
type_params,
|
||||||
..
|
range: _,
|
||||||
})
|
},
|
||||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
) => {
|
||||||
name,
|
|
||||||
decorator_list,
|
|
||||||
returns,
|
|
||||||
parameters,
|
|
||||||
body,
|
|
||||||
type_params,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
if checker.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
|
if checker.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
|
||||||
flake8_django::rules::non_leading_receiver_decorator(checker, decorator_list);
|
flake8_django::rules::non_leading_receiver_decorator(checker, decorator_list);
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +107,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if let Some(diagnostic) =
|
if let Some(diagnostic) =
|
||||||
pep8_naming::rules::invalid_first_argument_name_for_class_method(
|
pep8_naming::rules::invalid_first_argument_name_for_class_method(
|
||||||
checker,
|
checker,
|
||||||
checker.semantic.scope(),
|
checker.semantic.current_scope(),
|
||||||
name,
|
name,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
parameters,
|
parameters,
|
||||||
|
|
@ -125,7 +119,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::InvalidFirstArgumentNameForMethod) {
|
if checker.enabled(Rule::InvalidFirstArgumentNameForMethod) {
|
||||||
if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method(
|
if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method(
|
||||||
checker,
|
checker,
|
||||||
checker.semantic.scope(),
|
checker.semantic.current_scope(),
|
||||||
name,
|
name,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
parameters,
|
parameters,
|
||||||
|
|
@ -151,11 +145,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
flake8_pyi::rules::non_self_return_type(
|
flake8_pyi::rules::non_self_return_type(
|
||||||
checker,
|
checker,
|
||||||
stmt,
|
stmt,
|
||||||
|
*is_async,
|
||||||
name,
|
name,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
returns.as_ref().map(AsRef::as_ref),
|
returns.as_ref().map(AsRef::as_ref),
|
||||||
parameters,
|
parameters,
|
||||||
stmt.is_async_function_def_stmt(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::CustomTypeVarReturnType) {
|
if checker.enabled(Rule::CustomTypeVarReturnType) {
|
||||||
|
|
@ -181,19 +175,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::BadExitAnnotation) {
|
if checker.enabled(Rule::BadExitAnnotation) {
|
||||||
flake8_pyi::rules::bad_exit_annotation(
|
flake8_pyi::rules::bad_exit_annotation(checker, *is_async, name, parameters);
|
||||||
checker,
|
|
||||||
stmt.is_async_function_def_stmt(),
|
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::RedundantNumericUnion) {
|
if checker.enabled(Rule::RedundantNumericUnion) {
|
||||||
flake8_pyi::rules::redundant_numeric_union(checker, parameters);
|
flake8_pyi::rules::redundant_numeric_union(checker, parameters);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::DunderFunctionName) {
|
if checker.enabled(Rule::DunderFunctionName) {
|
||||||
if let Some(diagnostic) = pep8_naming::rules::dunder_function_name(
|
if let Some(diagnostic) = pep8_naming::rules::dunder_function_name(
|
||||||
checker.semantic.scope(),
|
checker.semantic.current_scope(),
|
||||||
stmt,
|
stmt,
|
||||||
name,
|
name,
|
||||||
&checker.settings.pep8_naming.ignore_names,
|
&checker.settings.pep8_naming.ignore_names,
|
||||||
|
|
@ -217,6 +206,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::CachedInstanceMethod) {
|
if checker.enabled(Rule::CachedInstanceMethod) {
|
||||||
flake8_bugbear::rules::cached_instance_method(checker, decorator_list);
|
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(&[
|
if checker.any_enabled(&[
|
||||||
Rule::UnnecessaryReturnNone,
|
Rule::UnnecessaryReturnNone,
|
||||||
Rule::ImplicitReturnValue,
|
Rule::ImplicitReturnValue,
|
||||||
|
|
@ -308,6 +300,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.any_enabled(&[
|
if checker.any_enabled(&[
|
||||||
Rule::PytestParametrizeNamesWrongType,
|
Rule::PytestParametrizeNamesWrongType,
|
||||||
Rule::PytestParametrizeValuesWrongType,
|
Rule::PytestParametrizeValuesWrongType,
|
||||||
|
Rule::PytestDuplicateParametrizeTestCases,
|
||||||
]) {
|
]) {
|
||||||
flake8_pytest_style::rules::parametrize(checker, decorator_list);
|
flake8_pytest_style::rules::parametrize(checker, decorator_list);
|
||||||
}
|
}
|
||||||
|
|
@ -317,16 +310,16 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
]) {
|
]) {
|
||||||
flake8_pytest_style::rules::marks(checker, decorator_list);
|
flake8_pytest_style::rules::marks(checker, decorator_list);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::BooleanPositionalArgInFunctionDefinition) {
|
if checker.enabled(Rule::BooleanTypeHintPositionalArgument) {
|
||||||
flake8_boolean_trap::rules::check_positional_boolean_in_def(
|
flake8_boolean_trap::rules::boolean_type_hint_positional_argument(
|
||||||
checker,
|
checker,
|
||||||
name,
|
name,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
parameters,
|
parameters,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::BooleanDefaultValueInFunctionDefinition) {
|
if checker.enabled(Rule::BooleanDefaultValuePositionalArgument) {
|
||||||
flake8_boolean_trap::rules::check_boolean_default_value_in_function_definition(
|
flake8_boolean_trap::rules::boolean_default_value_positional_argument(
|
||||||
checker,
|
checker,
|
||||||
name,
|
name,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
|
|
@ -348,7 +341,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::YieldInForLoop) {
|
if checker.enabled(Rule::YieldInForLoop) {
|
||||||
pyupgrade::rules::yield_in_for_loop(checker, stmt);
|
pyupgrade::rules::yield_in_for_loop(checker, stmt);
|
||||||
}
|
}
|
||||||
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind {
|
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
|
||||||
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
||||||
flake8_builtins::rules::builtin_method_shadowing(
|
flake8_builtins::rules::builtin_method_shadowing(
|
||||||
checker,
|
checker,
|
||||||
|
|
@ -519,17 +512,16 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::SingleStringSlots) {
|
if checker.enabled(Rule::SingleStringSlots) {
|
||||||
pylint::rules::single_string_slots(checker, class_def);
|
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: _ }) => {
|
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||||
if checker.enabled(Rule::MultipleImportsOnOneLine) {
|
if checker.enabled(Rule::MultipleImportsOnOneLine) {
|
||||||
pycodestyle::rules::multiple_imports_on_one_line(checker, stmt, names);
|
pycodestyle::rules::multiple_imports_on_one_line(checker, stmt, names);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
|
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
|
||||||
pycodestyle::rules::module_import_not_at_top_of_file(
|
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
|
||||||
checker,
|
|
||||||
stmt,
|
|
||||||
checker.locator,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::GlobalStatement) {
|
if checker.enabled(Rule::GlobalStatement) {
|
||||||
for name in names {
|
for name in names {
|
||||||
|
|
@ -565,12 +557,29 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::BannedApi) {
|
if checker.enabled(Rule::BannedApi) {
|
||||||
flake8_tidy_imports::rules::name_or_parent_is_banned(
|
flake8_tidy_imports::rules::banned_api(
|
||||||
checker,
|
checker,
|
||||||
&alias.name,
|
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||||
alias,
|
flake8_tidy_imports::matchers::MatchNameOrParent {
|
||||||
|
module: &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.source_type.is_stub() {
|
||||||
if checker.enabled(Rule::UselessImportAlias) {
|
if checker.enabled(Rule::UselessImportAlias) {
|
||||||
pylint::rules::useless_import_alias(checker, alias);
|
pylint::rules::useless_import_alias(checker, alias);
|
||||||
|
|
@ -685,11 +694,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
let module = module.as_deref();
|
let module = module.as_deref();
|
||||||
let level = level.map(|level| level.to_u32());
|
let level = level.map(|level| level.to_u32());
|
||||||
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
|
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
|
||||||
pycodestyle::rules::module_import_not_at_top_of_file(
|
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
|
||||||
checker,
|
|
||||||
stmt,
|
|
||||||
checker.locator,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::GlobalStatement) {
|
if checker.enabled(Rule::GlobalStatement) {
|
||||||
for name in names {
|
for name in names {
|
||||||
|
|
@ -725,16 +730,56 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if let Some(module) =
|
if let Some(module) =
|
||||||
helpers::resolve_imported_module_path(level, module, checker.module_path)
|
helpers::resolve_imported_module_path(level, module, checker.module_path)
|
||||||
{
|
{
|
||||||
flake8_tidy_imports::rules::name_or_parent_is_banned(checker, &module, stmt);
|
flake8_tidy_imports::rules::banned_api(
|
||||||
|
checker,
|
||||||
|
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||||
|
flake8_tidy_imports::matchers::MatchNameOrParent { module: &module },
|
||||||
|
),
|
||||||
|
&stmt,
|
||||||
|
);
|
||||||
|
|
||||||
for alias in names {
|
for alias in names {
|
||||||
if &alias.name == "*" {
|
if &alias.name == "*" {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
flake8_tidy_imports::rules::name_is_banned(
|
flake8_tidy_imports::rules::banned_api(
|
||||||
checker,
|
checker,
|
||||||
format!("{module}.{}", alias.name),
|
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchName(
|
||||||
alias,
|
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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -766,7 +811,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
} else if &alias.name == "*" {
|
} else if &alias.name == "*" {
|
||||||
if checker.enabled(Rule::UndefinedLocalWithNestedImportStarUsage) {
|
if checker.enabled(Rule::UndefinedLocalWithNestedImportStarUsage) {
|
||||||
if !matches!(checker.semantic.scope().kind, ScopeKind::Module) {
|
if !matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
pyflakes::rules::UndefinedLocalWithNestedImportStarUsage {
|
pyflakes::rules::UndefinedLocalWithNestedImportStarUsage {
|
||||||
name: helpers::format_import_from(level, module),
|
name: helpers::format_import_from(level, module),
|
||||||
|
|
@ -982,7 +1027,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
flake8_simplify::rules::nested_if_statements(
|
flake8_simplify::rules::nested_if_statements(
|
||||||
checker,
|
checker,
|
||||||
if_,
|
if_,
|
||||||
checker.semantic.stmt_parent(),
|
checker.semantic.current_statement_parent(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::IfWithSameArms) {
|
if checker.enabled(Rule::IfWithSameArms) {
|
||||||
|
|
@ -1004,7 +1049,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
tryceratops::rules::type_check_without_type_error(
|
tryceratops::rules::type_check_without_type_error(
|
||||||
checker,
|
checker,
|
||||||
if_,
|
if_,
|
||||||
checker.semantic.stmt_parent(),
|
checker.semantic.current_statement_parent(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::OutdatedVersionBlock) {
|
if checker.enabled(Rule::OutdatedVersionBlock) {
|
||||||
|
|
@ -1097,8 +1142,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
pygrep_hooks::rules::non_existent_mock_method(checker, test);
|
pygrep_hooks::rules::non_existent_mock_method(checker, test);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::With(ast::StmtWith { items, body, .. })
|
Stmt::With(with_ @ ast::StmtWith { items, body, .. }) => {
|
||||||
| Stmt::AsyncWith(ast::StmtAsyncWith { items, body, .. }) => {
|
|
||||||
if checker.enabled(Rule::AssertRaisesException) {
|
if checker.enabled(Rule::AssertRaisesException) {
|
||||||
flake8_bugbear::rules::assert_raises_exception(checker, items);
|
flake8_bugbear::rules::assert_raises_exception(checker, items);
|
||||||
}
|
}
|
||||||
|
|
@ -1108,9 +1152,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::MultipleWithStatements) {
|
if checker.enabled(Rule::MultipleWithStatements) {
|
||||||
flake8_simplify::rules::multiple_with_statements(
|
flake8_simplify::rules::multiple_with_statements(
|
||||||
checker,
|
checker,
|
||||||
stmt,
|
with_,
|
||||||
body,
|
checker.semantic.current_statement_parent(),
|
||||||
checker.semantic.stmt_parent(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::RedefinedLoopName) {
|
if checker.enabled(Rule::RedefinedLoopName) {
|
||||||
|
|
@ -1134,13 +1177,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
iter,
|
iter,
|
||||||
orelse,
|
orelse,
|
||||||
..
|
..
|
||||||
})
|
|
||||||
| Stmt::AsyncFor(ast::StmtAsyncFor {
|
|
||||||
target,
|
|
||||||
body,
|
|
||||||
iter,
|
|
||||||
orelse,
|
|
||||||
..
|
|
||||||
}) => {
|
}) => {
|
||||||
if checker.any_enabled(&[Rule::UnusedLoopControlVariable, Rule::IncorrectDictIterator])
|
if checker.any_enabled(&[Rule::UnusedLoopControlVariable, Rule::IncorrectDictIterator])
|
||||||
{
|
{
|
||||||
|
|
@ -1190,14 +1226,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
handlers,
|
handlers,
|
||||||
orelse,
|
orelse,
|
||||||
finalbody,
|
finalbody,
|
||||||
range: _,
|
..
|
||||||
})
|
|
||||||
| Stmt::TryStar(ast::StmtTryStar {
|
|
||||||
body,
|
|
||||||
handlers,
|
|
||||||
orelse,
|
|
||||||
finalbody,
|
|
||||||
range: _,
|
|
||||||
}) => {
|
}) => {
|
||||||
if checker.enabled(Rule::JumpStatementInFinally) {
|
if checker.enabled(Rule::JumpStatementInFinally) {
|
||||||
flake8_bugbear::rules::jump_statement_in_finally(checker, finalbody);
|
flake8_bugbear::rules::jump_statement_in_finally(checker, finalbody);
|
||||||
|
|
@ -1338,8 +1367,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
// Ignore assignments in function bodies; those are covered by other rules.
|
// Ignore assignments in function bodies; those are covered by other rules.
|
||||||
if !checker
|
if !checker
|
||||||
.semantic
|
.semantic
|
||||||
.scopes()
|
.current_scopes()
|
||||||
.any(|scope| scope.kind.is_any_function())
|
.any(|scope| scope.kind.is_function())
|
||||||
{
|
{
|
||||||
if checker.enabled(Rule::UnprefixedTypeParam) {
|
if checker.enabled(Rule::UnprefixedTypeParam) {
|
||||||
flake8_pyi::rules::prefix_type_params(checker, value, targets);
|
flake8_pyi::rules::prefix_type_params(checker, value, targets);
|
||||||
|
|
@ -1403,8 +1432,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
// Ignore assignments in function bodies; those are covered by other rules.
|
// Ignore assignments in function bodies; those are covered by other rules.
|
||||||
if !checker
|
if !checker
|
||||||
.semantic
|
.semantic
|
||||||
.scopes()
|
.current_scopes()
|
||||||
.any(|scope| scope.kind.is_any_function())
|
.any(|scope| scope.kind.is_function())
|
||||||
{
|
{
|
||||||
flake8_pyi::rules::annotated_assignment_default_in_stub(
|
flake8_pyi::rules::annotated_assignment_default_in_stub(
|
||||||
checker, target, value, annotation,
|
checker, target, value, annotation,
|
||||||
|
|
|
||||||
|
|
@ -176,13 +176,12 @@ impl<'a> Checker<'a> {
|
||||||
///
|
///
|
||||||
/// If the current expression in the context is not an f-string, returns ``None``.
|
/// If the current expression in the context is not an f-string, returns ``None``.
|
||||||
pub(crate) fn f_string_quote_style(&self) -> Option<Quote> {
|
pub(crate) fn f_string_quote_style(&self) -> Option<Quote> {
|
||||||
let model = &self.semantic;
|
if !self.semantic.in_f_string() {
|
||||||
if !model.in_f_string() {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the quote character used to start the containing f-string.
|
// Find the quote character used to start the containing f-string.
|
||||||
let expr = model.expr()?;
|
let expr = self.semantic.current_expression()?;
|
||||||
let string_range = self.indexer.f_string_range(expr.start())?;
|
let string_range = self.indexer.f_string_range(expr.start())?;
|
||||||
let trailing_quote = trailing_quote(self.locator.slice(string_range))?;
|
let trailing_quote = trailing_quote(self.locator.slice(string_range))?;
|
||||||
|
|
||||||
|
|
@ -202,7 +201,7 @@ impl<'a> Checker<'a> {
|
||||||
/// thus be applied whenever we delete a statement, but can otherwise be omitted.
|
/// thus be applied whenever we delete a statement, but can otherwise be omitted.
|
||||||
pub(crate) fn isolation(&self, parent: Option<&Stmt>) -> IsolationLevel {
|
pub(crate) fn isolation(&self, parent: Option<&Stmt>) -> IsolationLevel {
|
||||||
parent
|
parent
|
||||||
.and_then(|stmt| self.semantic.stmts.node_id(stmt))
|
.and_then(|stmt| self.semantic.statement_id(stmt))
|
||||||
.map_or(IsolationLevel::default(), |node_id| {
|
.map_or(IsolationLevel::default(), |node_id| {
|
||||||
IsolationLevel::Group(node_id.into())
|
IsolationLevel::Group(node_id.into())
|
||||||
})
|
})
|
||||||
|
|
@ -264,7 +263,7 @@ where
|
||||||
{
|
{
|
||||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||||
// Step 0: Pre-processing
|
// Step 0: Pre-processing
|
||||||
self.semantic.push_stmt(stmt);
|
self.semantic.push_statement(stmt);
|
||||||
|
|
||||||
// Track whether we've seen docstrings, non-imports, etc.
|
// Track whether we've seen docstrings, non-imports, etc.
|
||||||
match stmt {
|
match stmt {
|
||||||
|
|
@ -288,7 +287,7 @@ where
|
||||||
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
|
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
|
||||||
if !self.semantic.seen_import_boundary()
|
if !self.semantic.seen_import_boundary()
|
||||||
&& !helpers::is_assignment_to_a_dunder(stmt)
|
&& !helpers::is_assignment_to_a_dunder(stmt)
|
||||||
&& !helpers::in_nested_block(self.semantic.parents())
|
&& !helpers::in_nested_block(self.semantic.current_statements())
|
||||||
{
|
{
|
||||||
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
|
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
|
||||||
}
|
}
|
||||||
|
|
@ -372,7 +371,7 @@ where
|
||||||
);
|
);
|
||||||
} else if &alias.name == "*" {
|
} else if &alias.name == "*" {
|
||||||
self.semantic
|
self.semantic
|
||||||
.scope_mut()
|
.current_scope_mut()
|
||||||
.add_star_import(StarImport { level, module });
|
.add_star_import(StarImport { level, module });
|
||||||
} else {
|
} else {
|
||||||
let mut flags = BindingFlags::EXTERNAL;
|
let mut flags = BindingFlags::EXTERNAL;
|
||||||
|
|
@ -421,7 +420,7 @@ where
|
||||||
BindingKind::Global,
|
BindingKind::Global,
|
||||||
BindingFlags::GLOBAL,
|
BindingFlags::GLOBAL,
|
||||||
);
|
);
|
||||||
let scope = self.semantic.scope_mut();
|
let scope = self.semantic.current_scope_mut();
|
||||||
scope.add(name, binding_id);
|
scope.add(name, binding_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -444,7 +443,7 @@ where
|
||||||
BindingKind::Nonlocal(scope_id),
|
BindingKind::Nonlocal(scope_id),
|
||||||
BindingFlags::NONLOCAL,
|
BindingFlags::NONLOCAL,
|
||||||
);
|
);
|
||||||
let scope = self.semantic.scope_mut();
|
let scope = self.semantic.current_scope_mut();
|
||||||
scope.add(name, binding_id);
|
scope.add(name, binding_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -455,22 +454,16 @@ where
|
||||||
|
|
||||||
// Step 2: Traversal
|
// Step 2: Traversal
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
Stmt::FunctionDef(
|
||||||
|
function_def @ ast::StmtFunctionDef {
|
||||||
body,
|
body,
|
||||||
parameters,
|
parameters,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
returns,
|
returns,
|
||||||
type_params,
|
type_params,
|
||||||
..
|
..
|
||||||
})
|
},
|
||||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
) => {
|
||||||
body,
|
|
||||||
parameters,
|
|
||||||
decorator_list,
|
|
||||||
type_params,
|
|
||||||
returns,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
// Visit the decorators and arguments, but avoid the body, which will be
|
// Visit the decorators and arguments, but avoid the body, which will be
|
||||||
// deferred.
|
// deferred.
|
||||||
for decorator in decorator_list {
|
for decorator in decorator_list {
|
||||||
|
|
@ -531,8 +524,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let definition = docstrings::extraction::extract_definition(
|
let definition = docstrings::extraction::extract_definition(
|
||||||
ExtractionTarget::Function,
|
ExtractionTarget::Function(function_def),
|
||||||
stmt,
|
|
||||||
self.semantic.definition_id,
|
self.semantic.definition_id,
|
||||||
&self.semantic.definitions,
|
&self.semantic.definitions,
|
||||||
);
|
);
|
||||||
|
|
@ -540,8 +532,7 @@ where
|
||||||
|
|
||||||
self.semantic.push_scope(match &stmt {
|
self.semantic.push_scope(match &stmt {
|
||||||
Stmt::FunctionDef(stmt) => ScopeKind::Function(stmt),
|
Stmt::FunctionDef(stmt) => ScopeKind::Function(stmt),
|
||||||
Stmt::AsyncFunctionDef(stmt) => ScopeKind::AsyncFunction(stmt),
|
_ => unreachable!("Expected Stmt::FunctionDef"),
|
||||||
_ => unreachable!("Expected Stmt::FunctionDef | Stmt::AsyncFunctionDef"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.deferred.functions.push(self.semantic.snapshot());
|
self.deferred.functions.push(self.semantic.snapshot());
|
||||||
|
|
@ -575,8 +566,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let definition = docstrings::extraction::extract_definition(
|
let definition = docstrings::extraction::extract_definition(
|
||||||
ExtractionTarget::Class,
|
ExtractionTarget::Class(class_def),
|
||||||
stmt,
|
|
||||||
self.semantic.definition_id,
|
self.semantic.definition_id,
|
||||||
&self.semantic.definitions,
|
&self.semantic.definitions,
|
||||||
);
|
);
|
||||||
|
|
@ -609,14 +599,7 @@ where
|
||||||
handlers,
|
handlers,
|
||||||
orelse,
|
orelse,
|
||||||
finalbody,
|
finalbody,
|
||||||
range: _,
|
..
|
||||||
})
|
|
||||||
| Stmt::TryStar(ast::StmtTryStar {
|
|
||||||
body,
|
|
||||||
handlers,
|
|
||||||
orelse,
|
|
||||||
finalbody,
|
|
||||||
range: _,
|
|
||||||
}) => {
|
}) => {
|
||||||
let mut handled_exceptions = Exceptions::empty();
|
let mut handled_exceptions = Exceptions::empty();
|
||||||
for type_ in extract_handled_exceptions(handlers) {
|
for type_ in extract_handled_exceptions(handlers) {
|
||||||
|
|
@ -657,7 +640,7 @@ where
|
||||||
// available at runtime.
|
// available at runtime.
|
||||||
// See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements
|
// See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements
|
||||||
let runtime_annotation = if self.semantic.future_annotations() {
|
let runtime_annotation = if self.semantic.future_annotations() {
|
||||||
if self.semantic.scope().kind.is_class() {
|
if self.semantic.current_scope().kind.is_class() {
|
||||||
let baseclasses = &self
|
let baseclasses = &self
|
||||||
.settings
|
.settings
|
||||||
.flake8_type_checking
|
.flake8_type_checking
|
||||||
|
|
@ -676,7 +659,7 @@ where
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
matches!(
|
matches!(
|
||||||
self.semantic.scope().kind,
|
self.semantic.current_scope().kind,
|
||||||
ScopeKind::Class(_) | ScopeKind::Module
|
ScopeKind::Class(_) | ScopeKind::Module
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
@ -743,8 +726,7 @@ where
|
||||||
|
|
||||||
// Step 3: Clean-up
|
// Step 3: Clean-up
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. })
|
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) => {
|
||||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { name, .. }) => {
|
|
||||||
let scope_id = self.semantic.scope_id;
|
let scope_id = self.semantic.scope_id;
|
||||||
self.deferred.scopes.push(scope_id);
|
self.deferred.scopes.push(scope_id);
|
||||||
self.semantic.pop_scope(); // Function scope
|
self.semantic.pop_scope(); // Function scope
|
||||||
|
|
@ -777,7 +759,7 @@ where
|
||||||
analyze::statement(stmt, self);
|
analyze::statement(stmt, self);
|
||||||
|
|
||||||
self.semantic.flags = flags_snapshot;
|
self.semantic.flags = flags_snapshot;
|
||||||
self.semantic.pop_stmt();
|
self.semantic.pop_statement();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_annotation(&mut self, expr: &'b Expr) {
|
fn visit_annotation(&mut self, expr: &'b Expr) {
|
||||||
|
|
@ -813,7 +795,7 @@ where
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.semantic.push_expr(expr);
|
self.semantic.push_expression(expr);
|
||||||
|
|
||||||
// Store the flags prior to any further descent, so that we can restore them after visiting
|
// Store the flags prior to any further descent, so that we can restore them after visiting
|
||||||
// the node.
|
// the node.
|
||||||
|
|
@ -841,7 +823,7 @@ where
|
||||||
}) => {
|
}) => {
|
||||||
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
|
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
|
||||||
if id == "locals" && ctx.is_load() {
|
if id == "locals" && ctx.is_load() {
|
||||||
let scope = self.semantic.scope_mut();
|
let scope = self.semantic.current_scope_mut();
|
||||||
scope.set_uses_locals();
|
scope.set_uses_locals();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1207,7 +1189,7 @@ where
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::JoinedStr(_) => {
|
Expr::FString(_) => {
|
||||||
self.semantic.flags |= SemanticModelFlags::F_STRING;
|
self.semantic.flags |= SemanticModelFlags::F_STRING;
|
||||||
visitor::walk_expr(self, expr);
|
visitor::walk_expr(self, expr);
|
||||||
}
|
}
|
||||||
|
|
@ -1231,7 +1213,7 @@ where
|
||||||
analyze::expression(expr, self);
|
analyze::expression(expr, self);
|
||||||
|
|
||||||
self.semantic.flags = flags_snapshot;
|
self.semantic.flags = flags_snapshot;
|
||||||
self.semantic.pop_expr();
|
self.semantic.pop_expression();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_except_handler(&mut self, except_handler: &'b ExceptHandler) {
|
fn visit_except_handler(&mut self, except_handler: &'b ExceptHandler) {
|
||||||
|
|
@ -1286,7 +1268,7 @@ where
|
||||||
|
|
||||||
fn visit_format_spec(&mut self, format_spec: &'b Expr) {
|
fn visit_format_spec(&mut self, format_spec: &'b Expr) {
|
||||||
match format_spec {
|
match format_spec {
|
||||||
Expr::JoinedStr(ast::ExprJoinedStr { values, range: _ }) => {
|
Expr::FString(ast::ExprFString { values, .. }) => {
|
||||||
for value in values {
|
for value in values {
|
||||||
self.visit_expr(value);
|
self.visit_expr(value);
|
||||||
}
|
}
|
||||||
|
|
@ -1611,7 +1593,7 @@ impl<'a> Checker<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
||||||
let parent = self.semantic.stmt();
|
let parent = self.semantic.current_statement();
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
parent,
|
parent,
|
||||||
|
|
@ -1626,7 +1608,7 @@ impl<'a> Checker<'a> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(parent, Stmt::For(_) | Stmt::AsyncFor(_)) {
|
if parent.is_for_stmt() {
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
id,
|
id,
|
||||||
expr.range(),
|
expr.range(),
|
||||||
|
|
@ -1646,7 +1628,7 @@ impl<'a> Checker<'a> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope = self.semantic.scope();
|
let scope = self.semantic.current_scope();
|
||||||
|
|
||||||
if scope.kind.is_module()
|
if scope.kind.is_module()
|
||||||
&& match parent {
|
&& match parent {
|
||||||
|
|
@ -1698,8 +1680,8 @@ impl<'a> Checker<'a> {
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.semantic
|
.semantic
|
||||||
.expr_ancestors()
|
.current_expressions()
|
||||||
.any(|expr| expr.is_named_expr_expr())
|
.any(Expr::is_named_expr_expr)
|
||||||
{
|
{
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
id,
|
id,
|
||||||
|
|
@ -1725,7 +1707,7 @@ impl<'a> Checker<'a> {
|
||||||
|
|
||||||
self.semantic.resolve_del(id, expr.range());
|
self.semantic.resolve_del(id, expr.range());
|
||||||
|
|
||||||
if helpers::on_conditional_branch(&mut self.semantic.parents()) {
|
if helpers::on_conditional_branch(&mut self.semantic.current_statements()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1733,7 +1715,7 @@ impl<'a> Checker<'a> {
|
||||||
let binding_id =
|
let binding_id =
|
||||||
self.semantic
|
self.semantic
|
||||||
.push_binding(expr.range(), BindingKind::Deletion, BindingFlags::empty());
|
.push_binding(expr.range(), BindingKind::Deletion, BindingFlags::empty());
|
||||||
let scope = self.semantic.scope_mut();
|
let scope = self.semantic.current_scope_mut();
|
||||||
scope.add(id, binding_id);
|
scope.add(id, binding_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1825,19 +1807,14 @@ impl<'a> Checker<'a> {
|
||||||
for snapshot in deferred_functions {
|
for snapshot in deferred_functions {
|
||||||
self.semantic.restore(snapshot);
|
self.semantic.restore(snapshot);
|
||||||
|
|
||||||
match &self.semantic.stmt() {
|
if let Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
|
||||||
body, parameters, ..
|
body, parameters, ..
|
||||||
})
|
}) = self.semantic.current_statement()
|
||||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
{
|
||||||
body, parameters, ..
|
|
||||||
}) => {
|
|
||||||
self.visit_parameters(parameters);
|
self.visit_parameters(parameters);
|
||||||
self.visit_body(body);
|
self.visit_body(body);
|
||||||
}
|
} else {
|
||||||
_ => {
|
unreachable!("Expected Stmt::FunctionDef")
|
||||||
unreachable!("Expected Stmt::FunctionDef | Stmt::AsyncFunctionDef")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,8 +94,15 @@ pub(crate) fn check_noqa(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce that the noqa directive was actually used (RUF100).
|
// Enforce that the noqa directive was actually used (RUF100), unless RUF100 was itself
|
||||||
if analyze_directives && settings.rules.enabled(Rule::UnusedNOQA) {
|
// suppressed.
|
||||||
|
if settings.rules.enabled(Rule::UnusedNOQA)
|
||||||
|
&& analyze_directives
|
||||||
|
&& !exemption.is_some_and(|exemption| match exemption {
|
||||||
|
FileExemption::All => true,
|
||||||
|
FileExemption::Codes(codes) => codes.contains(&Rule::UnusedNOQA.noqa_code()),
|
||||||
|
})
|
||||||
|
{
|
||||||
for line in noqa_directives.lines() {
|
for line in noqa_directives.lines() {
|
||||||
match &line.directive {
|
match &line.directive {
|
||||||
Directive::All(directive) => {
|
Directive::All(directive) => {
|
||||||
|
|
|
||||||
|
|
@ -226,8 +226,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Pylint, "W0711") => (RuleGroup::Unspecified, rules::pylint::rules::BinaryOpException),
|
(Pylint, "W0711") => (RuleGroup::Unspecified, rules::pylint::rules::BinaryOpException),
|
||||||
(Pylint, "W1508") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarDefault),
|
(Pylint, "W1508") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarDefault),
|
||||||
(Pylint, "W1509") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessPopenPreexecFn),
|
(Pylint, "W1509") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessPopenPreexecFn),
|
||||||
|
(Pylint, "W1510") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessRunWithoutCheck),
|
||||||
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
|
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
|
||||||
(Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
|
(Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
|
||||||
|
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
|
||||||
(Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax),
|
(Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax),
|
||||||
|
|
||||||
// flake8-async
|
// flake8-async
|
||||||
|
|
@ -309,6 +311,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
// flake8-tidy-imports
|
// flake8-tidy-imports
|
||||||
(Flake8TidyImports, "251") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::BannedApi),
|
(Flake8TidyImports, "251") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::BannedApi),
|
||||||
(Flake8TidyImports, "252") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::RelativeImports),
|
(Flake8TidyImports, "252") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::RelativeImports),
|
||||||
|
(Flake8TidyImports, "253") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::BannedModuleLevelImports),
|
||||||
|
|
||||||
// flake8-return
|
// flake8-return
|
||||||
(Flake8Return, "501") => (RuleGroup::Unspecified, rules::flake8_return::rules::UnnecessaryReturnNone),
|
(Flake8Return, "501") => (RuleGroup::Unspecified, rules::flake8_return::rules::UnnecessaryReturnNone),
|
||||||
|
|
@ -565,9 +568,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Flake8Bandit, "701") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
|
(Flake8Bandit, "701") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
|
||||||
|
|
||||||
// flake8-boolean-trap
|
// flake8-boolean-trap
|
||||||
(Flake8BooleanTrap, "001") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanPositionalArgInFunctionDefinition),
|
(Flake8BooleanTrap, "001") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanTypeHintPositionalArgument),
|
||||||
(Flake8BooleanTrap, "002") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanDefaultValueInFunctionDefinition),
|
(Flake8BooleanTrap, "002") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanDefaultValuePositionalArgument),
|
||||||
(Flake8BooleanTrap, "003") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanPositionalValueInFunctionCall),
|
(Flake8BooleanTrap, "003") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanPositionalValueInCall),
|
||||||
|
|
||||||
// flake8-unused-arguments
|
// flake8-unused-arguments
|
||||||
(Flake8UnusedArguments, "001") => (RuleGroup::Unspecified, rules::flake8_unused_arguments::rules::UnusedFunctionArgument),
|
(Flake8UnusedArguments, "001") => (RuleGroup::Unspecified, rules::flake8_unused_arguments::rules::UnusedFunctionArgument),
|
||||||
|
|
@ -682,6 +685,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Flake8PytestStyle, "011") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestRaisesTooBroad),
|
(Flake8PytestStyle, "011") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestRaisesTooBroad),
|
||||||
(Flake8PytestStyle, "012") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestRaisesWithMultipleStatements),
|
(Flake8PytestStyle, "012") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestRaisesWithMultipleStatements),
|
||||||
(Flake8PytestStyle, "013") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestIncorrectPytestImport),
|
(Flake8PytestStyle, "013") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestIncorrectPytestImport),
|
||||||
|
(Flake8PytestStyle, "014") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestDuplicateParametrizeTestCases),
|
||||||
(Flake8PytestStyle, "015") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestAssertAlwaysFalse),
|
(Flake8PytestStyle, "015") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestAssertAlwaysFalse),
|
||||||
(Flake8PytestStyle, "016") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestFailWithoutMessage),
|
(Flake8PytestStyle, "016") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestFailWithoutMessage),
|
||||||
(Flake8PytestStyle, "017") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestAssertInExcept),
|
(Flake8PytestStyle, "017") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestAssertInExcept),
|
||||||
|
|
@ -694,6 +698,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Flake8PytestStyle, "024") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUnnecessaryAsyncioMarkOnFixture),
|
(Flake8PytestStyle, "024") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUnnecessaryAsyncioMarkOnFixture),
|
||||||
(Flake8PytestStyle, "025") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestErroneousUseFixturesOnFixture),
|
(Flake8PytestStyle, "025") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestErroneousUseFixturesOnFixture),
|
||||||
(Flake8PytestStyle, "026") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUseFixturesWithoutParameters),
|
(Flake8PytestStyle, "026") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUseFixturesWithoutParameters),
|
||||||
|
(Flake8PytestStyle, "027") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUnittestRaisesAssertion),
|
||||||
|
|
||||||
// flake8-pie
|
// flake8-pie
|
||||||
(Flake8Pie, "790") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessaryPass),
|
(Flake8Pie, "790") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessaryPass),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
//! Extract docstrings from an AST.
|
//! Extract docstrings from an AST.
|
||||||
|
|
||||||
use ruff_python_ast::{self as ast, Constant, Expr, Stmt};
|
use ruff_python_ast::{self as ast, Constant, Expr, Stmt};
|
||||||
|
|
||||||
use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, MemberKind};
|
use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, MemberKind};
|
||||||
|
|
||||||
/// Extract a docstring from a function or class body.
|
/// Extract a docstring from a function or class body.
|
||||||
|
|
@ -28,63 +27,48 @@ pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||||
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
|
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
|
||||||
match definition {
|
match definition {
|
||||||
Definition::Module(module) => docstring_from(module.python_ast),
|
Definition::Module(module) => docstring_from(module.python_ast),
|
||||||
Definition::Member(member) => {
|
Definition::Member(member) => docstring_from(member.body()),
|
||||||
if let Stmt::ClassDef(ast::StmtClassDef { body, .. })
|
|
||||||
| Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
|
|
||||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. }) = &member.stmt
|
|
||||||
{
|
|
||||||
docstring_from(body)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) enum ExtractionTarget {
|
pub(crate) enum ExtractionTarget<'a> {
|
||||||
Class,
|
Class(&'a ast::StmtClassDef),
|
||||||
Function,
|
Function(&'a ast::StmtFunctionDef),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
||||||
pub(crate) fn extract_definition<'a>(
|
pub(crate) fn extract_definition<'a>(
|
||||||
target: ExtractionTarget,
|
target: ExtractionTarget<'a>,
|
||||||
stmt: &'a Stmt,
|
|
||||||
parent: DefinitionId,
|
parent: DefinitionId,
|
||||||
definitions: &Definitions<'a>,
|
definitions: &Definitions<'a>,
|
||||||
) -> Member<'a> {
|
) -> Member<'a> {
|
||||||
match target {
|
match target {
|
||||||
ExtractionTarget::Function => match &definitions[parent] {
|
ExtractionTarget::Function(function) => match &definitions[parent] {
|
||||||
Definition::Module(..) => Member {
|
Definition::Module(..) => Member {
|
||||||
parent,
|
parent,
|
||||||
kind: MemberKind::Function,
|
kind: MemberKind::Function(function),
|
||||||
stmt,
|
|
||||||
},
|
},
|
||||||
Definition::Member(Member {
|
Definition::Member(Member {
|
||||||
kind: MemberKind::Class | MemberKind::NestedClass,
|
kind: MemberKind::Class(_) | MemberKind::NestedClass(_),
|
||||||
..
|
..
|
||||||
}) => Member {
|
}) => Member {
|
||||||
parent,
|
parent,
|
||||||
kind: MemberKind::Method,
|
kind: MemberKind::Method(function),
|
||||||
stmt,
|
|
||||||
},
|
},
|
||||||
Definition::Member(..) => Member {
|
Definition::Member(_) => Member {
|
||||||
parent,
|
parent,
|
||||||
kind: MemberKind::NestedFunction,
|
kind: MemberKind::NestedFunction(function),
|
||||||
stmt,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ExtractionTarget::Class => match &definitions[parent] {
|
ExtractionTarget::Class(class) => match &definitions[parent] {
|
||||||
Definition::Module(..) => Member {
|
Definition::Module(_) => Member {
|
||||||
parent,
|
parent,
|
||||||
kind: MemberKind::Class,
|
kind: MemberKind::Class(class),
|
||||||
stmt,
|
|
||||||
},
|
},
|
||||||
Definition::Member(..) => Member {
|
Definition::Member(_) => Member {
|
||||||
parent,
|
parent,
|
||||||
kind: MemberKind::NestedClass,
|
kind: MemberKind::NestedClass(class),
|
||||||
stmt,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use globset::GlobMatcher;
|
use globset::GlobMatcher;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use path_absolutize::{path_dedot, Absolutize};
|
use path_absolutize::Absolutize;
|
||||||
|
|
||||||
use crate::registry::RuleSet;
|
use crate::registry::RuleSet;
|
||||||
|
|
||||||
|
|
@ -61,7 +61,13 @@ pub fn normalize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root:
|
||||||
/// Convert an absolute path to be relative to the current working directory.
|
/// Convert an absolute path to be relative to the current working directory.
|
||||||
pub fn relativize_path<P: AsRef<Path>>(path: P) -> String {
|
pub fn relativize_path<P: AsRef<Path>>(path: P) -> String {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let cwd = Path::new(".");
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let cwd = path_absolutize::path_dedot::CWD.as_path();
|
||||||
|
|
||||||
|
if let Ok(path) = path.strip_prefix(cwd) {
|
||||||
return format!("{}", path.display());
|
return format!("{}", path.display());
|
||||||
}
|
}
|
||||||
format!("{}", path.display())
|
format!("{}", path.display())
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,13 @@ impl<'a> Insertion<'a> {
|
||||||
TextSize::default()
|
TextSize::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Skip over commented lines.
|
// Skip over commented lines, with whitespace separation.
|
||||||
for line in UniversalNewlineIterator::with_offset(locator.after(location), location) {
|
for line in UniversalNewlineIterator::with_offset(locator.after(location), location) {
|
||||||
if line.trim_whitespace_start().starts_with('#') {
|
let trimmed_line = line.trim_whitespace_start();
|
||||||
|
if trimmed_line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if trimmed_line.starts_with('#') {
|
||||||
location = line.full_end();
|
location = line.full_end();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
use std::io::{BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ pub const JUPYTER_NOTEBOOK_EXT: &str = "ipynb";
|
||||||
|
|
||||||
/// Run round-trip source code generation on a given Jupyter notebook file path.
|
/// Run round-trip source code generation on a given Jupyter notebook file path.
|
||||||
pub fn round_trip(path: &Path) -> anyhow::Result<String> {
|
pub fn round_trip(path: &Path) -> anyhow::Result<String> {
|
||||||
let mut notebook = Notebook::read(path).map_err(|err| {
|
let mut notebook = Notebook::from_path(path).map_err(|err| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"Failed to read notebook file `{}`: {:?}",
|
"Failed to read notebook file `{}`: {:?}",
|
||||||
path.display(),
|
path.display(),
|
||||||
|
|
@ -120,18 +120,30 @@ pub struct Notebook {
|
||||||
|
|
||||||
impl Notebook {
|
impl Notebook {
|
||||||
/// Read the Jupyter Notebook from the given [`Path`].
|
/// Read the Jupyter Notebook from the given [`Path`].
|
||||||
///
|
pub fn from_path(path: &Path) -> Result<Self, Box<Diagnostic>> {
|
||||||
/// See also the black implementation
|
Self::from_reader(BufReader::new(File::open(path).map_err(|err| {
|
||||||
/// <https://github.com/psf/black/blob/69ca0a4c7a365c5f5eea519a90980bab72cab764/src/black/__init__.py#L1017-L1046>
|
|
||||||
pub fn read(path: &Path) -> Result<Self, Box<Diagnostic>> {
|
|
||||||
let mut reader = BufReader::new(File::open(path).map_err(|err| {
|
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
IOError {
|
IOError {
|
||||||
message: format!("{err}"),
|
message: format!("{err}"),
|
||||||
},
|
},
|
||||||
TextRange::default(),
|
TextRange::default(),
|
||||||
)
|
)
|
||||||
})?);
|
})?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the Jupyter Notebook from its JSON string.
|
||||||
|
pub fn from_contents(contents: &str) -> Result<Self, Box<Diagnostic>> {
|
||||||
|
Self::from_reader(Cursor::new(contents))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a Jupyter Notebook from a [`Read`] implementor.
|
||||||
|
///
|
||||||
|
/// See also the black implementation
|
||||||
|
/// <https://github.com/psf/black/blob/69ca0a4c7a365c5f5eea519a90980bab72cab764/src/black/__init__.py#L1017-L1046>
|
||||||
|
fn from_reader<R>(mut reader: R) -> Result<Self, Box<Diagnostic>>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
let trailing_newline = reader.seek(SeekFrom::End(-1)).is_ok_and(|_| {
|
let trailing_newline = reader.seek(SeekFrom::End(-1)).is_ok_and(|_| {
|
||||||
let mut buf = [0; 1];
|
let mut buf = [0; 1];
|
||||||
reader.read_exact(&mut buf).is_ok_and(|_| buf[0] == b'\n')
|
reader.read_exact(&mut buf).is_ok_and(|_| buf[0] == b'\n')
|
||||||
|
|
@ -144,7 +156,7 @@ impl Notebook {
|
||||||
TextRange::default(),
|
TextRange::default(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let raw_notebook: RawNotebook = match serde_json::from_reader(reader) {
|
let raw_notebook: RawNotebook = match serde_json::from_reader(reader.by_ref()) {
|
||||||
Ok(notebook) => notebook,
|
Ok(notebook) => notebook,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// Translate the error into a diagnostic
|
// Translate the error into a diagnostic
|
||||||
|
|
@ -159,7 +171,11 @@ impl Notebook {
|
||||||
Category::Syntax | Category::Eof => {
|
Category::Syntax | Category::Eof => {
|
||||||
// Maybe someone saved the python sources (those with the `# %%` separator)
|
// Maybe someone saved the python sources (those with the `# %%` separator)
|
||||||
// as jupyter notebook instead. Let's help them.
|
// as jupyter notebook instead. Let's help them.
|
||||||
let contents = std::fs::read_to_string(path).map_err(|err| {
|
let mut contents = String::new();
|
||||||
|
reader
|
||||||
|
.rewind()
|
||||||
|
.and_then(|_| reader.read_to_string(&mut contents))
|
||||||
|
.map_err(|err| {
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
IOError {
|
IOError {
|
||||||
message: format!("{err}"),
|
message: format!("{err}"),
|
||||||
|
|
@ -167,6 +183,7 @@ impl Notebook {
|
||||||
TextRange::default(),
|
TextRange::default(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Check if tokenizing was successful and the file is non-empty
|
// Check if tokenizing was successful and the file is non-empty
|
||||||
if lex(&contents, Mode::Module).any(|result| result.is_err()) {
|
if lex(&contents, Mode::Module).any(|result| result.is_err()) {
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
|
|
@ -182,7 +199,7 @@ impl Notebook {
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
SyntaxError {
|
SyntaxError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Expected a Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT} extension), \
|
"Expected a Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT}), \
|
||||||
which must be internally stored as JSON, \
|
which must be internally stored as JSON, \
|
||||||
but found a Python source file: {err}"
|
but found a Python source file: {err}"
|
||||||
),
|
),
|
||||||
|
|
@ -484,22 +501,22 @@ mod tests {
|
||||||
fn test_invalid() {
|
fn test_invalid() {
|
||||||
let path = Path::new("resources/test/fixtures/jupyter/invalid_extension.ipynb");
|
let path = Path::new("resources/test/fixtures/jupyter/invalid_extension.ipynb");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Notebook::read(path).unwrap_err().kind.body,
|
Notebook::from_path(path).unwrap_err().kind.body,
|
||||||
"SyntaxError: Expected a Jupyter Notebook (.ipynb extension), \
|
"SyntaxError: Expected a Jupyter Notebook (.ipynb), \
|
||||||
which must be internally stored as JSON, \
|
which must be internally stored as JSON, \
|
||||||
but found a Python source file: \
|
but found a Python source file: \
|
||||||
expected value at line 1 column 1"
|
expected value at line 1 column 1"
|
||||||
);
|
);
|
||||||
let path = Path::new("resources/test/fixtures/jupyter/not_json.ipynb");
|
let path = Path::new("resources/test/fixtures/jupyter/not_json.ipynb");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Notebook::read(path).unwrap_err().kind.body,
|
Notebook::from_path(path).unwrap_err().kind.body,
|
||||||
"SyntaxError: A Jupyter Notebook (.ipynb) must internally be JSON, \
|
"SyntaxError: A Jupyter Notebook (.ipynb) must internally be JSON, \
|
||||||
but this file isn't valid JSON: \
|
but this file isn't valid JSON: \
|
||||||
expected value at line 1 column 1"
|
expected value at line 1 column 1"
|
||||||
);
|
);
|
||||||
let path = Path::new("resources/test/fixtures/jupyter/wrong_schema.ipynb");
|
let path = Path::new("resources/test/fixtures/jupyter/wrong_schema.ipynb");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Notebook::read(path).unwrap_err().kind.body,
|
Notebook::from_path(path).unwrap_err().kind.body,
|
||||||
"SyntaxError: This file does not match the schema expected of Jupyter Notebooks: \
|
"SyntaxError: This file does not match the schema expected of Jupyter Notebooks: \
|
||||||
missing field `cells` at line 1 column 2"
|
missing field `cells` at line 1 column 2"
|
||||||
);
|
);
|
||||||
|
|
@ -571,11 +588,11 @@ print("after empty cells")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_line_magics() -> Result<()> {
|
fn test_ipy_escape_command() -> Result<()> {
|
||||||
let path = "line_magics.ipynb".to_string();
|
let path = "ipy_escape_command.ipynb".to_string();
|
||||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||||
&path,
|
&path,
|
||||||
Path::new("line_magics_expected.ipynb"),
|
Path::new("ipy_escape_command_expected.ipynb"),
|
||||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||||
)?;
|
)?;
|
||||||
assert_messages!(diagnostics, path, source_kind);
|
assert_messages!(diagnostics, path, source_kind);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/jupyter/notebook.rs
|
source: crates/ruff/src/jupyter/notebook.rs
|
||||||
---
|
---
|
||||||
line_magics.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
ipy_escape_command.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
||||||
|
|
|
|
||||||
3 | %matplotlib inline
|
3 | %matplotlib inline
|
||||||
4 |
|
4 |
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::num::NonZeroU8;
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
use ruff_macros::CacheKey;
|
use ruff_macros::CacheKey;
|
||||||
|
|
@ -58,7 +59,7 @@ impl Eq for LineWidth {}
|
||||||
|
|
||||||
impl PartialOrd for LineWidth {
|
impl PartialOrd for LineWidth {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
self.width.partial_cmp(&other.width)
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +84,7 @@ impl LineWidth {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(mut self, chars: impl Iterator<Item = char>) -> Self {
|
fn update(mut self, chars: impl Iterator<Item = char>) -> Self {
|
||||||
let tab_size: usize = self.tab_size.into();
|
let tab_size: usize = self.tab_size.as_usize();
|
||||||
for c in chars {
|
for c in chars {
|
||||||
match c {
|
match c {
|
||||||
'\t' => {
|
'\t' => {
|
||||||
|
|
@ -144,22 +145,22 @@ impl PartialOrd<LineLength> for LineWidth {
|
||||||
/// The size of a tab.
|
/// The size of a tab.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct TabSize(pub u8);
|
pub struct TabSize(NonZeroU8);
|
||||||
|
|
||||||
|
impl TabSize {
|
||||||
|
pub(crate) fn as_usize(self) -> usize {
|
||||||
|
self.0.get() as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for TabSize {
|
impl Default for TabSize {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(4)
|
Self(NonZeroU8::new(4).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u8> for TabSize {
|
impl From<NonZeroU8> for TabSize {
|
||||||
fn from(tab_size: u8) -> Self {
|
fn from(tab_size: NonZeroU8) -> Self {
|
||||||
Self(tab_size)
|
Self(tab_size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TabSize> for usize {
|
|
||||||
fn from(tab_size: TabSize) -> Self {
|
|
||||||
tab_size.0 as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -293,12 +293,10 @@ impl Display for MessageCodeFrame<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_whitespace(source: &str, annotation_range: TextRange) -> SourceCode {
|
fn replace_whitespace(source: &str, annotation_range: TextRange) -> SourceCode {
|
||||||
static TAB_SIZE: TabSize = TabSize(4); // TODO(jonathan): use `tab-size`
|
|
||||||
|
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut last_end = 0;
|
let mut last_end = 0;
|
||||||
let mut range = annotation_range;
|
let mut range = annotation_range;
|
||||||
let mut line_width = LineWidth::new(TAB_SIZE);
|
let mut line_width = LineWidth::new(TabSize::default());
|
||||||
|
|
||||||
for (index, c) in source.char_indices() {
|
for (index, c) in source.char_indices() {
|
||||||
let old_width = line_width.get();
|
let old_width = line_width.get();
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ impl RuleSet {
|
||||||
/// let set_1 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
|
/// let set_1 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
|
||||||
/// let set_2 = RuleSet::from_rules(&[
|
/// let set_2 = RuleSet::from_rules(&[
|
||||||
/// Rule::BadQuotesInlineString,
|
/// Rule::BadQuotesInlineString,
|
||||||
/// Rule::BooleanPositionalValueInFunctionCall,
|
/// Rule::BooleanPositionalValueInCall,
|
||||||
/// ]);
|
/// ]);
|
||||||
///
|
///
|
||||||
/// let union = set_1.union(&set_2);
|
/// let union = set_1.union(&set_2);
|
||||||
|
|
@ -80,7 +80,7 @@ impl RuleSet {
|
||||||
/// assert!(union.contains(Rule::AmbiguousFunctionName));
|
/// assert!(union.contains(Rule::AmbiguousFunctionName));
|
||||||
/// assert!(union.contains(Rule::AnyType));
|
/// assert!(union.contains(Rule::AnyType));
|
||||||
/// assert!(union.contains(Rule::BadQuotesInlineString));
|
/// assert!(union.contains(Rule::BadQuotesInlineString));
|
||||||
/// assert!(union.contains(Rule::BooleanPositionalValueInFunctionCall));
|
/// assert!(union.contains(Rule::BooleanPositionalValueInCall));
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn union(mut self, other: &Self) -> Self {
|
pub const fn union(mut self, other: &Self) -> Self {
|
||||||
|
|
@ -132,7 +132,7 @@ impl RuleSet {
|
||||||
/// ])));
|
/// ])));
|
||||||
///
|
///
|
||||||
/// assert!(!set_1.intersects(&RuleSet::from_rules(&[
|
/// assert!(!set_1.intersects(&RuleSet::from_rules(&[
|
||||||
/// Rule::BooleanPositionalValueInFunctionCall,
|
/// Rule::BooleanPositionalValueInCall,
|
||||||
/// Rule::BadQuotesInlineString
|
/// Rule::BadQuotesInlineString
|
||||||
/// ])));
|
/// ])));
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ pub(crate) fn variable_name_task_id(
|
||||||
// If the keyword argument is not a string, we can't do anything.
|
// If the keyword argument is not a string, we can't do anything.
|
||||||
let task_id = match &keyword.value {
|
let task_id = match &keyword.value {
|
||||||
Expr::Constant(constant) => match &constant.value {
|
Expr::Constant(constant) => match &constant.value {
|
||||||
Constant::Str(value) => value,
|
Constant::Str(ast::StringConstant { value, .. }) => value,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
_ => return None,
|
_ => return None,
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use ruff_python_ast::{PySourceType, Ranged, Stmt};
|
use ruff_python_ast::{PySourceType, Ranged};
|
||||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||||
|
|
||||||
use ruff_diagnostics::Edit;
|
use ruff_diagnostics::Edit;
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
|
|
||||||
/// ANN204
|
/// ANN204
|
||||||
pub(crate) fn add_return_annotation(
|
pub(crate) fn add_return_annotation<T: Ranged>(
|
||||||
locator: &Locator,
|
statement: &T,
|
||||||
stmt: &Stmt,
|
|
||||||
annotation: &str,
|
annotation: &str,
|
||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
|
locator: &Locator,
|
||||||
) -> Result<Edit> {
|
) -> Result<Edit> {
|
||||||
let contents = &locator.contents()[stmt.range()];
|
let contents = &locator.contents()[statement.range()];
|
||||||
|
|
||||||
// Find the colon (following the `def` keyword).
|
// Find the colon (following the `def` keyword).
|
||||||
let mut seen_lpar = false;
|
let mut seen_lpar = false;
|
||||||
let mut seen_rpar = false;
|
let mut seen_rpar = false;
|
||||||
let mut count = 0u32;
|
let mut count = 0u32;
|
||||||
for (tok, range) in
|
for (tok, range) in
|
||||||
lexer::lex_starts_at(contents, source_type.as_mode(), stmt.start()).flatten()
|
lexer::lex_starts_at(contents, source_type.as_mode(), statement.start()).flatten()
|
||||||
{
|
{
|
||||||
if seen_lpar && seen_rpar {
|
if seen_lpar && seen_rpar {
|
||||||
if matches!(tok, Tok::Colon) {
|
if matches!(tok, Tok::Colon) {
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,11 @@
|
||||||
use ruff_python_ast::{self as ast, Expr, Parameters, Stmt};
|
|
||||||
|
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_semantic::analyze::visibility;
|
use ruff_python_semantic::analyze::visibility;
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind, SemanticModel};
|
use ruff_python_semantic::{Definition, SemanticModel};
|
||||||
|
|
||||||
pub(super) fn match_function_def(
|
|
||||||
stmt: &Stmt,
|
|
||||||
) -> (&str, &Parameters, Option<&Expr>, &[Stmt], &[ast::Decorator]) {
|
|
||||||
match stmt {
|
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
returns,
|
|
||||||
body,
|
|
||||||
decorator_list,
|
|
||||||
..
|
|
||||||
})
|
|
||||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
returns,
|
|
||||||
body,
|
|
||||||
decorator_list,
|
|
||||||
..
|
|
||||||
}) => (
|
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
returns.as_ref().map(AsRef::as_ref),
|
|
||||||
body,
|
|
||||||
decorator_list,
|
|
||||||
),
|
|
||||||
_ => panic!("Found non-FunctionDef in match_function_def"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the name of the function, if it's overloaded.
|
/// Return the name of the function, if it's overloaded.
|
||||||
pub(crate) fn overloaded_name(definition: &Definition, semantic: &SemanticModel) -> Option<String> {
|
pub(crate) fn overloaded_name(definition: &Definition, semantic: &SemanticModel) -> Option<String> {
|
||||||
if let Definition::Member(Member {
|
let function = definition.as_function_def()?;
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
if visibility::is_overload(&function.decorator_list, semantic) {
|
||||||
stmt,
|
Some(function.name.to_string())
|
||||||
..
|
|
||||||
}) = definition
|
|
||||||
{
|
|
||||||
if visibility::is_overload(cast::decorator_list(stmt), semantic) {
|
|
||||||
let (name, ..) = match_function_def(stmt);
|
|
||||||
Some(name.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -60,19 +18,12 @@ pub(crate) fn is_overload_impl(
|
||||||
overloaded_name: &str,
|
overloaded_name: &str,
|
||||||
semantic: &SemanticModel,
|
semantic: &SemanticModel,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Definition::Member(Member {
|
let Some(function) = definition.as_function_def() else {
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
return false;
|
||||||
stmt,
|
};
|
||||||
..
|
if visibility::is_overload(&function.decorator_list, semantic) {
|
||||||
}) = definition
|
|
||||||
{
|
|
||||||
if visibility::is_overload(cast::decorator_list(stmt), semantic) {
|
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
let (name, ..) = match_function_def(stmt);
|
function.name.as_str() == overloaded_name
|
||||||
name == overloaded_name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,19 @@
|
||||||
use ruff_python_ast::{self as ast, Constant, Expr, ParameterWithDefault, Ranged, Stmt};
|
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, Violation};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||||
|
use ruff_python_ast::{self as ast, Constant, Expr, ParameterWithDefault, Ranged, Stmt};
|
||||||
use ruff_python_parser::typing::parse_type_annotation;
|
use ruff_python_parser::typing::parse_type_annotation;
|
||||||
use ruff_python_semantic::analyze::visibility;
|
use ruff_python_semantic::analyze::visibility;
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
use ruff_python_semantic::Definition;
|
||||||
use ruff_python_stdlib::typing::simple_magic_return_type;
|
use ruff_python_stdlib::typing::simple_magic_return_type;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::registry::{AsRule, Rule};
|
use crate::registry::{AsRule, Rule};
|
||||||
|
use crate::rules::flake8_annotations::fixes;
|
||||||
use crate::rules::ruff::typing::type_hint_resolves_to_any;
|
use crate::rules::ruff::typing::type_hint_resolves_to_any;
|
||||||
|
|
||||||
use super::super::fixes;
|
|
||||||
use super::super::helpers::match_function_def;
|
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks that function arguments have type annotations.
|
/// Checks that function arguments have type annotations.
|
||||||
///
|
///
|
||||||
|
|
@ -498,20 +494,23 @@ pub(crate) fn definition(
|
||||||
definition: &Definition,
|
definition: &Definition,
|
||||||
visibility: visibility::Visibility,
|
visibility: visibility::Visibility,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
let Some(function) = definition.as_function_def() else {
|
||||||
// We could adhere more closely to `flake8-annotations` by defining public
|
|
||||||
// vs. secret vs. protected.
|
|
||||||
let Definition::Member(Member { kind, stmt, .. }) = definition else {
|
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_method = match kind {
|
let ast::StmtFunctionDef {
|
||||||
MemberKind::Method => true,
|
range: _,
|
||||||
MemberKind::Function | MemberKind::NestedFunction => false,
|
is_async: _,
|
||||||
_ => return vec![],
|
decorator_list,
|
||||||
};
|
name,
|
||||||
|
type_params: _,
|
||||||
|
parameters,
|
||||||
|
returns,
|
||||||
|
body,
|
||||||
|
} = function;
|
||||||
|
|
||||||
|
let is_method = definition.is_method();
|
||||||
|
|
||||||
let (name, arguments, returns, body, decorator_list) = match_function_def(stmt);
|
|
||||||
// Keep track of whether we've seen any typed arguments or return values.
|
// Keep track of whether we've seen any typed arguments or return values.
|
||||||
let mut has_any_typed_arg = false; // Any argument has been typed?
|
let mut has_any_typed_arg = false; // Any argument has been typed?
|
||||||
let mut has_typed_return = false; // Return value has been typed?
|
let mut has_typed_return = false; // Return value has been typed?
|
||||||
|
|
@ -528,20 +527,19 @@ pub(crate) fn definition(
|
||||||
parameter,
|
parameter,
|
||||||
default: _,
|
default: _,
|
||||||
range: _,
|
range: _,
|
||||||
} in arguments
|
} in parameters
|
||||||
.posonlyargs
|
.posonlyargs
|
||||||
.iter()
|
.iter()
|
||||||
.chain(&arguments.args)
|
.chain(¶meters.args)
|
||||||
.chain(&arguments.kwonlyargs)
|
.chain(¶meters.kwonlyargs)
|
||||||
.skip(
|
.skip(
|
||||||
// If this is a non-static method, skip `cls` or `self`.
|
// If this is a non-static method, skip `cls` or `self`.
|
||||||
usize::from(
|
usize::from(
|
||||||
is_method
|
is_method && !visibility::is_staticmethod(decorator_list, checker.semantic()),
|
||||||
&& !visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic()),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// ANN401 for dynamically typed arguments
|
// ANN401 for dynamically typed parameters
|
||||||
if let Some(annotation) = ¶meter.annotation {
|
if let Some(annotation) = ¶meter.annotation {
|
||||||
has_any_typed_arg = true;
|
has_any_typed_arg = true;
|
||||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||||
|
|
@ -572,7 +570,7 @@ pub(crate) fn definition(
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANN002, ANN401
|
// ANN002, ANN401
|
||||||
if let Some(arg) = &arguments.vararg {
|
if let Some(arg) = ¶meters.vararg {
|
||||||
if let Some(expr) = &arg.annotation {
|
if let Some(expr) = &arg.annotation {
|
||||||
has_any_typed_arg = true;
|
has_any_typed_arg = true;
|
||||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||||
|
|
@ -598,7 +596,7 @@ pub(crate) fn definition(
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANN003, ANN401
|
// ANN003, ANN401
|
||||||
if let Some(arg) = &arguments.kwarg {
|
if let Some(arg) = ¶meters.kwarg {
|
||||||
if let Some(expr) = &arg.annotation {
|
if let Some(expr) = &arg.annotation {
|
||||||
has_any_typed_arg = true;
|
has_any_typed_arg = true;
|
||||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||||
|
|
@ -629,18 +627,18 @@ pub(crate) fn definition(
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANN101, ANN102
|
// ANN101, ANN102
|
||||||
if is_method && !visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic()) {
|
if is_method && !visibility::is_staticmethod(decorator_list, checker.semantic()) {
|
||||||
if let Some(ParameterWithDefault {
|
if let Some(ParameterWithDefault {
|
||||||
parameter,
|
parameter,
|
||||||
default: _,
|
default: _,
|
||||||
range: _,
|
range: _,
|
||||||
}) = arguments
|
}) = parameters
|
||||||
.posonlyargs
|
.posonlyargs
|
||||||
.first()
|
.first()
|
||||||
.or_else(|| arguments.args.first())
|
.or_else(|| parameters.args.first())
|
||||||
{
|
{
|
||||||
if parameter.annotation.is_none() {
|
if parameter.annotation.is_none() {
|
||||||
if visibility::is_classmethod(cast::decorator_list(stmt), checker.semantic()) {
|
if visibility::is_classmethod(decorator_list, checker.semantic()) {
|
||||||
if checker.enabled(Rule::MissingTypeCls) {
|
if checker.enabled(Rule::MissingTypeCls) {
|
||||||
diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
MissingTypeCls {
|
MissingTypeCls {
|
||||||
|
|
@ -676,24 +674,22 @@ pub(crate) fn definition(
|
||||||
// (explicitly or implicitly).
|
// (explicitly or implicitly).
|
||||||
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
|
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
|
||||||
) {
|
) {
|
||||||
if is_method && visibility::is_classmethod(cast::decorator_list(stmt), checker.semantic()) {
|
if is_method && visibility::is_classmethod(decorator_list, checker.semantic()) {
|
||||||
if checker.enabled(Rule::MissingReturnTypeClassMethod) {
|
if checker.enabled(Rule::MissingReturnTypeClassMethod) {
|
||||||
diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
MissingReturnTypeClassMethod {
|
MissingReturnTypeClassMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if is_method
|
} else if is_method && visibility::is_staticmethod(decorator_list, checker.semantic()) {
|
||||||
&& visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic())
|
|
||||||
{
|
|
||||||
if checker.enabled(Rule::MissingReturnTypeStaticMethod) {
|
if checker.enabled(Rule::MissingReturnTypeStaticMethod) {
|
||||||
diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
MissingReturnTypeStaticMethod {
|
MissingReturnTypeStaticMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if is_method && visibility::is_init(name) {
|
} else if is_method && visibility::is_init(name) {
|
||||||
|
|
@ -705,15 +701,15 @@ pub(crate) fn definition(
|
||||||
MissingReturnTypeSpecialMethod {
|
MissingReturnTypeSpecialMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
);
|
);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
fixes::add_return_annotation(
|
fixes::add_return_annotation(
|
||||||
checker.locator(),
|
function,
|
||||||
stmt,
|
|
||||||
"None",
|
"None",
|
||||||
checker.source_type,
|
checker.source_type,
|
||||||
|
checker.locator(),
|
||||||
)
|
)
|
||||||
.map(Fix::suggested)
|
.map(Fix::suggested)
|
||||||
});
|
});
|
||||||
|
|
@ -727,16 +723,16 @@ pub(crate) fn definition(
|
||||||
MissingReturnTypeSpecialMethod {
|
MissingReturnTypeSpecialMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
);
|
);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if let Some(return_type) = simple_magic_return_type(name) {
|
if let Some(return_type) = simple_magic_return_type(name) {
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
fixes::add_return_annotation(
|
fixes::add_return_annotation(
|
||||||
checker.locator(),
|
function,
|
||||||
stmt,
|
|
||||||
return_type,
|
return_type,
|
||||||
checker.source_type,
|
checker.source_type,
|
||||||
|
checker.locator(),
|
||||||
)
|
)
|
||||||
.map(Fix::suggested)
|
.map(Fix::suggested)
|
||||||
});
|
});
|
||||||
|
|
@ -752,7 +748,7 @@ pub(crate) fn definition(
|
||||||
MissingReturnTypeUndocumentedPublicFunction {
|
MissingReturnTypeUndocumentedPublicFunction {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -762,7 +758,7 @@ pub(crate) fn definition(
|
||||||
MissingReturnTypePrivateFunction {
|
MissingReturnTypePrivateFunction {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,21 +112,21 @@ fn py_stat(call_path: &CallPath) -> Option<u16> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn int_value(expr: &Expr, model: &SemanticModel) -> Option<u16> {
|
fn int_value(expr: &Expr, semantic: &SemanticModel) -> Option<u16> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Constant(ast::ExprConstant {
|
Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Int(value),
|
value: Constant::Int(value),
|
||||||
..
|
..
|
||||||
}) => value.to_u16(),
|
}) => value.to_u16(),
|
||||||
Expr::Attribute(_) => model.resolve_call_path(expr).as_ref().and_then(py_stat),
|
Expr::Attribute(_) => semantic.resolve_call_path(expr).as_ref().and_then(py_stat),
|
||||||
Expr::BinOp(ast::ExprBinOp {
|
Expr::BinOp(ast::ExprBinOp {
|
||||||
left,
|
left,
|
||||||
op,
|
op,
|
||||||
right,
|
right,
|
||||||
range: _,
|
range: _,
|
||||||
}) => {
|
}) => {
|
||||||
let left_value = int_value(left, model)?;
|
let left_value = int_value(left, semantic)?;
|
||||||
let right_value = int_value(right, model)?;
|
let right_value = int_value(right, semantic)?;
|
||||||
match op {
|
match op {
|
||||||
Operator::BitAnd => Some(left_value & right_value),
|
Operator::BitAnd => Some(left_value & right_value),
|
||||||
Operator::BitOr => Some(left_value | right_value),
|
Operator::BitOr => Some(left_value | right_value),
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ fn matches_sql_statement(string: &str) -> bool {
|
||||||
SQL_REGEX.is_match(string)
|
SQL_REGEX.is_match(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_string_format_expression(expr: &Expr, model: &SemanticModel) -> bool {
|
fn matches_string_format_expression(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
match expr {
|
match expr {
|
||||||
// "select * from table where val = " + "str" + ...
|
// "select * from table where val = " + "str" + ...
|
||||||
// "select * from table where val = %s" % ...
|
// "select * from table where val = %s" % ...
|
||||||
|
|
@ -62,8 +62,8 @@ fn matches_string_format_expression(expr: &Expr, model: &SemanticModel) -> bool
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
// Only evaluate the full BinOp, not the nested components.
|
// Only evaluate the full BinOp, not the nested components.
|
||||||
if model
|
if semantic
|
||||||
.expr_parent()
|
.current_expression_parent()
|
||||||
.map_or(true, |parent| !parent.is_bin_op_expr())
|
.map_or(true, |parent| !parent.is_bin_op_expr())
|
||||||
{
|
{
|
||||||
if any_over_expr(expr, &has_string_literal) {
|
if any_over_expr(expr, &has_string_literal) {
|
||||||
|
|
@ -80,7 +80,7 @@ fn matches_string_format_expression(expr: &Expr, model: &SemanticModel) -> bool
|
||||||
attr == "format" && string_literal(value).is_some()
|
attr == "format" && string_literal(value).is_some()
|
||||||
}
|
}
|
||||||
// f"select * from table where val = {val}"
|
// f"select * from table where val = {val}"
|
||||||
Expr::JoinedStr(_) => true,
|
Expr::FString(_) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use ruff_python_ast::{Expr, Ranged};
|
use ruff_python_ast::{self as ast, Expr, Ranged};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for the use of hardcoded temporary file or directory paths.
|
/// Checks for the use of hardcoded temporary file or directory paths.
|
||||||
///
|
///
|
||||||
|
|
@ -49,19 +51,33 @@ impl Violation for HardcodedTempFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// S108
|
/// S108
|
||||||
pub(crate) fn hardcoded_tmp_directory(
|
pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, expr: &Expr, value: &str) {
|
||||||
expr: &Expr,
|
if !checker
|
||||||
value: &str,
|
.settings
|
||||||
prefixes: &[String],
|
.flake8_bandit
|
||||||
) -> Option<Diagnostic> {
|
.hardcoded_tmp_directory
|
||||||
if prefixes.iter().any(|prefix| value.starts_with(prefix)) {
|
.iter()
|
||||||
Some(Diagnostic::new(
|
.any(|prefix| value.starts_with(prefix))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Expr::Call(ast::ExprCall { func, .. })) =
|
||||||
|
checker.semantic().current_expression_parent()
|
||||||
|
{
|
||||||
|
if checker
|
||||||
|
.semantic()
|
||||||
|
.resolve_call_path(func)
|
||||||
|
.is_some_and(|call_path| matches!(call_path.as_slice(), ["tempfile", ..]))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
HardcodedTempFile {
|
HardcodedTempFile {
|
||||||
string: value.to_string(),
|
string: value.to_string(),
|
||||||
},
|
},
|
||||||
expr.range(),
|
expr.range(),
|
||||||
))
|
));
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,31 @@ use crate::{
|
||||||
checkers::ast::Checker, registry::Rule, rules::flake8_bandit::helpers::string_literal,
|
checkers::ast::Checker, registry::Rule, rules::flake8_bandit::helpers::string_literal,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Check for method calls that initiate a subprocess with a shell.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Starting a subprocess with a shell can allow attackers to execute arbitrary
|
||||||
|
/// shell commands. Consider starting the process without a shell call and
|
||||||
|
/// sanitize the input to mitigate the risk of shell injection.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// import subprocess
|
||||||
|
///
|
||||||
|
/// subprocess.run("ls -l", shell=True)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// import subprocess
|
||||||
|
///
|
||||||
|
/// subprocess.run(["ls", "-l"])
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [Python documentation: `subprocess` — Subprocess management](https://docs.python.org/3/library/subprocess.html)
|
||||||
|
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct SubprocessPopenWithShellEqualsTrue {
|
pub struct SubprocessPopenWithShellEqualsTrue {
|
||||||
seems_safe: bool,
|
seems_safe: bool,
|
||||||
|
|
@ -28,6 +53,30 @@ impl Violation for SubprocessPopenWithShellEqualsTrue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Check for method calls that initiate a subprocess without a shell.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Starting a subprocess without a shell can prevent attackers from executing
|
||||||
|
/// arbitrary shell commands; however, it is still error-prone. Consider
|
||||||
|
/// validating the input.
|
||||||
|
///
|
||||||
|
/// ## Known problems
|
||||||
|
/// Prone to false positives as it is difficult to determine whether the
|
||||||
|
/// passed arguments have been validated ([#4045]).
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// import subprocess
|
||||||
|
///
|
||||||
|
/// cmd = input("Enter a command: ").split()
|
||||||
|
/// subprocess.run(cmd)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [Python documentation: `subprocess` — Subprocess management](https://docs.python.org/3/library/subprocess.html)
|
||||||
|
///
|
||||||
|
/// [#4045]: https://github.com/astral-sh/ruff/issues/4045
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct SubprocessWithoutShellEqualsTrue;
|
pub struct SubprocessWithoutShellEqualsTrue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ impl Violation for SuspiciousPickleUsage {
|
||||||
/// import marshal
|
/// import marshal
|
||||||
///
|
///
|
||||||
/// with open("foo.marshal", "rb") as file:
|
/// with open("foo.marshal", "rb") as file:
|
||||||
/// foo = pickle.load(file)
|
/// foo = marshal.load(file)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Use instead:
|
/// Use instead:
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
use ruff_python_ast::{self as ast, Constant, Expr, Ranged};
|
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
|
||||||
|
|
||||||
/// Returns `true` if a function call is allowed to use a boolean trap.
|
/// Returns `true` if a function call is allowed to use a boolean trap.
|
||||||
pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
||||||
|
|
@ -62,18 +58,13 @@ pub(super) fn allow_boolean_trap(func: &Expr) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn is_boolean_arg(arg: &Expr) -> bool {
|
/// Returns `true` if an expression is a boolean literal.
|
||||||
|
pub(super) const fn is_boolean(expr: &Expr) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
&arg,
|
&expr,
|
||||||
Expr::Constant(ast::ExprConstant {
|
Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Bool(_),
|
value: Constant::Bool(_),
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn add_if_boolean(checker: &mut Checker, arg: &Expr, kind: DiagnosticKind) {
|
|
||||||
if is_boolean_arg(arg) {
|
|
||||||
checker.diagnostics.push(Diagnostic::new(kind, arg.range()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ mod tests {
|
||||||
use crate::test::test_path;
|
use crate::test::test_path;
|
||||||
use crate::{assert_messages, settings};
|
use crate::{assert_messages, settings};
|
||||||
|
|
||||||
#[test_case(Rule::BooleanPositionalArgInFunctionDefinition, Path::new("FBT.py"))]
|
#[test_case(Rule::BooleanTypeHintPositionalArgument, Path::new("FBT.py"))]
|
||||||
#[test_case(Rule::BooleanDefaultValueInFunctionDefinition, Path::new("FBT.py"))]
|
#[test_case(Rule::BooleanDefaultValuePositionalArgument, Path::new("FBT.py"))]
|
||||||
#[test_case(Rule::BooleanPositionalValueInFunctionCall, Path::new("FBT.py"))]
|
#[test_case(Rule::BooleanPositionalValueInCall, Path::new("FBT.py"))]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::call_path::collect_call_path;
|
||||||
|
use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters, Ranged};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::rules::flake8_boolean_trap::helpers::{is_allowed_func_def, is_boolean};
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for the use of boolean positional arguments in function definitions,
|
||||||
|
/// as determined by the presence of a boolean default value.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Calling a function with boolean positional arguments is confusing as the
|
||||||
|
/// meaning of the boolean value is not clear to the caller and to future
|
||||||
|
/// readers of the code.
|
||||||
|
///
|
||||||
|
/// The use of a boolean will also limit the function to only two possible
|
||||||
|
/// behaviors, which makes the function difficult to extend in the future.
|
||||||
|
///
|
||||||
|
/// Instead, consider refactoring into separate implementations for the
|
||||||
|
/// `True` and `False` cases, using an `Enum`, or making the argument a
|
||||||
|
/// keyword-only argument, to force callers to be explicit when providing
|
||||||
|
/// the argument.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// from math import ceil, floor
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// def round_number(number, up=True):
|
||||||
|
/// return ceil(number) if up else floor(number)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// round_number(1.5, True) # What does `True` mean?
|
||||||
|
/// round_number(1.5, False) # What does `False` mean?
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Instead, refactor into separate implementations:
|
||||||
|
/// ```python
|
||||||
|
/// from math import ceil, floor
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// def round_up(number):
|
||||||
|
/// return ceil(number)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// def round_down(number):
|
||||||
|
/// return floor(number)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// round_up(1.5)
|
||||||
|
/// round_down(1.5)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Or, refactor to use an `Enum`:
|
||||||
|
/// ```python
|
||||||
|
/// from enum import Enum
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// class RoundingMethod(Enum):
|
||||||
|
/// UP = 1
|
||||||
|
/// DOWN = 2
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// def round_number(value, method):
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Or, make the argument a keyword-only argument:
|
||||||
|
/// ```python
|
||||||
|
/// from math import ceil, floor
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// def round_number(number, *, up=True):
|
||||||
|
/// return ceil(number) if up else floor(number)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// round_number(1.5, up=True)
|
||||||
|
/// round_number(1.5, up=False)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||||
|
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||||
|
#[violation]
|
||||||
|
pub struct BooleanDefaultValuePositionalArgument;
|
||||||
|
|
||||||
|
impl Violation for BooleanDefaultValuePositionalArgument {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!("Boolean default positional argument in function definition")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn boolean_default_value_positional_argument(
|
||||||
|
checker: &mut Checker,
|
||||||
|
name: &str,
|
||||||
|
decorator_list: &[Decorator],
|
||||||
|
parameters: &Parameters,
|
||||||
|
) {
|
||||||
|
if is_allowed_func_def(name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if decorator_list.iter().any(|decorator| {
|
||||||
|
collect_call_path(&decorator.expression)
|
||||||
|
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"])
|
||||||
|
}) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ParameterWithDefault {
|
||||||
|
parameter,
|
||||||
|
default,
|
||||||
|
range: _,
|
||||||
|
} in parameters.posonlyargs.iter().chain(¶meters.args)
|
||||||
|
{
|
||||||
|
if default.as_ref().is_some_and(|default| is_boolean(default)) {
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
BooleanDefaultValuePositionalArgument,
|
||||||
|
parameter.name.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use ruff_python_ast::Expr;
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
|
||||||
use ruff_diagnostics::Violation;
|
|
||||||
|
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::{Expr, Ranged};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::rules::flake8_boolean_trap::helpers::{add_if_boolean, allow_boolean_trap};
|
use crate::rules::flake8_boolean_trap::helpers::{allow_boolean_trap, is_boolean};
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for boolean positional arguments in function calls.
|
/// Checks for boolean positional arguments in function calls.
|
||||||
|
|
@ -17,44 +15,42 @@ use crate::rules::flake8_boolean_trap::helpers::{add_if_boolean, allow_boolean_t
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
/// def foo(flag: bool) -> None:
|
/// def func(flag: bool) -> None:
|
||||||
/// ...
|
/// ...
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// foo(True)
|
/// func(True)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Use instead:
|
/// Use instead:
|
||||||
/// ```python
|
/// ```python
|
||||||
/// def foo(flag: bool) -> None:
|
/// def func(flag: bool) -> None:
|
||||||
/// ...
|
/// ...
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// foo(flag=True)
|
/// func(flag=True)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## References
|
/// ## References
|
||||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct BooleanPositionalValueInFunctionCall;
|
pub struct BooleanPositionalValueInCall;
|
||||||
|
|
||||||
impl Violation for BooleanPositionalValueInFunctionCall {
|
impl Violation for BooleanPositionalValueInCall {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
format!("Boolean positional value in function call")
|
format!("Boolean positional value in function call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_boolean_positional_value_in_function_call(
|
pub(crate) fn boolean_positional_value_in_call(checker: &mut Checker, args: &[Expr], func: &Expr) {
|
||||||
checker: &mut Checker,
|
|
||||||
args: &[Expr],
|
|
||||||
func: &Expr,
|
|
||||||
) {
|
|
||||||
if allow_boolean_trap(func) {
|
if allow_boolean_trap(func) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for arg in args {
|
for arg in args.iter().filter(|arg| is_boolean(arg)) {
|
||||||
add_if_boolean(checker, arg, BooleanPositionalValueInFunctionCall.into());
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(BooleanPositionalValueInCall, arg.range()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,16 +11,22 @@ use crate::checkers::ast::Checker;
|
||||||
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for boolean positional arguments in function definitions.
|
/// Checks for the use of boolean positional arguments in function definitions,
|
||||||
|
/// as determined by the presence of a `bool` type hint.
|
||||||
///
|
///
|
||||||
/// ## Why is this bad?
|
/// ## Why is this bad?
|
||||||
/// Calling a function with boolean positional arguments is confusing as the
|
/// Calling a function with boolean positional arguments is confusing as the
|
||||||
/// meaning of the boolean value is not clear to the caller, and to future
|
/// meaning of the boolean value is not clear to the caller and to future
|
||||||
/// readers of the code.
|
/// readers of the code.
|
||||||
///
|
///
|
||||||
/// The use of a boolean will also limit the function to only two possible
|
/// The use of a boolean will also limit the function to only two possible
|
||||||
/// behaviors, which makes the function difficult to extend in the future.
|
/// behaviors, which makes the function difficult to extend in the future.
|
||||||
///
|
///
|
||||||
|
/// Instead, consider refactoring into separate implementations for the
|
||||||
|
/// `True` and `False` cases, using an `Enum`, or making the argument a
|
||||||
|
/// keyword-only argument, to force callers to be explicit when providing
|
||||||
|
/// the argument.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
/// from math import ceil, floor
|
/// from math import ceil, floor
|
||||||
|
|
@ -65,20 +71,33 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||||
/// ...
|
/// ...
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// Or, make the argument a keyword-only argument:
|
||||||
|
/// ```python
|
||||||
|
/// from math import ceil, floor
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// def round_number(number: float, *, up: bool) -> int:
|
||||||
|
/// return ceil(number) if up else floor(number)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// round_number(1.5, up=True)
|
||||||
|
/// round_number(1.5, up=False)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// ## References
|
/// ## References
|
||||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct BooleanPositionalArgInFunctionDefinition;
|
pub struct BooleanTypeHintPositionalArgument;
|
||||||
|
|
||||||
impl Violation for BooleanPositionalArgInFunctionDefinition {
|
impl Violation for BooleanTypeHintPositionalArgument {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
format!("Boolean positional arg in function definition")
|
format!("Boolean-typed positional argument in function definition")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_positional_boolean_in_def(
|
pub(crate) fn boolean_type_hint_positional_argument(
|
||||||
checker: &mut Checker,
|
checker: &mut Checker,
|
||||||
name: &str,
|
name: &str,
|
||||||
decorator_list: &[Decorator],
|
decorator_list: &[Decorator],
|
||||||
|
|
@ -101,28 +120,25 @@ pub(crate) fn check_positional_boolean_in_def(
|
||||||
range: _,
|
range: _,
|
||||||
} in parameters.posonlyargs.iter().chain(¶meters.args)
|
} in parameters.posonlyargs.iter().chain(¶meters.args)
|
||||||
{
|
{
|
||||||
if parameter.annotation.is_none() {
|
let Some(annotation) = parameter.annotation.as_ref() else {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let Some(expr) = ¶meter.annotation else {
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// check for both bool (python class) and 'bool' (string annotation)
|
// check for both bool (python class) and 'bool' (string annotation)
|
||||||
let hint = match expr.as_ref() {
|
let hint = match annotation.as_ref() {
|
||||||
Expr::Name(name) => &name.id == "bool",
|
Expr::Name(name) => &name.id == "bool",
|
||||||
Expr::Constant(ast::ExprConstant {
|
Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Str(value),
|
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||||
..
|
..
|
||||||
}) => value == "bool",
|
}) => value == "bool",
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
if !hint {
|
if !hint || !checker.semantic().is_builtin("bool") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
BooleanPositionalArgInFunctionDefinition,
|
BooleanTypeHintPositionalArgument,
|
||||||
parameter.range(),
|
parameter.name.range(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters};
|
|
||||||
|
|
||||||
use ruff_diagnostics::Violation;
|
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
|
||||||
use ruff_python_ast::call_path::collect_call_path;
|
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
|
||||||
use crate::rules::flake8_boolean_trap::helpers::{add_if_boolean, is_allowed_func_def};
|
|
||||||
|
|
||||||
/// ## What it does
|
|
||||||
/// Checks for the use of booleans as default values in function definitions.
|
|
||||||
///
|
|
||||||
/// ## Why is this bad?
|
|
||||||
/// Calling a function with boolean default means that the keyword argument
|
|
||||||
/// argument can be omitted, which makes the function call ambiguous.
|
|
||||||
///
|
|
||||||
/// Instead, define the relevant argument as keyword-only.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
/// ```python
|
|
||||||
/// from math import ceil, floor
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// def round_number(number: float, *, up: bool = True) -> int:
|
|
||||||
/// return ceil(number) if up else floor(number)
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// round_number(1.5)
|
|
||||||
/// round_number(1.5, up=False)
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Use instead:
|
|
||||||
/// ```python
|
|
||||||
/// from math import ceil, floor
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// def round_number(number: float, *, up: bool) -> int:
|
|
||||||
/// return ceil(number) if up else floor(number)
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// round_number(1.5, up=True)
|
|
||||||
/// round_number(1.5, up=False)
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## References
|
|
||||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
|
||||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
|
||||||
#[violation]
|
|
||||||
pub struct BooleanDefaultValueInFunctionDefinition;
|
|
||||||
|
|
||||||
impl Violation for BooleanDefaultValueInFunctionDefinition {
|
|
||||||
#[derive_message_formats]
|
|
||||||
fn message(&self) -> String {
|
|
||||||
format!("Boolean default value in function definition")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn check_boolean_default_value_in_function_definition(
|
|
||||||
checker: &mut Checker,
|
|
||||||
name: &str,
|
|
||||||
decorator_list: &[Decorator],
|
|
||||||
parameters: &Parameters,
|
|
||||||
) {
|
|
||||||
if is_allowed_func_def(name) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if decorator_list.iter().any(|decorator| {
|
|
||||||
collect_call_path(&decorator.expression)
|
|
||||||
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"])
|
|
||||||
}) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ParameterWithDefault {
|
|
||||||
parameter: _,
|
|
||||||
default,
|
|
||||||
range: _,
|
|
||||||
} in parameters.args.iter().chain(¶meters.posonlyargs)
|
|
||||||
{
|
|
||||||
let Some(default) = default else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
add_if_boolean(
|
|
||||||
checker,
|
|
||||||
default,
|
|
||||||
BooleanDefaultValueInFunctionDefinition.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
pub(crate) use check_boolean_default_value_in_function_definition::*;
|
pub(crate) use boolean_default_value_positional_argument::*;
|
||||||
pub(crate) use check_boolean_positional_value_in_function_call::*;
|
pub(crate) use boolean_positional_value_in_call::*;
|
||||||
pub(crate) use check_positional_boolean_in_def::*;
|
pub(crate) use boolean_type_hint_positional_argument::*;
|
||||||
|
|
||||||
mod check_boolean_default_value_in_function_definition;
|
mod boolean_default_value_positional_argument;
|
||||||
mod check_boolean_positional_value_in_function_call;
|
mod boolean_positional_value_in_call;
|
||||||
mod check_positional_boolean_in_def;
|
mod boolean_type_hint_positional_argument;
|
||||||
|
|
|
||||||
|
|
@ -1,91 +1,91 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/flake8_boolean_trap/mod.rs
|
source: crates/ruff/src/rules/flake8_boolean_trap/mod.rs
|
||||||
---
|
---
|
||||||
FBT.py:4:5: FBT001 Boolean positional arg in function definition
|
FBT.py:4:5: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
2 | posonly_nohint,
|
2 | posonly_nohint,
|
||||||
3 | posonly_nonboolhint: int,
|
3 | posonly_nonboolhint: int,
|
||||||
4 | posonly_boolhint: bool,
|
4 | posonly_boolhint: bool,
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
| ^^^^^^^^^^^^^^^^ FBT001
|
||||||
5 | posonly_boolstrhint: "bool",
|
5 | posonly_boolstrhint: "bool",
|
||||||
6 | /,
|
6 | /,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:5:5: FBT001 Boolean positional arg in function definition
|
FBT.py:5:5: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
3 | posonly_nonboolhint: int,
|
3 | posonly_nonboolhint: int,
|
||||||
4 | posonly_boolhint: bool,
|
4 | posonly_boolhint: bool,
|
||||||
5 | posonly_boolstrhint: "bool",
|
5 | posonly_boolstrhint: "bool",
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
| ^^^^^^^^^^^^^^^^^^^ FBT001
|
||||||
6 | /,
|
6 | /,
|
||||||
7 | offset,
|
7 | offset,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:10:5: FBT001 Boolean positional arg in function definition
|
FBT.py:10:5: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
8 | posorkw_nonvalued_nohint,
|
8 | posorkw_nonvalued_nohint,
|
||||||
9 | posorkw_nonvalued_nonboolhint: int,
|
9 | posorkw_nonvalued_nonboolhint: int,
|
||||||
10 | posorkw_nonvalued_boolhint: bool,
|
10 | posorkw_nonvalued_boolhint: bool,
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||||
12 | posorkw_boolvalued_nohint=True,
|
12 | posorkw_boolvalued_nohint=True,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:11:5: FBT001 Boolean positional arg in function definition
|
FBT.py:11:5: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
9 | posorkw_nonvalued_nonboolhint: int,
|
9 | posorkw_nonvalued_nonboolhint: int,
|
||||||
10 | posorkw_nonvalued_boolhint: bool,
|
10 | posorkw_nonvalued_boolhint: bool,
|
||||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||||
12 | posorkw_boolvalued_nohint=True,
|
12 | posorkw_boolvalued_nohint=True,
|
||||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:14:5: FBT001 Boolean positional arg in function definition
|
FBT.py:14:5: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
12 | posorkw_boolvalued_nohint=True,
|
12 | posorkw_boolvalued_nohint=True,
|
||||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||||
16 | posorkw_nonboolvalued_nohint=1,
|
16 | posorkw_nonboolvalued_nohint=1,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:15:5: FBT001 Boolean positional arg in function definition
|
FBT.py:15:5: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||||
16 | posorkw_nonboolvalued_nohint=1,
|
16 | posorkw_nonboolvalued_nohint=1,
|
||||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:18:5: FBT001 Boolean positional arg in function definition
|
FBT.py:18:5: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
16 | posorkw_nonboolvalued_nohint=1,
|
16 | posorkw_nonboolvalued_nohint=1,
|
||||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||||
18 | posorkw_nonboolvalued_boolhint: bool = 3,
|
18 | posorkw_nonboolvalued_boolhint: bool = 3,
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||||
19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
||||||
20 | *,
|
20 | *,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:19:5: FBT001 Boolean positional arg in function definition
|
FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||||
18 | posorkw_nonboolvalued_boolhint: bool = 3,
|
18 | posorkw_nonboolvalued_boolhint: bool = 3,
|
||||||
19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||||
20 | *,
|
20 | *,
|
||||||
21 | kwonly_nonvalued_nohint,
|
21 | kwonly_nonvalued_nohint,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:86:19: FBT001 Boolean positional arg in function definition
|
FBT.py:86:19: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
85 | # FBT001: Boolean positional arg in function definition
|
85 | # FBT001: Boolean positional arg in function definition
|
||||||
86 | def foo(self, value: bool) -> None:
|
86 | def foo(self, value: bool) -> None:
|
||||||
| ^^^^^^^^^^^ FBT001
|
| ^^^^^ FBT001
|
||||||
87 | pass
|
87 | pass
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,42 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/flake8_boolean_trap/mod.rs
|
source: crates/ruff/src/rules/flake8_boolean_trap/mod.rs
|
||||||
---
|
---
|
||||||
FBT.py:12:31: FBT002 Boolean default value in function definition
|
FBT.py:12:5: FBT002 Boolean default positional argument in function definition
|
||||||
|
|
|
|
||||||
10 | posorkw_nonvalued_boolhint: bool,
|
10 | posorkw_nonvalued_boolhint: bool,
|
||||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||||
12 | posorkw_boolvalued_nohint=True,
|
12 | posorkw_boolvalued_nohint=True,
|
||||||
| ^^^^ FBT002
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ FBT002
|
||||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:13:43: FBT002 Boolean default value in function definition
|
FBT.py:13:5: FBT002 Boolean default positional argument in function definition
|
||||||
|
|
|
|
||||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||||
12 | posorkw_boolvalued_nohint=True,
|
12 | posorkw_boolvalued_nohint=True,
|
||||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||||
| ^^^^ FBT002
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT002
|
||||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:14:41: FBT002 Boolean default value in function definition
|
FBT.py:14:5: FBT002 Boolean default positional argument in function definition
|
||||||
|
|
|
|
||||||
12 | posorkw_boolvalued_nohint=True,
|
12 | posorkw_boolvalued_nohint=True,
|
||||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||||
| ^^^^ FBT002
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT002
|
||||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||||
16 | posorkw_nonboolvalued_nohint=1,
|
16 | posorkw_nonboolvalued_nohint=1,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:15:46: FBT002 Boolean default value in function definition
|
FBT.py:15:5: FBT002 Boolean default positional argument in function definition
|
||||||
|
|
|
|
||||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||||
| ^^^^ FBT002
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT002
|
||||||
16 | posorkw_nonboolvalued_nohint=1,
|
16 | posorkw_nonboolvalued_nohint=1,
|
||||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||||
|
|
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ mod tests {
|
||||||
#[test_case(Rule::UselessComparison, Path::new("B015.py"))]
|
#[test_case(Rule::UselessComparison, Path::new("B015.py"))]
|
||||||
#[test_case(Rule::UselessContextlibSuppress, Path::new("B022.py"))]
|
#[test_case(Rule::UselessContextlibSuppress, Path::new("B022.py"))]
|
||||||
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
|
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
|
||||||
#[test_case(Rule::ZipWithoutExplicitStrict, Path::new("B905.py"))]
|
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
@ -60,6 +59,17 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zip_without_explicit_strict() -> Result<()> {
|
||||||
|
let snapshot = "B905.py";
|
||||||
|
let diagnostics = test_path(
|
||||||
|
Path::new("flake8_bugbear").join(snapshot).as_path(),
|
||||||
|
&Settings::for_rule(Rule::ZipWithoutExplicitStrict),
|
||||||
|
)?;
|
||||||
|
assert_messages!(snapshot, diagnostics);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn extend_immutable_calls() -> Result<()> {
|
fn extend_immutable_calls() -> Result<()> {
|
||||||
let snapshot = "extend_immutable_calls".to_string();
|
let snapshot = "extend_immutable_calls".to_string();
|
||||||
|
|
@ -72,7 +82,7 @@ mod tests {
|
||||||
"fastapi.Query".to_string(),
|
"fastapi.Query".to_string(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
..Settings::for_rules(vec![Rule::FunctionCallInDefaultArgument])
|
..Settings::for_rule(Rule::FunctionCallInDefaultArgument)
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
assert_messages!(snapshot, diagnostics);
|
assert_messages!(snapshot, diagnostics);
|
||||||
|
|
|
||||||
|
|
@ -159,18 +159,12 @@ pub(crate) fn abstract_base_class(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (Stmt::FunctionDef(ast::StmtFunctionDef {
|
let Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||||
decorator_list,
|
decorator_list,
|
||||||
body,
|
body,
|
||||||
name: method_name,
|
name: method_name,
|
||||||
..
|
..
|
||||||
})
|
}) = stmt
|
||||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
|
||||||
decorator_list,
|
|
||||||
body,
|
|
||||||
name: method_name,
|
|
||||||
..
|
|
||||||
})) = stmt
|
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
|
|
||||||
/// B019
|
/// B019
|
||||||
pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Decorator]) {
|
pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Decorator]) {
|
||||||
if !checker.semantic().scope().kind.is_class() {
|
if !checker.semantic().current_scope().kind.is_class() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for decorator in decorator_list {
|
for decorator in decorator_list {
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,9 @@ fn duplicate_handler_exceptions<'a>(
|
||||||
if unique_elts.len() == 1 {
|
if unique_elts.len() == 1 {
|
||||||
checker.generator().expr(unique_elts[0])
|
checker.generator().expr(unique_elts[0])
|
||||||
} else {
|
} else {
|
||||||
checker.generator().expr(&type_pattern(unique_elts))
|
// Multiple exceptions must always be parenthesized. This is done
|
||||||
|
// manually as the generator never parenthesizes lone tuples.
|
||||||
|
format!("({})", checker.generator().expr(&type_pattern(unique_elts)))
|
||||||
},
|
},
|
||||||
expr.range(),
|
expr.range(),
|
||||||
)));
|
)));
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ pub(crate) fn f_string_docstring(checker: &mut Checker, body: &[Stmt]) {
|
||||||
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
|
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !value.is_joined_str_expr() {
|
if !value.is_f_string_expr() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checker
|
checker
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,9 @@ struct ArgumentDefaultVisitor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ArgumentDefaultVisitor<'a> {
|
impl<'a> ArgumentDefaultVisitor<'a> {
|
||||||
fn new(model: &'a SemanticModel<'a>, extend_immutable_calls: Vec<CallPath<'a>>) -> Self {
|
fn new(semantic: &'a SemanticModel<'a>, extend_immutable_calls: Vec<CallPath<'a>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
semantic: model,
|
semantic,
|
||||||
extend_immutable_calls,
|
extend_immutable_calls,
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,6 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||||
parameters, body, ..
|
parameters, body, ..
|
||||||
})
|
|
||||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
|
||||||
parameters, body, ..
|
|
||||||
}) => {
|
}) => {
|
||||||
// Collect all loaded variable names.
|
// Collect all loaded variable names.
|
||||||
let mut visitor = LoadedNamesVisitor::default();
|
let mut visitor = LoadedNamesVisitor::default();
|
||||||
|
|
@ -236,7 +233,7 @@ struct AssignedNamesVisitor<'a> {
|
||||||
/// `Visitor` to collect all used identifiers in a statement.
|
/// `Visitor` to collect all used identifiers in a statement.
|
||||||
impl<'a> Visitor<'a> for AssignedNamesVisitor<'a> {
|
impl<'a> Visitor<'a> for AssignedNamesVisitor<'a> {
|
||||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||||
if matches!(stmt, Stmt::FunctionDef(_) | Stmt::AsyncFunctionDef(_)) {
|
if stmt.is_function_def_stmt() {
|
||||||
// Don't recurse.
|
// Don't recurse.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -251,8 +248,7 @@ impl<'a> Visitor<'a> for AssignedNamesVisitor<'a> {
|
||||||
}
|
}
|
||||||
Stmt::AugAssign(ast::StmtAugAssign { target, .. })
|
Stmt::AugAssign(ast::StmtAugAssign { target, .. })
|
||||||
| Stmt::AnnAssign(ast::StmtAnnAssign { target, .. })
|
| Stmt::AnnAssign(ast::StmtAnnAssign { target, .. })
|
||||||
| Stmt::For(ast::StmtFor { target, .. })
|
| Stmt::For(ast::StmtFor { target, .. }) => {
|
||||||
| Stmt::AsyncFor(ast::StmtAsyncFor { target, .. }) => {
|
|
||||||
let mut visitor = NamesFromAssignmentsVisitor::default();
|
let mut visitor = NamesFromAssignmentsVisitor::default();
|
||||||
visitor.visit_expr(target);
|
visitor.visit_expr(target);
|
||||||
self.names.extend(visitor.names);
|
self.names.extend(visitor.names);
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ pub(crate) fn getattr_with_constant(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Expr::Constant(ast::ExprConstant {
|
let Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Str(value),
|
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||||
..
|
..
|
||||||
}) = arg
|
}) = arg
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -69,16 +69,12 @@ fn walk_stmt(checker: &mut Checker, body: &[Stmt], f: fn(&Stmt) -> bool) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::While(ast::StmtWhile { body, .. })
|
Stmt::While(ast::StmtWhile { body, .. }) | Stmt::For(ast::StmtFor { body, .. }) => {
|
||||||
| Stmt::For(ast::StmtFor { body, .. })
|
|
||||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, .. }) => {
|
|
||||||
walk_stmt(checker, body, Stmt::is_return_stmt);
|
walk_stmt(checker, body, Stmt::is_return_stmt);
|
||||||
}
|
}
|
||||||
Stmt::If(ast::StmtIf { body, .. })
|
Stmt::If(ast::StmtIf { body, .. })
|
||||||
| Stmt::Try(ast::StmtTry { body, .. })
|
| Stmt::Try(ast::StmtTry { body, .. })
|
||||||
| Stmt::TryStar(ast::StmtTryStar { body, .. })
|
| Stmt::With(ast::StmtWith { body, .. }) => {
|
||||||
| Stmt::With(ast::StmtWith { body, .. })
|
|
||||||
| Stmt::AsyncWith(ast::StmtAsyncWith { body, .. }) => {
|
|
||||||
walk_stmt(checker, body, f);
|
walk_stmt(checker, body, f);
|
||||||
}
|
}
|
||||||
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
use ruff_python_ast::{ParameterWithDefault, Parameters, Ranged};
|
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||||
|
use ruff_python_ast::{self as ast, Expr, Parameter, ParameterWithDefault, Ranged};
|
||||||
|
use ruff_python_codegen::{Generator, Stylist};
|
||||||
|
use ruff_python_index::Indexer;
|
||||||
use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr};
|
use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr};
|
||||||
|
use ruff_python_trivia::{indentation_at_offset, textwrap};
|
||||||
|
use ruff_source_file::Locator;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::registry::AsRule;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for uses of mutable objects as function argument defaults.
|
/// Checks for uses of mutable objects as function argument defaults.
|
||||||
|
|
@ -50,24 +55,30 @@ use crate::checkers::ast::Checker;
|
||||||
pub struct MutableArgumentDefault;
|
pub struct MutableArgumentDefault;
|
||||||
|
|
||||||
impl Violation for MutableArgumentDefault {
|
impl Violation for MutableArgumentDefault {
|
||||||
|
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||||
|
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
format!("Do not use mutable data structures for argument defaults")
|
format!("Do not use mutable data structures for argument defaults")
|
||||||
}
|
}
|
||||||
|
fn autofix_title(&self) -> Option<String> {
|
||||||
|
Some(format!("Replace with `None`; initialize within function"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// B006
|
/// B006
|
||||||
pub(crate) fn mutable_argument_default(checker: &mut Checker, parameters: &Parameters) {
|
pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast::StmtFunctionDef) {
|
||||||
// Scan in reverse order to right-align zip().
|
// Scan in reverse order to right-align zip().
|
||||||
for ParameterWithDefault {
|
for ParameterWithDefault {
|
||||||
parameter,
|
parameter,
|
||||||
default,
|
default,
|
||||||
range: _,
|
range: _,
|
||||||
} in parameters
|
} in function_def
|
||||||
|
.parameters
|
||||||
.posonlyargs
|
.posonlyargs
|
||||||
.iter()
|
.iter()
|
||||||
.chain(¶meters.args)
|
.chain(&function_def.parameters.args)
|
||||||
.chain(¶meters.kwonlyargs)
|
.chain(&function_def.parameters.kwonlyargs)
|
||||||
{
|
{
|
||||||
let Some(default) = default else {
|
let Some(default) = default else {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -79,9 +90,84 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, parameters: &Param
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|expr| is_immutable_annotation(expr, checker.semantic()))
|
.is_some_and(|expr| is_immutable_annotation(expr, checker.semantic()))
|
||||||
{
|
{
|
||||||
checker
|
let mut diagnostic = Diagnostic::new(MutableArgumentDefault, default.range());
|
||||||
.diagnostics
|
|
||||||
.push(Diagnostic::new(MutableArgumentDefault, default.range()));
|
// If the function body is on the same line as the function def, do not fix
|
||||||
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
|
if let Some(fix) = move_initialization(
|
||||||
|
function_def,
|
||||||
|
parameter,
|
||||||
|
default,
|
||||||
|
checker.locator(),
|
||||||
|
checker.stylist(),
|
||||||
|
checker.indexer(),
|
||||||
|
checker.generator(),
|
||||||
|
) {
|
||||||
|
diagnostic.set_fix(fix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a [`Fix`] to move a mutable argument default initialization
|
||||||
|
/// into the function body.
|
||||||
|
fn move_initialization(
|
||||||
|
function_def: &ast::StmtFunctionDef,
|
||||||
|
parameter: &Parameter,
|
||||||
|
default: &Expr,
|
||||||
|
locator: &Locator,
|
||||||
|
stylist: &Stylist,
|
||||||
|
indexer: &Indexer,
|
||||||
|
generator: Generator,
|
||||||
|
) -> Option<Fix> {
|
||||||
|
let mut body = function_def.body.iter();
|
||||||
|
|
||||||
|
let statement = body.next()?;
|
||||||
|
if indexer.preceded_by_multi_statement_line(statement, locator) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the indentation depth of the function body.
|
||||||
|
let indentation = indentation_at_offset(statement.start(), locator)?;
|
||||||
|
|
||||||
|
// Set the default argument value to `None`.
|
||||||
|
let default_edit = Edit::range_replacement("None".to_string(), default.range());
|
||||||
|
|
||||||
|
// Add an `if`, to set the argument to its original value if still `None`.
|
||||||
|
let mut content = String::new();
|
||||||
|
content.push_str(&format!("if {} is None:", parameter.name.as_str()));
|
||||||
|
content.push_str(stylist.line_ending().as_str());
|
||||||
|
content.push_str(stylist.indentation());
|
||||||
|
content.push_str(&format!(
|
||||||
|
"{} = {}",
|
||||||
|
parameter.name.as_str(),
|
||||||
|
generator.expr(default)
|
||||||
|
));
|
||||||
|
content.push_str(stylist.line_ending().as_str());
|
||||||
|
|
||||||
|
// Indent the edit to match the body indentation.
|
||||||
|
let content = textwrap::indent(&content, indentation).to_string();
|
||||||
|
|
||||||
|
let initialization_edit = if is_docstring_stmt(statement) {
|
||||||
|
// If the first statement in the function is a docstring, insert _after_ it.
|
||||||
|
if let Some(statement) = body.next() {
|
||||||
|
// If there's a second statement, insert _before_ it, but ensure this isn't a
|
||||||
|
// multi-statement line.
|
||||||
|
if indexer.preceded_by_multi_statement_line(statement, locator) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Edit::insertion(content, locator.line_start(statement.start()))
|
||||||
|
} else {
|
||||||
|
// If the docstring is the only statement, insert _before_ it.
|
||||||
|
Edit::insertion(content, locator.full_line_end(statement.end()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, insert before the first statement.
|
||||||
|
let at = locator.line_start(statement.start());
|
||||||
|
Edit::insertion(content, at)
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Fix::manual_edits(default_edit, [initialization_edit]))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ pub(crate) fn setattr_with_constant(
|
||||||
if !is_identifier(name) {
|
if !is_identifier(name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if is_mangled_private(name.as_str()) {
|
if is_mangled_private(name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// We can only replace a `setattr` call (which is an `Expr`) with an assignment
|
// We can only replace a `setattr` call (which is an `Expr`) with an assignment
|
||||||
|
|
@ -97,7 +97,7 @@ pub(crate) fn setattr_with_constant(
|
||||||
if let Stmt::Expr(ast::StmtExpr {
|
if let Stmt::Expr(ast::StmtExpr {
|
||||||
value: child,
|
value: child,
|
||||||
range: _,
|
range: _,
|
||||||
}) = checker.semantic().stmt()
|
}) = checker.semantic().current_statement()
|
||||||
{
|
{
|
||||||
if expr == child.as_ref() {
|
if expr == child.as_ref() {
|
||||||
let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range());
|
let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range());
|
||||||
|
|
|
||||||
|
|
@ -68,13 +68,13 @@ pub(crate) fn unreliable_callable_check(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Expr::Constant(ast::ExprConstant {
|
let Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Str(string),
|
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||||
..
|
..
|
||||||
}) = attr
|
}) = attr
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if string != "__call__" {
|
if value != "__call__" {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ pub(crate) fn unused_loop_control_variable(checker: &mut Checker, target: &Expr,
|
||||||
if certainty.into() {
|
if certainty.into() {
|
||||||
// Avoid fixing if the variable, or any future bindings to the variable, are
|
// Avoid fixing if the variable, or any future bindings to the variable, are
|
||||||
// used _after_ the loop.
|
// used _after_ the loop.
|
||||||
let scope = checker.semantic().scope();
|
let scope = checker.semantic().current_scope();
|
||||||
if scope
|
if scope
|
||||||
.get_all(name)
|
.get_all(name)
|
||||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ pub(crate) fn useless_expression(checker: &mut Checker, value: &Expr) {
|
||||||
// Ignore strings, to avoid false positives with docstrings.
|
// Ignore strings, to avoid false positives with docstrings.
|
||||||
if matches!(
|
if matches!(
|
||||||
value,
|
value,
|
||||||
Expr::JoinedStr(_)
|
Expr::FString(_)
|
||||||
| Expr::Constant(ast::ExprConstant {
|
| Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Str(..) | Constant::Ellipsis,
|
value: Constant::Str(..) | Constant::Ellipsis,
|
||||||
..
|
..
|
||||||
|
|
|
||||||
|
|
@ -1,123 +1,479 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||||
---
|
---
|
||||||
B006_B008.py:63:25: B006 Do not use mutable data structures for argument defaults
|
B006_B008.py:63:25: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
63 | def this_is_wrong(value=[1, 2, 3]):
|
63 | def this_is_wrong(value=[1, 2, 3]):
|
||||||
| ^^^^^^^^^ B006
|
| ^^^^^^^^^ B006
|
||||||
64 | ...
|
64 | ...
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:67:30: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
60 60 | # Flag mutable literals/comprehensions
|
||||||
|
61 61 |
|
||||||
|
62 62 |
|
||||||
|
63 |-def this_is_wrong(value=[1, 2, 3]):
|
||||||
|
63 |+def this_is_wrong(value=None):
|
||||||
|
64 |+ if value is None:
|
||||||
|
65 |+ value = [1, 2, 3]
|
||||||
|
64 66 | ...
|
||||||
|
65 67 |
|
||||||
|
66 68 |
|
||||||
|
|
||||||
|
B006_B008.py:67:30: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
67 | def this_is_also_wrong(value={}):
|
67 | def this_is_also_wrong(value={}):
|
||||||
| ^^ B006
|
| ^^ B006
|
||||||
68 | ...
|
68 | ...
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:71:20: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
|
64 64 | ...
|
||||||
71 | def and_this(value=set()):
|
65 65 |
|
||||||
| ^^^^^ B006
|
66 66 |
|
||||||
72 | ...
|
67 |-def this_is_also_wrong(value={}):
|
||||||
|
|
67 |+def this_is_also_wrong(value=None):
|
||||||
|
68 |+ if value is None:
|
||||||
|
69 |+ value = {}
|
||||||
|
68 70 | ...
|
||||||
|
69 71 |
|
||||||
|
70 72 |
|
||||||
|
|
||||||
B006_B008.py:75:20: B006 Do not use mutable data structures for argument defaults
|
B006_B008.py:73:52: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
75 | def this_too(value=collections.OrderedDict()):
|
71 | class Foo:
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
72 | @staticmethod
|
||||||
76 | ...
|
73 | def this_is_also_wrong_and_more_indented(value={}):
|
||||||
|
| ^^ B006
|
||||||
|
74 | pass
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:79:32: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
70 70 |
|
||||||
|
71 71 | class Foo:
|
||||||
|
72 72 | @staticmethod
|
||||||
|
73 |- def this_is_also_wrong_and_more_indented(value={}):
|
||||||
|
73 |+ def this_is_also_wrong_and_more_indented(value=None):
|
||||||
|
74 |+ if value is None:
|
||||||
|
75 |+ value = {}
|
||||||
|
74 76 | pass
|
||||||
|
75 77 |
|
||||||
|
76 78 |
|
||||||
|
|
||||||
|
B006_B008.py:77:31: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
79 | async def async_this_too(value=collections.defaultdict()):
|
77 | def multiline_arg_wrong(value={
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
| _______________________________^
|
||||||
|
78 | |
|
||||||
|
79 | | }):
|
||||||
|
| |_^ B006
|
||||||
80 | ...
|
80 | ...
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:83:26: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
74 74 | pass
|
||||||
|
75 75 |
|
||||||
|
76 76 |
|
||||||
|
77 |-def multiline_arg_wrong(value={
|
||||||
|
78 |-
|
||||||
|
79 |-}):
|
||||||
|
77 |+def multiline_arg_wrong(value=None):
|
||||||
|
78 |+ if value is None:
|
||||||
|
79 |+ value = {}
|
||||||
|
80 80 | ...
|
||||||
|
81 81 |
|
||||||
|
82 82 | def single_line_func_wrong(value = {}): ...
|
||||||
|
|
||||||
|
B006_B008.py:82:36: B006 Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
83 | def dont_forget_me(value=collections.deque()):
|
80 | ...
|
||||||
|
81 |
|
||||||
|
82 | def single_line_func_wrong(value = {}): ...
|
||||||
|
| ^^ B006
|
||||||
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
B006_B008.py:85:20: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
||||||
|
85 | def and_this(value=set()):
|
||||||
|
| ^^^^^ B006
|
||||||
|
86 | ...
|
||||||
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
ℹ Possible fix
|
||||||
|
82 82 | def single_line_func_wrong(value = {}): ...
|
||||||
|
83 83 |
|
||||||
|
84 84 |
|
||||||
|
85 |-def and_this(value=set()):
|
||||||
|
85 |+def and_this(value=None):
|
||||||
|
86 |+ if value is None:
|
||||||
|
87 |+ value = set()
|
||||||
|
86 88 | ...
|
||||||
|
87 89 |
|
||||||
|
88 90 |
|
||||||
|
|
||||||
|
B006_B008.py:89:20: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
||||||
|
89 | def this_too(value=collections.OrderedDict()):
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||||
|
90 | ...
|
||||||
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
ℹ Possible fix
|
||||||
|
86 86 | ...
|
||||||
|
87 87 |
|
||||||
|
88 88 |
|
||||||
|
89 |-def this_too(value=collections.OrderedDict()):
|
||||||
|
89 |+def this_too(value=None):
|
||||||
|
90 |+ if value is None:
|
||||||
|
91 |+ value = collections.OrderedDict()
|
||||||
|
90 92 | ...
|
||||||
|
91 93 |
|
||||||
|
92 94 |
|
||||||
|
|
||||||
|
B006_B008.py:93:32: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
||||||
|
93 | async def async_this_too(value=collections.defaultdict()):
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||||
|
94 | ...
|
||||||
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
ℹ Possible fix
|
||||||
|
90 90 | ...
|
||||||
|
91 91 |
|
||||||
|
92 92 |
|
||||||
|
93 |-async def async_this_too(value=collections.defaultdict()):
|
||||||
|
93 |+async def async_this_too(value=None):
|
||||||
|
94 |+ if value is None:
|
||||||
|
95 |+ value = collections.defaultdict()
|
||||||
|
94 96 | ...
|
||||||
|
95 97 |
|
||||||
|
96 98 |
|
||||||
|
|
||||||
|
B006_B008.py:97:26: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
||||||
|
97 | def dont_forget_me(value=collections.deque()):
|
||||||
| ^^^^^^^^^^^^^^^^^^^ B006
|
| ^^^^^^^^^^^^^^^^^^^ B006
|
||||||
84 | ...
|
98 | ...
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:88:46: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
94 94 | ...
|
||||||
|
95 95 |
|
||||||
|
96 96 |
|
||||||
|
97 |-def dont_forget_me(value=collections.deque()):
|
||||||
|
97 |+def dont_forget_me(value=None):
|
||||||
|
98 |+ if value is None:
|
||||||
|
99 |+ value = collections.deque()
|
||||||
|
98 100 | ...
|
||||||
|
99 101 |
|
||||||
|
100 102 |
|
||||||
|
|
||||||
|
B006_B008.py:102:46: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
87 | # N.B. we're also flagging the function call in the comprehension
|
101 | # N.B. we're also flagging the function call in the comprehension
|
||||||
88 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
102 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||||
89 | pass
|
103 | pass
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:92:46: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
99 99 |
|
||||||
|
100 100 |
|
||||||
|
101 101 | # N.B. we're also flagging the function call in the comprehension
|
||||||
|
102 |-def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
||||||
|
102 |+def list_comprehension_also_not_okay(default=None):
|
||||||
|
103 |+ if default is None:
|
||||||
|
104 |+ default = [i ** 2 for i in range(3)]
|
||||||
|
103 105 | pass
|
||||||
|
104 106 |
|
||||||
|
105 107 |
|
||||||
|
|
||||||
|
B006_B008.py:106:46: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
92 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
106 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||||
93 | pass
|
107 | pass
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:96:45: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
103 103 | pass
|
||||||
|
104 104 |
|
||||||
|
105 105 |
|
||||||
|
106 |-def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
||||||
|
106 |+def dict_comprehension_also_not_okay(default=None):
|
||||||
|
107 |+ if default is None:
|
||||||
|
108 |+ default = {i: i ** 2 for i in range(3)}
|
||||||
|
107 109 | pass
|
||||||
|
108 110 |
|
||||||
|
109 111 |
|
||||||
|
|
||||||
|
B006_B008.py:110:45: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
96 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
110 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||||
97 | pass
|
111 | pass
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:100:33: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
107 107 | pass
|
||||||
|
108 108 |
|
||||||
|
109 109 |
|
||||||
|
110 |-def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
||||||
|
110 |+def set_comprehension_also_not_okay(default=None):
|
||||||
|
111 |+ if default is None:
|
||||||
|
112 |+ default = {i ** 2 for i in range(3)}
|
||||||
|
111 113 | pass
|
||||||
|
112 114 |
|
||||||
|
113 115 |
|
||||||
|
|
||||||
|
B006_B008.py:114:33: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
100 | def kwonlyargs_mutable(*, value=[]):
|
114 | def kwonlyargs_mutable(*, value=[]):
|
||||||
| ^^ B006
|
| ^^ B006
|
||||||
101 | ...
|
115 | ...
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:221:20: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
111 111 | pass
|
||||||
|
112 112 |
|
||||||
|
113 113 |
|
||||||
|
114 |-def kwonlyargs_mutable(*, value=[]):
|
||||||
|
114 |+def kwonlyargs_mutable(*, value=None):
|
||||||
|
115 |+ if value is None:
|
||||||
|
116 |+ value = []
|
||||||
|
115 117 | ...
|
||||||
|
116 118 |
|
||||||
|
117 119 |
|
||||||
|
|
||||||
|
B006_B008.py:235:20: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
219 | # B006 and B008
|
233 | # B006 and B008
|
||||||
220 | # We should handle arbitrary nesting of these B008.
|
234 | # We should handle arbitrary nesting of these B008.
|
||||||
221 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
235 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||||
222 | pass
|
236 | pass
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:258:27: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
232 232 |
|
||||||
|
233 233 | # B006 and B008
|
||||||
|
234 234 | # We should handle arbitrary nesting of these B008.
|
||||||
|
235 |-def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||||
|
235 |+def nested_combo(a=None):
|
||||||
|
236 |+ if a is None:
|
||||||
|
237 |+ a = [float(3), dt.datetime.now()]
|
||||||
|
236 238 | pass
|
||||||
|
237 239 |
|
||||||
|
238 240 |
|
||||||
|
|
||||||
|
B006_B008.py:272:27: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
257 | def mutable_annotations(
|
271 | def mutable_annotations(
|
||||||
258 | a: list[int] | None = [],
|
272 | a: list[int] | None = [],
|
||||||
| ^^ B006
|
| ^^ B006
|
||||||
259 | b: Optional[Dict[int, int]] = {},
|
273 | b: Optional[Dict[int, int]] = {},
|
||||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:259:35: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
269 269 |
|
||||||
|
270 270 |
|
||||||
|
271 271 | def mutable_annotations(
|
||||||
|
272 |- a: list[int] | None = [],
|
||||||
|
272 |+ a: list[int] | None = None,
|
||||||
|
273 273 | b: Optional[Dict[int, int]] = {},
|
||||||
|
274 274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
|
275 275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
|
276 276 | ):
|
||||||
|
277 |+ if a is None:
|
||||||
|
278 |+ a = []
|
||||||
|
277 279 | pass
|
||||||
|
278 280 |
|
||||||
|
279 281 |
|
||||||
|
|
||||||
|
B006_B008.py:273:35: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
257 | def mutable_annotations(
|
271 | def mutable_annotations(
|
||||||
258 | a: list[int] | None = [],
|
272 | a: list[int] | None = [],
|
||||||
259 | b: Optional[Dict[int, int]] = {},
|
273 | b: Optional[Dict[int, int]] = {},
|
||||||
| ^^ B006
|
| ^^ B006
|
||||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:260:62: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
270 270 |
|
||||||
|
271 271 | def mutable_annotations(
|
||||||
|
272 272 | a: list[int] | None = [],
|
||||||
|
273 |- b: Optional[Dict[int, int]] = {},
|
||||||
|
273 |+ b: Optional[Dict[int, int]] = None,
|
||||||
|
274 274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
|
275 275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
|
276 276 | ):
|
||||||
|
277 |+ if b is None:
|
||||||
|
278 |+ b = {}
|
||||||
|
277 279 | pass
|
||||||
|
278 280 |
|
||||||
|
279 281 |
|
||||||
|
|
||||||
|
B006_B008.py:274:62: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
258 | a: list[int] | None = [],
|
272 | a: list[int] | None = [],
|
||||||
259 | b: Optional[Dict[int, int]] = {},
|
273 | b: Optional[Dict[int, int]] = {},
|
||||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
| ^^^^^ B006
|
| ^^^^^ B006
|
||||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
262 | ):
|
276 | ):
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
B006_B008.py:261:80: B006 Do not use mutable data structures for argument defaults
|
ℹ Possible fix
|
||||||
|
271 271 | def mutable_annotations(
|
||||||
|
272 272 | a: list[int] | None = [],
|
||||||
|
273 273 | b: Optional[Dict[int, int]] = {},
|
||||||
|
274 |- c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
|
274 |+ c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||||
|
275 275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
|
276 276 | ):
|
||||||
|
277 |+ if c is None:
|
||||||
|
278 |+ c = set()
|
||||||
|
277 279 | pass
|
||||||
|
278 280 |
|
||||||
|
279 281 |
|
||||||
|
|
||||||
|
B006_B008.py:275:80: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
|
||||||
259 | b: Optional[Dict[int, int]] = {},
|
273 | b: Optional[Dict[int, int]] = {},
|
||||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
| ^^^^^ B006
|
| ^^^^^ B006
|
||||||
262 | ):
|
276 | ):
|
||||||
263 | pass
|
277 | pass
|
||||||
|
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
ℹ Possible fix
|
||||||
|
272 272 | a: list[int] | None = [],
|
||||||
|
273 273 | b: Optional[Dict[int, int]] = {},
|
||||||
|
274 274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
|
275 |- d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||||
|
275 |+ d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||||
|
276 276 | ):
|
||||||
|
277 |+ if d is None:
|
||||||
|
278 |+ d = set()
|
||||||
|
277 279 | pass
|
||||||
|
278 280 |
|
||||||
|
279 281 |
|
||||||
|
|
||||||
|
B006_B008.py:280:52: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
||||||
|
280 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||||
|
| ^^ B006
|
||||||
|
281 | """Docstring"""
|
||||||
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
ℹ Possible fix
|
||||||
|
277 277 | pass
|
||||||
|
278 278 |
|
||||||
|
279 279 |
|
||||||
|
280 |-def single_line_func_wrong(value: dict[str, str] = {}):
|
||||||
|
280 |+def single_line_func_wrong(value: dict[str, str] = None):
|
||||||
|
281 281 | """Docstring"""
|
||||||
|
282 |+ if value is None:
|
||||||
|
283 |+ value = {}
|
||||||
|
282 284 |
|
||||||
|
283 285 |
|
||||||
|
284 286 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||||
|
|
||||||
|
B006_B008.py:284:52: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
||||||
|
284 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||||
|
| ^^ B006
|
||||||
|
285 | """Docstring"""
|
||||||
|
286 | ...
|
||||||
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
ℹ Possible fix
|
||||||
|
281 281 | """Docstring"""
|
||||||
|
282 282 |
|
||||||
|
283 283 |
|
||||||
|
284 |-def single_line_func_wrong(value: dict[str, str] = {}):
|
||||||
|
284 |+def single_line_func_wrong(value: dict[str, str] = None):
|
||||||
|
285 285 | """Docstring"""
|
||||||
|
286 |+ if value is None:
|
||||||
|
287 |+ value = {}
|
||||||
|
286 288 | ...
|
||||||
|
287 289 |
|
||||||
|
288 290 |
|
||||||
|
|
||||||
|
B006_B008.py:289:52: B006 Do not use mutable data structures for argument defaults
|
||||||
|
|
|
||||||
|
289 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||||
|
| ^^ B006
|
||||||
|
290 | """Docstring"""; ...
|
||||||
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
B006_B008.py:293:52: B006 Do not use mutable data structures for argument defaults
|
||||||
|
|
|
||||||
|
293 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||||
|
| ^^ B006
|
||||||
|
294 | """Docstring"""; \
|
||||||
|
295 | ...
|
||||||
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
B006_B008.py:298:52: B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
|
|
||||||
|
298 | def single_line_func_wrong(value: dict[str, str] = {
|
||||||
|
| ____________________________________________________^
|
||||||
|
299 | | # This is a comment
|
||||||
|
300 | | }):
|
||||||
|
| |_^ B006
|
||||||
|
301 | """Docstring"""
|
||||||
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
ℹ Possible fix
|
||||||
|
295 295 | ...
|
||||||
|
296 296 |
|
||||||
|
297 297 |
|
||||||
|
298 |-def single_line_func_wrong(value: dict[str, str] = {
|
||||||
|
299 |- # This is a comment
|
||||||
|
300 |-}):
|
||||||
|
298 |+def single_line_func_wrong(value: dict[str, str] = None):
|
||||||
|
301 299 | """Docstring"""
|
||||||
|
300 |+ if value is None:
|
||||||
|
301 |+ value = {}
|
||||||
|
302 302 |
|
||||||
|
303 303 |
|
||||||
|
304 304 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||||
|
|
||||||
|
B006_B008.py:304:52: B006 Do not use mutable data structures for argument defaults
|
||||||
|
|
|
||||||
|
304 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||||
|
| ^^ B006
|
||||||
|
305 | : \
|
||||||
|
306 | """Docstring"""
|
||||||
|
|
|
||||||
|
= help: Replace with `None`; initialize within function
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue