Compare commits
1 Commits
v0.0.285
...
charlie/de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
965243d98e |
14
.github/workflows/ci.yaml
vendored
14
.github/workflows/ci.yaml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
- "!crates/ruff_formatter/**"
|
||||
- "!crates/ruff_dev/**"
|
||||
- "!crates/ruff_shrinking/**"
|
||||
- scripts/*
|
||||
- scripts/check_ecosystem.py
|
||||
|
||||
formatter:
|
||||
- Cargo.toml
|
||||
@@ -56,7 +56,6 @@ jobs:
|
||||
- crates/ruff_text_size/**
|
||||
- crates/ruff_python_parser/**
|
||||
- crates/ruff_dev/**
|
||||
- scripts/*
|
||||
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -328,7 +327,7 @@ jobs:
|
||||
name: "Formatter ecosystem and progress checks"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
|
||||
if: needs.determine_changes.outputs.formatter == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -338,6 +337,9 @@ jobs:
|
||||
- name: "Formatter progress"
|
||||
run: scripts/formatter_ecosystem_checks.sh
|
||||
- name: "Github step summary"
|
||||
run: cat target/progress_projects_stats.txt > $GITHUB_STEP_SUMMARY
|
||||
- name: "Remove checkouts from cache"
|
||||
run: rm -r target/progress_projects
|
||||
run: grep "similarity index" target/progress_projects_log.txt | sort > $GITHUB_STEP_SUMMARY
|
||||
# CPython is not black formatted, so we run only the stability check
|
||||
- name: "Clone CPython 3.10"
|
||||
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
|
||||
|
||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.0.2
|
||||
uses: cloudflare/wrangler-action@2.0.0
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
2
.github/workflows/playground.yaml
vendored
2
.github/workflows/playground.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
working-directory: playground
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.0.2
|
||||
uses: cloudflare/wrangler-action@2.0.0
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
# 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
|
||||
|
||||
### `.ipynb_checkpoints`, `.pyenv`, `.pytest_cache`, and `.vscode` are now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))
|
||||
|
||||
@@ -131,6 +131,7 @@ At time of writing, the repository includes the following crates:
|
||||
- `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_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
|
||||
intermediate representation for each node, which `ruff_formatter` prints based on the configured
|
||||
line length.
|
||||
@@ -571,7 +572,7 @@ An alternative is to convert the perf data to `flamegraph.svg` using
|
||||
[flamegraph](https://github.com/flamegraph-rs/flamegraph) (`cargo install flamegraph`):
|
||||
|
||||
```shell
|
||||
flamegraph --perfdata perf.data --no-inline
|
||||
flamegraph --perfdata perf.data
|
||||
```
|
||||
|
||||
#### Mac
|
||||
|
||||
33
Cargo.lock
generated
33
Cargo.lock
generated
@@ -14,18 +14,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
@@ -812,7 +800,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.285"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1003,16 +991,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imara-diff"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imperative"
|
||||
version = "1.0.4"
|
||||
@@ -2064,7 +2042,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.285"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2141,7 +2119,6 @@ dependencies = [
|
||||
"ruff",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_index",
|
||||
"ruff_python_parser",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2164,7 +2141,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.285"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2220,7 +2197,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"ignore",
|
||||
"imara-diff",
|
||||
"indicatif",
|
||||
"indoc",
|
||||
"itertools",
|
||||
@@ -2354,7 +2330,6 @@ dependencies = [
|
||||
"similar",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2378,6 +2353,7 @@ dependencies = [
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"lexical-parse-float",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"unic-ucd-category",
|
||||
@@ -2424,7 +2400,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
"ruff_index",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
|
||||
@@ -49,7 +49,6 @@ toml = { version = "0.7.2" }
|
||||
tracing = "0.1.37"
|
||||
tracing-indicatif = "0.3.4"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
unicode-width = "0.1.10"
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
# 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
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 📏 Over [600 built-in rules](https://beta.ruff.rs/docs/rules/)
|
||||
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
|
||||
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
|
||||
built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
||||
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.285
|
||||
rev: v0.0.282
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -211,8 +211,8 @@ line-length = 88
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
# Assume Python 3.8
|
||||
target-version = "py38"
|
||||
# Assume Python 3.10.
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.mccabe]
|
||||
# Unlike Flake8, default to a complexity level of 10.
|
||||
@@ -233,7 +233,7 @@ linting command.
|
||||
|
||||
<!-- Begin section: Rules -->
|
||||
|
||||
**Ruff supports over 600 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
**Ruff supports over 500 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
|
||||
Rust as a first-party feature.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.285"
|
||||
version = "0.0.282"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.285"
|
||||
version = "0.0.282"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -62,6 +62,8 @@ quick-junit = { version = "0.3.2" }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
|
||||
schemars = { workspace = true, optional = true }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { workspace = true }
|
||||
@@ -75,7 +77,7 @@ strum_macros = { workspace = true }
|
||||
thiserror = { version = "1.0.43" }
|
||||
toml = { workspace = true }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unicode-width = { workspace = true }
|
||||
unicode-width = { version = "0.1.10" }
|
||||
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
|
||||
@@ -14,19 +14,3 @@ with open("/dev/shm/unit/test", "w") as f:
|
||||
# not ok by config
|
||||
with open("/foo/bar", "w") as f:
|
||||
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,20 +68,6 @@ def this_is_also_wrong(value={}):
|
||||
...
|
||||
|
||||
|
||||
class Foo:
|
||||
@staticmethod
|
||||
def this_is_also_wrong_and_more_indented(value={}):
|
||||
pass
|
||||
|
||||
|
||||
def multiline_arg_wrong(value={
|
||||
|
||||
}):
|
||||
...
|
||||
|
||||
def single_line_func_wrong(value = {}): ...
|
||||
|
||||
|
||||
def and_this(value=set()):
|
||||
...
|
||||
|
||||
@@ -275,32 +261,3 @@ def mutable_annotations(
|
||||
d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
"""Docstring"""
|
||||
|
||||
|
||||
def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
"""Docstring"""
|
||||
...
|
||||
|
||||
|
||||
def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
"""Docstring"""; ...
|
||||
|
||||
|
||||
def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
"""Docstring"""; \
|
||||
...
|
||||
|
||||
|
||||
def single_line_func_wrong(value: dict[str, str] = {
|
||||
# This is a comment
|
||||
}):
|
||||
"""Docstring"""
|
||||
|
||||
|
||||
def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
: \
|
||||
"""Docstring"""
|
||||
|
||||
@@ -74,10 +74,3 @@ try:
|
||||
except (ValueError, binascii.Error):
|
||||
# binascii.Error is a subclass of ValueError.
|
||||
pass
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/6412
|
||||
try:
|
||||
pass
|
||||
except (ValueError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import typing
|
||||
|
||||
# Shouldn't affect non-union field types.
|
||||
field1: str
|
||||
|
||||
# Should emit for duplicate field types.
|
||||
field2: str | str # PYI016: Duplicate union member `str`
|
||||
|
||||
|
||||
# Should emit for union types in arguments.
|
||||
def func1(arg1: int | int): # PYI016: Duplicate union member `int`
|
||||
print(arg1)
|
||||
|
||||
|
||||
# Should emit for unions in return types.
|
||||
def func2() -> str | str: # PYI016: Duplicate union member `str`
|
||||
return "my string"
|
||||
|
||||
|
||||
# Should emit in longer unions, even if not directly adjacent.
|
||||
field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
@@ -32,55 +33,3 @@ field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
|
||||
# Should emit for nested unions.
|
||||
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,13 +74,3 @@ 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`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import builtins
|
||||
from typing import Union
|
||||
|
||||
|
||||
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
x: type[int] | type[str] | type[float]
|
||||
y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
@@ -8,9 +9,7 @@ z: Union[type[float], 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
|
||||
x: type[int, str, float]
|
||||
@@ -18,14 +17,4 @@ y: builtins.type[int, str, complex]
|
||||
z: Union[float, complex]
|
||||
|
||||
|
||||
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
|
||||
def func(arg: type[int, float] | str) -> None: ...
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import builtins
|
||||
from typing import Union
|
||||
|
||||
|
||||
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
x: type[int] | type[str] | type[float]
|
||||
y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
z: Union[type[float], type[complex]]
|
||||
z: Union[type[float, int], type[complex]]
|
||||
|
||||
|
||||
def func(arg: type[int] | str | type[float]) -> None: ...
|
||||
|
||||
# OK
|
||||
@@ -14,11 +16,5 @@ x: type[int, str, float]
|
||||
y: builtins.type[int, str, complex]
|
||||
z: Union[float, complex]
|
||||
|
||||
|
||||
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,15 +80,3 @@ class Test(unittest.TestCase):
|
||||
|
||||
def test_assert_not_regexp_matches(self):
|
||||
self.assertNotRegex("abc", r"abc") # Error
|
||||
|
||||
def test_fail_if(self):
|
||||
self.failIf("abc") # Error
|
||||
|
||||
def test_fail_unless(self):
|
||||
self.failUnless("abc") # Error
|
||||
|
||||
def test_fail_unless_equal(self):
|
||||
self.failUnlessEqual(1, 2) # Error
|
||||
|
||||
def test_fail_if_equal(self):
|
||||
self.failIfEqual(1, 2) # Error
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from pickle import PicklingError, UnpicklingError
|
||||
import socket
|
||||
|
||||
import pytest
|
||||
@@ -21,12 +20,6 @@ def test_error_no_argument_given():
|
||||
with pytest.raises(socket.error):
|
||||
raise ValueError("Can't divide 1 by 0")
|
||||
|
||||
with pytest.raises(PicklingError):
|
||||
raise PicklingError("Can't pickle")
|
||||
|
||||
with pytest.raises(UnpicklingError):
|
||||
raise UnpicklingError("Can't unpickle")
|
||||
|
||||
|
||||
def test_error_match_is_empty():
|
||||
with pytest.raises(ValueError, match=None):
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [1, 1, 2])
|
||||
def test_error_literal(x):
|
||||
...
|
||||
|
||||
|
||||
a = 1
|
||||
b = 2
|
||||
c = 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [a, a, b, b, b, c])
|
||||
def test_error_expr_simple(x):
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [(a, b), (a, b), (b, c)])
|
||||
def test_error_expr_complex(x):
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [1, 2])
|
||||
def test_ok(x):
|
||||
...
|
||||
@@ -1,48 +0,0 @@
|
||||
import unittest
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
def test_errors(self):
|
||||
with self.assertRaises(ValueError):
|
||||
raise ValueError
|
||||
with self.assertRaises(expected_exception=ValueError):
|
||||
raise ValueError
|
||||
|
||||
with self.failUnlessRaises(ValueError):
|
||||
raise ValueError
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "test"):
|
||||
raise ValueError("test")
|
||||
|
||||
with self.assertRaisesRegex(ValueError, expected_regex="test"):
|
||||
raise ValueError("test")
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
expected_exception=ValueError, expected_regex="test"
|
||||
):
|
||||
raise ValueError("test")
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
expected_regex="test", expected_exception=ValueError
|
||||
):
|
||||
raise ValueError("test")
|
||||
|
||||
with self.assertRaisesRegexp(ValueError, "test"):
|
||||
raise ValueError("test")
|
||||
|
||||
def test_unfixable_errors(self):
|
||||
with self.assertRaises(ValueError, msg="msg"):
|
||||
raise ValueError
|
||||
|
||||
with self.assertRaises(
|
||||
# comment
|
||||
ValueError
|
||||
):
|
||||
raise ValueError
|
||||
|
||||
with (
|
||||
self
|
||||
# comment
|
||||
.assertRaises(ValueError)
|
||||
):
|
||||
raise ValueError
|
||||
@@ -1,12 +0,0 @@
|
||||
import unittest
|
||||
import pytest
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
def test_pytest_raises(self):
|
||||
with pytest.raises(ValueError):
|
||||
raise ValueError
|
||||
|
||||
def test_errors(self):
|
||||
with self.assertRaises(ValueError):
|
||||
raise ValueError
|
||||
@@ -73,7 +73,3 @@ print(foo.__dict__)
|
||||
print(foo.__str__())
|
||||
print(foo().__class__)
|
||||
print(foo._asdict())
|
||||
|
||||
import os
|
||||
|
||||
os._exit()
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
## Banned modules ##
|
||||
import torch
|
||||
|
||||
from torch import *
|
||||
|
||||
from tensorflow import a, b, c
|
||||
|
||||
import torch as torch_wearing_a_trenchcoat
|
||||
|
||||
# this should count as module level
|
||||
x = 1; import tensorflow
|
||||
|
||||
# banning a module also bans any submodules
|
||||
import torch.foo.bar
|
||||
|
||||
from tensorflow.foo import bar
|
||||
|
||||
from torch.foo.bar import *
|
||||
|
||||
# unlike TID251, inline imports are *not* banned
|
||||
def my_cool_function():
|
||||
import tensorflow.foo.bar
|
||||
|
||||
def another_cool_function():
|
||||
from torch.foo import bar
|
||||
|
||||
def import_alias():
|
||||
from torch.foo import bar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import torch
|
||||
@@ -1,12 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
|
||||
|
||||
class Birthday(DeclarativeBase):
|
||||
|
||||
__tablename__ = "birthday"
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
day: Mapped[date]
|
||||
@@ -202,14 +202,3 @@ class C:
|
||||
###
|
||||
def f(x: None) -> None:
|
||||
_ = cast(Any, _identity)(x=x)
|
||||
|
||||
###
|
||||
# Unused arguments with `locals`.
|
||||
###
|
||||
def f(bar: str):
|
||||
print(locals())
|
||||
|
||||
|
||||
class C:
|
||||
def __init__(self, x) -> None:
|
||||
print(locals())
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# A copyright notice could go here
|
||||
|
||||
# A linter directive could go here
|
||||
|
||||
x = 1
|
||||
@@ -21,29 +21,3 @@ while i < 10:
|
||||
print("error")
|
||||
|
||||
i += 1
|
||||
|
||||
# OK - no other way to write this
|
||||
for i in range(10):
|
||||
try:
|
||||
print(f"{i}")
|
||||
break
|
||||
except:
|
||||
print("error")
|
||||
|
||||
# OK - no other way to write this
|
||||
for i in range(10):
|
||||
try:
|
||||
print(f"{i}")
|
||||
continue
|
||||
except:
|
||||
print("error")
|
||||
|
||||
|
||||
# OK - no other way to write this
|
||||
for i in range(10):
|
||||
try:
|
||||
print(f"{i}")
|
||||
if i > 0:
|
||||
break
|
||||
except:
|
||||
print("error")
|
||||
|
||||
@@ -30,10 +30,3 @@ def foo() -> None:
|
||||
|
||||
if __name__ == "__main__":
|
||||
import g
|
||||
|
||||
import h; import i
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import j; \
|
||||
import k
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
# aaaa
|
||||
# aaaaa
|
||||
# a
|
||||
# a
|
||||
# aa
|
||||
# aaa
|
||||
# aaaa
|
||||
# a
|
||||
# aa
|
||||
# aaa
|
||||
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"""
|
||||
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"""
|
||||
|
||||
if True: # noqa: E501
|
||||
[12]
|
||||
[12 ]
|
||||
[1,2]
|
||||
[1, 2]
|
||||
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"""
|
||||
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"""
|
||||
|
||||
c = """2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
c = """2ℝ4A67ß9💣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"""
|
||||
d = """💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
|
||||
@@ -25,12 +25,6 @@ if (True) == TrueElement or x == TrueElement:
|
||||
if res == True != False:
|
||||
pass
|
||||
|
||||
if(True) == TrueElement or x == TrueElement:
|
||||
pass
|
||||
|
||||
if (yield i) == True:
|
||||
print("even")
|
||||
|
||||
#: Okay
|
||||
if x not in y:
|
||||
pass
|
||||
|
||||
@@ -61,30 +61,3 @@ if x == types.X:
|
||||
|
||||
#: E721
|
||||
assert type(res) is int
|
||||
|
||||
|
||||
class Foo:
|
||||
def asdf(self, value: str | None):
|
||||
#: E721
|
||||
if type(value) is str:
|
||||
...
|
||||
|
||||
|
||||
class Foo:
|
||||
def type(self):
|
||||
pass
|
||||
|
||||
def asdf(self, value: str | None):
|
||||
#: E721
|
||||
if type(value) is str:
|
||||
...
|
||||
|
||||
|
||||
class Foo:
|
||||
def asdf(self, value: str | None):
|
||||
def type():
|
||||
pass
|
||||
|
||||
# Okay
|
||||
if type(value) is str:
|
||||
...
|
||||
|
||||
@@ -133,8 +133,3 @@ def scope():
|
||||
from collections.abc import Callable
|
||||
|
||||
f: Callable[[str, int, list[str]], list[str]] = lambda a, b, /, c: [*c, a * b]
|
||||
|
||||
|
||||
class TemperatureScales(Enum):
|
||||
CELSIUS = (lambda deg_c: deg_c)
|
||||
FAHRENHEIT = (lambda deg_c: deg_c * 9 / 5 + 32)
|
||||
|
||||
@@ -92,10 +92,3 @@ match *0, 1, *2:
|
||||
case 0,:
|
||||
import x
|
||||
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,13 +70,3 @@ import requests_mock as rm
|
||||
|
||||
def requests_mock(requests_mock: rm.Mocker):
|
||||
print(rm.ANY)
|
||||
|
||||
|
||||
import sklearn.base
|
||||
import mlflow.sklearn
|
||||
|
||||
|
||||
def f():
|
||||
import sklearn
|
||||
|
||||
mlflow
|
||||
|
||||
@@ -145,9 +145,3 @@ def f() -> None:
|
||||
obj = Foo()
|
||||
obj.do_thing()
|
||||
|
||||
|
||||
def f():
|
||||
try:
|
||||
pass
|
||||
except Exception as _:
|
||||
pass
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
class Apples:
|
||||
def _init_(self): # [bad-dunder-name]
|
||||
pass
|
||||
|
||||
def __hello__(self): # [bad-dunder-name]
|
||||
print("hello")
|
||||
|
||||
def __init_(self): # [bad-dunder-name]
|
||||
# author likely unintentionally misspelled the correct init dunder.
|
||||
pass
|
||||
|
||||
def _init_(self): # [bad-dunder-name]
|
||||
# author likely unintentionally misspelled the correct init dunder.
|
||||
pass
|
||||
|
||||
def ___neg__(self): # [bad-dunder-name]
|
||||
# author likely accidentally added an additional `_`
|
||||
pass
|
||||
|
||||
def __inv__(self): # [bad-dunder-name]
|
||||
# author likely meant to call the invert dunder method
|
||||
pass
|
||||
|
||||
def hello(self):
|
||||
print("hello")
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def init(self):
|
||||
# valid name even though someone could accidentally mean __init__
|
||||
pass
|
||||
|
||||
def _protected_method(self):
|
||||
print("Protected")
|
||||
|
||||
def __private_method(self):
|
||||
print("Private")
|
||||
|
||||
@property
|
||||
def __doc__(self):
|
||||
return "Docstring"
|
||||
|
||||
|
||||
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
|
||||
...
|
||||
@@ -14,10 +14,7 @@
|
||||
|
||||
"{:s} {:y}".format("hello", "world") # [bad-format-character]
|
||||
|
||||
"{:*^30s}".format("centered") # OK
|
||||
"{:{s}}".format("hello", s="s") # OK (nested replacement value not checked)
|
||||
|
||||
"{:{s:y}}".format("hello", s="s") # [bad-format-character] (nested replacement format spec checked)
|
||||
"{:*^30s}".format("centered")
|
||||
|
||||
## f-strings
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ print("foo %(foo)d bar %(bar)d" % {"foo": "1", "bar": "2"})
|
||||
"%(key)d" % {"key": []}
|
||||
print("%d" % ("%s" % ("nested",),))
|
||||
"%d" % ((1, 2, 3),)
|
||||
"%d" % (1 if x > 0 else [])
|
||||
|
||||
# False negatives
|
||||
WORD = "abc"
|
||||
@@ -56,4 +55,3 @@ r'\%03o' % (ord(c),)
|
||||
"%d" % (len(foo),)
|
||||
'(%r, %r, %r, %r)' % (hostname, address, username, '$PASSWORD')
|
||||
'%r' % ({'server_school_roles': server_school_roles, 'is_school_multiserver_domain': is_school_multiserver_domain}, )
|
||||
"%d" % (1 if x > 0 else 2)
|
||||
|
||||
@@ -10,4 +10,3 @@ os.getenv("AA", "GOOD" + "BAD")
|
||||
os.getenv("AA", "GOOD" + 1)
|
||||
os.getenv("AA", "GOOD %s" % "BAD")
|
||||
os.getenv("B", Z)
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ os.getenv(key="foo", default="bar")
|
||||
os.getenv(key=f"foo", default="bar")
|
||||
os.getenv(key="foo" + "bar", default=1)
|
||||
os.getenv(key=1 + "bar", default=1) # [invalid-envvar-value]
|
||||
os.getenv("PATH_TEST" if using_clear_path else "PATH_ORIG")
|
||||
os.getenv(1 if using_clear_path else "PATH_ORIG")
|
||||
|
||||
AA = "aa"
|
||||
os.getenv(AA)
|
||||
|
||||
@@ -19,10 +19,6 @@ logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
|
||||
# do not handle keyword arguments
|
||||
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d")
|
||||
|
||||
logging.info(msg="Hello %s")
|
||||
|
||||
logging.info(msg="Hello %s %s")
|
||||
|
||||
import warning
|
||||
|
||||
warning.warning("Hello %s %s", "World!")
|
||||
|
||||
@@ -15,10 +15,6 @@ logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
|
||||
# do not handle keyword arguments
|
||||
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
|
||||
|
||||
warning.warning("Hello %s", "World!", "again")
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
# Errors.
|
||||
subprocess.run("ls")
|
||||
subprocess.run("ls", shell=True)
|
||||
|
||||
# Non-errors.
|
||||
subprocess.run("ls", check=True)
|
||||
subprocess.run("ls", check=False)
|
||||
subprocess.run("ls", shell=True, check=True)
|
||||
subprocess.run("ls", shell=True, check=False)
|
||||
foo.run("ls") # Not a subprocess.run call.
|
||||
subprocess.bar("ls") # Not a subprocess.run call.
|
||||
@@ -32,30 +32,3 @@ print(
|
||||
)
|
||||
|
||||
'{' '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,17 +15,3 @@ f"{0}".format(1)
|
||||
print(f"{0}".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)
|
||||
|
||||
28
crates/ruff/resources/test/fixtures/pyupgrade/UP030_2.py
vendored
Normal file
28
crates/ruff/resources/test/fixtures/pyupgrade/UP030_2.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# 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,42 +5,11 @@ from typing import TypeAlias
|
||||
x: typing.TypeAlias = int
|
||||
x: TypeAlias = int
|
||||
|
||||
# UP040 simple generic
|
||||
|
||||
# UP040 with generics (todo)
|
||||
T = typing.TypeVar["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
|
||||
x: TypeAlias
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
x = [1, 2, 3]
|
||||
y = [4, 5, 6]
|
||||
|
||||
# RUF017
|
||||
sum([x, y], start=[])
|
||||
sum([x, y], [])
|
||||
sum([[1, 2, 3], [4, 5, 6]], start=[])
|
||||
sum([[1, 2, 3], [4, 5, 6]], [])
|
||||
sum([[1, 2, 3], [4, 5, 6]],
|
||||
[])
|
||||
|
||||
# OK
|
||||
sum([x, y])
|
||||
sum([[1, 2, 3], [4, 5, 6]])
|
||||
@@ -1,5 +0,0 @@
|
||||
# ruff: noqa: RUF100
|
||||
|
||||
import os # noqa: F401
|
||||
|
||||
print(os.sep)
|
||||
@@ -52,7 +52,3 @@ def good(a: int):
|
||||
def another_good(a):
|
||||
if a % 2 == 0:
|
||||
raise GoodArgCantBeEven(a)
|
||||
|
||||
|
||||
def another_good():
|
||||
raise NotImplementedError("This is acceptable too")
|
||||
|
||||
@@ -179,13 +179,16 @@ fn is_only<T: PartialEq>(vec: &[T], value: &T) -> bool {
|
||||
fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
|
||||
match parent {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { 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) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::For(ast::StmtFor { body, orelse, .. })
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
||||
| Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
||||
if is_only(body, child) || is_only(orelse, child) {
|
||||
return true;
|
||||
@@ -209,7 +212,14 @@ fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
..
|
||||
range: _,
|
||||
})
|
||||
| Stmt::TryStar(ast::StmtTryStar {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
}) => {
|
||||
if is_only(body, child)
|
||||
|| is_only(orelse, child)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
use ruff_python_ast::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
@@ -17,20 +16,14 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||
return;
|
||||
}
|
||||
|
||||
for binding in &*checker.semantic.bindings {
|
||||
for binding in checker.semantic.bindings.iter() {
|
||||
if checker.enabled(Rule::UnusedVariable) {
|
||||
if binding.kind.is_bound_exception()
|
||||
&& !binding.is_used()
|
||||
&& !checker
|
||||
.settings
|
||||
.dummy_variable_rgx
|
||||
.is_match(binding.name(checker.locator))
|
||||
{
|
||||
if binding.kind.is_bound_exception() && !binding.is_used() {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
pyflakes::rules::UnusedVariable {
|
||||
name: binding.name(checker.locator).to_string(),
|
||||
},
|
||||
binding.range(),
|
||||
binding.range,
|
||||
);
|
||||
if checker.patch(Rule::UnusedVariable) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
|
||||
@@ -11,18 +11,21 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||
for snapshot in for_loops {
|
||||
checker.semantic.restore(snapshot);
|
||||
|
||||
let Stmt::For(ast::StmtFor {
|
||||
if let Stmt::For(ast::StmtFor {
|
||||
target, iter, body, ..
|
||||
}) = checker.semantic.current_statement()
|
||||
else {
|
||||
unreachable!("Expected Stmt::For");
|
||||
};
|
||||
|
||||
if checker.enabled(Rule::UnusedLoopControlVariable) {
|
||||
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::IncorrectDictIterator) {
|
||||
perflint::rules::incorrect_dict_iterator(checker, target, iter);
|
||||
})
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor {
|
||||
target, iter, body, ..
|
||||
}) = &checker.semantic.current_statement()
|
||||
{
|
||||
if checker.enabled(Rule::UnusedLoopControlVariable) {
|
||||
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::IncorrectDictIterator) {
|
||||
perflint::rules::incorrect_dict_iterator(checker, target, iter);
|
||||
}
|
||||
} else {
|
||||
unreachable!("Expected Expr::For | Expr::AsyncFor");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_python_ast::cast;
|
||||
use ruff_python_semantic::analyze::{branch_detection, visibility};
|
||||
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
|
||||
|
||||
@@ -82,7 +82,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
pylint::rules::GlobalVariableNotAssigned {
|
||||
name: (*name).to_string(),
|
||||
},
|
||||
binding.range(),
|
||||
binding.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -123,14 +123,14 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let line = checker.locator.compute_line_index(shadowed.start());
|
||||
let line = checker.locator.compute_line_index(shadowed.range.start());
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::ImportShadowedByLoopVar {
|
||||
name: name.to_string(),
|
||||
line,
|
||||
},
|
||||
binding.range(),
|
||||
binding.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -172,25 +172,18 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(statement_id) = shadowed.source else {
|
||||
let Some(source) = shadowed.source else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// If this is an overloaded function, abort.
|
||||
if shadowed.kind.is_function_definition() {
|
||||
if checker
|
||||
.semantic
|
||||
.statement(statement_id)
|
||||
.as_function_def_stmt()
|
||||
.is_some_and(|function| {
|
||||
visibility::is_overload(
|
||||
&function.decorator_list,
|
||||
&checker.semantic,
|
||||
)
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if shadowed.kind.is_function_definition()
|
||||
&& visibility::is_overload(
|
||||
cast::decorator_list(checker.semantic.statement(source)),
|
||||
&checker.semantic,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Only enforce cross-scope shadowing for imports.
|
||||
@@ -219,13 +212,13 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let line = checker.locator.compute_line_index(shadowed.start());
|
||||
let line = checker.locator.compute_line_index(shadowed.range.start());
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
pyflakes::rules::RedefinedWhileUnused {
|
||||
name: (*name).to_string(),
|
||||
line,
|
||||
},
|
||||
binding.range(),
|
||||
binding.range,
|
||||
);
|
||||
if let Some(range) = binding.parent_range(&checker.semantic) {
|
||||
diagnostic.set_parent(range.start());
|
||||
@@ -248,7 +241,10 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Lambda(_)) {
|
||||
if matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Lambda(_)
|
||||
) {
|
||||
if checker.enabled(Rule::UnusedVariable) {
|
||||
pyflakes::rules::unused_variable(checker, scope, &mut diagnostics);
|
||||
}
|
||||
@@ -274,7 +270,10 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
|
||||
if matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Module
|
||||
) {
|
||||
if enforce_typing_imports {
|
||||
let runtime_imports: Vec<&Binding> = checker
|
||||
.semantic
|
||||
|
||||
@@ -171,7 +171,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
expr.start(),
|
||||
));
|
||||
|
||||
if pydocstyle::helpers::should_ignore_docstring(expr) {
|
||||
if pydocstyle::helpers::should_ignore_docstring(contents) {
|
||||
#[allow(deprecated)]
|
||||
let location = checker.locator.compute_source_location(expr.start());
|
||||
warn_user!(
|
||||
|
||||
@@ -80,9 +80,17 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
Rule::RedundantLiteralUnion,
|
||||
Rule::UnnecessaryTypeUnion,
|
||||
]) {
|
||||
// Avoid duplicate checks if the parent is a union, since these rules already
|
||||
// Avoid duplicate checks if the parent is an `Union[...]` since these rules
|
||||
// traverse nested unions.
|
||||
if !checker.semantic.in_nested_union() {
|
||||
let is_unchecked_union = checker
|
||||
.semantic
|
||||
.current_expression_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) {
|
||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||
}
|
||||
@@ -198,7 +206,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
ExprContext::Store => {
|
||||
if checker.enabled(Rule::NonLowercaseVariableInFunction) {
|
||||
if checker.semantic.current_scope().kind.is_function() {
|
||||
if checker.semantic.current_scope().kind.is_any_function() {
|
||||
// Ignore globals.
|
||||
if !checker
|
||||
.semantic
|
||||
@@ -261,7 +269,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::load_before_global_declaration(checker, id, expr);
|
||||
}
|
||||
}
|
||||
Expr::Attribute(attribute) => {
|
||||
Expr::Attribute(ast::ExprAttribute { attr, value, .. }) => {
|
||||
// Ex) typing.List[...]
|
||||
if checker.any_enabled(&[
|
||||
Rule::FutureRewritableTypeAnnotation,
|
||||
@@ -323,7 +331,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::CollectionsNamedTuple) {
|
||||
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
||||
}
|
||||
pandas_vet::rules::attr(checker, attribute);
|
||||
pandas_vet::rules::attr(checker, attr, value, expr);
|
||||
}
|
||||
Expr::Call(
|
||||
call @ ast::ExprCall {
|
||||
@@ -402,7 +410,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::FormatLiterals) {
|
||||
pyupgrade::rules::format_literals(checker, &summary, call);
|
||||
pyupgrade::rules::format_literals(checker, &summary, expr);
|
||||
}
|
||||
if checker.enabled(Rule::FString) {
|
||||
pyupgrade::rules::f_strings(
|
||||
@@ -418,7 +426,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
|
||||
if checker.enabled(Rule::BadStringFormatCharacter) {
|
||||
pylint::rules::bad_string_format_character::call(
|
||||
checker, val, location,
|
||||
checker,
|
||||
val.as_str(),
|
||||
location,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -673,8 +683,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
checker, expr, func, args, keywords,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::BooleanPositionalValueInCall) {
|
||||
flake8_boolean_trap::rules::boolean_positional_value_in_call(checker, args, func);
|
||||
if checker.enabled(Rule::BooleanPositionalValueInFunctionCall) {
|
||||
flake8_boolean_trap::rules::check_boolean_positional_value_in_function_call(
|
||||
checker, args, func,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::Debugger) {
|
||||
flake8_debugger::rules::debugger_call(checker, expr, func);
|
||||
@@ -756,19 +768,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::PytestUnittestRaisesAssertion) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_pytest_style::rules::unittest_raises_assertion(checker, call)
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::SubprocessPopenPreexecFn) {
|
||||
pylint::rules::subprocess_popen_preexec_fn(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::SubprocessRunWithoutCheck) {
|
||||
pylint::rules::subprocess_run_without_check(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::PytestRaisesWithoutException,
|
||||
Rule::PytestRaisesTooBroad,
|
||||
@@ -873,9 +875,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnsupportedMethodCallOnAll) {
|
||||
flake8_pyi::rules::unsupported_method_call_on_all(checker, func);
|
||||
}
|
||||
if checker.enabled(Rule::QuadraticListSummation) {
|
||||
ruff::rules::quadratic_list_summation(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(ast::ExprDict {
|
||||
keys,
|
||||
@@ -924,7 +923,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::await_outside_async(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::FString(ast::ExprFString { values, .. }) => {
|
||||
Expr::JoinedStr(ast::ExprJoinedStr { values, range: _ }) => {
|
||||
if checker.enabled(Rule::FStringMissingPlaceholders) {
|
||||
pyflakes::rules::f_string_missing_placeholders(expr, values, checker);
|
||||
}
|
||||
@@ -951,7 +950,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
range: _,
|
||||
}) => {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
}) = left.as_ref()
|
||||
{
|
||||
@@ -1085,34 +1084,47 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid duplicate checks if the parent is a union, since these rules already
|
||||
// Avoid duplicate checks if the parent is an `|` since these rules
|
||||
// traverse nested unions.
|
||||
if !checker.semantic.in_nested_union() {
|
||||
if checker.enabled(Rule::DuplicateUnionMember)
|
||||
&& checker.semantic.in_type_definition()
|
||||
{
|
||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryLiteralUnion) {
|
||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantLiteralUnion) {
|
||||
flake8_pyi::rules::redundant_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryTypeUnion) {
|
||||
flake8_pyi::rules::unnecessary_type_union(checker, expr);
|
||||
}
|
||||
let is_unchecked_union = !matches!(
|
||||
checker.semantic.current_expression_parent(),
|
||||
Some(Expr::BinOp(ast::ExprBinOp {
|
||||
op: Operator::BitOr,
|
||||
..
|
||||
}))
|
||||
);
|
||||
if checker.enabled(Rule::DuplicateUnionMember)
|
||||
&& checker.semantic.in_type_definition()
|
||||
&& is_unchecked_union
|
||||
{
|
||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryLiteralUnion) && is_unchecked_union {
|
||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantLiteralUnion) && is_unchecked_union {
|
||||
flake8_pyi::rules::redundant_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryTypeUnion) && is_unchecked_union {
|
||||
flake8_pyi::rules::unnecessary_type_union(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::UnaryOp(
|
||||
unary_op @ ast::ExprUnaryOp {
|
||||
op,
|
||||
operand,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[Rule::NotInTest, Rule::NotIsTest]) {
|
||||
pycodestyle::rules::not_tests(checker, unary_op);
|
||||
Expr::UnaryOp(ast::ExprUnaryOp {
|
||||
op,
|
||||
operand,
|
||||
range: _,
|
||||
}) => {
|
||||
let check_not_in = checker.enabled(Rule::NotInTest);
|
||||
let check_not_is = checker.enabled(Rule::NotIsTest);
|
||||
if check_not_in || check_not_is {
|
||||
pycodestyle::rules::not_tests(
|
||||
checker,
|
||||
expr,
|
||||
*op,
|
||||
operand,
|
||||
check_not_in,
|
||||
check_not_is,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::UnaryPrefixIncrementDecrement) {
|
||||
flake8_bugbear::rules::unary_prefix_increment_decrement(
|
||||
@@ -1137,8 +1149,18 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[Rule::NoneComparison, Rule::TrueFalseComparison]) {
|
||||
pycodestyle::rules::literal_comparisons(checker, compare);
|
||||
let check_none_comparisons = checker.enabled(Rule::NoneComparison);
|
||||
let check_true_false_comparisons = checker.enabled(Rule::TrueFalseComparison);
|
||||
if check_none_comparisons || check_true_false_comparisons {
|
||||
pycodestyle::rules::literal_comparisons(
|
||||
checker,
|
||||
expr,
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
check_none_comparisons,
|
||||
check_true_false_comparisons,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::IsLiteral) {
|
||||
pyflakes::rules::invalid_literal_comparison(checker, left, ops, comparators, expr);
|
||||
@@ -1221,7 +1243,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::HardcodedTempFile) {
|
||||
flake8_bandit::rules::hardcoded_tmp_directory(checker, expr, value);
|
||||
if let Some(diagnostic) = flake8_bandit::rules::hardcoded_tmp_directory(
|
||||
expr,
|
||||
value,
|
||||
&checker.settings.flake8_bandit.hardcoded_tmp_directory,
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, kind.as_deref());
|
||||
|
||||
@@ -6,6 +6,9 @@ use crate::rules::{flake8_bugbear, flake8_pyi, ruff};
|
||||
|
||||
/// Run lint rules over a [`Parameters`] syntax node.
|
||||
pub(crate) fn parameters(parameters: &Parameters, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::MutableArgumentDefault) {
|
||||
flake8_bugbear::rules::mutable_argument_default(checker, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::FunctionCallInDefaultArgument) {
|
||||
flake8_bugbear::rules::function_call_in_argument_default(checker, parameters);
|
||||
}
|
||||
|
||||
@@ -69,18 +69,24 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(
|
||||
function_def @ ast::StmtFunctionDef {
|
||||
is_async,
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
parameters,
|
||||
body,
|
||||
type_params,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
parameters,
|
||||
body,
|
||||
type_params,
|
||||
..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
parameters,
|
||||
body,
|
||||
type_params,
|
||||
..
|
||||
}) => {
|
||||
if checker.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
|
||||
flake8_django::rules::non_leading_receiver_decorator(checker, decorator_list);
|
||||
}
|
||||
@@ -145,11 +151,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_pyi::rules::non_self_return_type(
|
||||
checker,
|
||||
stmt,
|
||||
*is_async,
|
||||
name,
|
||||
decorator_list,
|
||||
returns.as_ref().map(AsRef::as_ref),
|
||||
parameters,
|
||||
stmt.is_async_function_def_stmt(),
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::CustomTypeVarReturnType) {
|
||||
@@ -175,7 +181,12 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BadExitAnnotation) {
|
||||
flake8_pyi::rules::bad_exit_annotation(checker, *is_async, name, parameters);
|
||||
flake8_pyi::rules::bad_exit_annotation(
|
||||
checker,
|
||||
stmt.is_async_function_def_stmt(),
|
||||
name,
|
||||
parameters,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantNumericUnion) {
|
||||
flake8_pyi::rules::redundant_numeric_union(checker, parameters);
|
||||
@@ -206,9 +217,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::CachedInstanceMethod) {
|
||||
flake8_bugbear::rules::cached_instance_method(checker, decorator_list);
|
||||
}
|
||||
if checker.enabled(Rule::MutableArgumentDefault) {
|
||||
flake8_bugbear::rules::mutable_argument_default(checker, function_def);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnnecessaryReturnNone,
|
||||
Rule::ImplicitReturnValue,
|
||||
@@ -300,7 +308,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.any_enabled(&[
|
||||
Rule::PytestParametrizeNamesWrongType,
|
||||
Rule::PytestParametrizeValuesWrongType,
|
||||
Rule::PytestDuplicateParametrizeTestCases,
|
||||
]) {
|
||||
flake8_pytest_style::rules::parametrize(checker, decorator_list);
|
||||
}
|
||||
@@ -310,16 +317,16 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
]) {
|
||||
flake8_pytest_style::rules::marks(checker, decorator_list);
|
||||
}
|
||||
if checker.enabled(Rule::BooleanTypeHintPositionalArgument) {
|
||||
flake8_boolean_trap::rules::boolean_type_hint_positional_argument(
|
||||
if checker.enabled(Rule::BooleanPositionalArgInFunctionDefinition) {
|
||||
flake8_boolean_trap::rules::check_positional_boolean_in_def(
|
||||
checker,
|
||||
name,
|
||||
decorator_list,
|
||||
parameters,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::BooleanDefaultValuePositionalArgument) {
|
||||
flake8_boolean_trap::rules::boolean_default_value_positional_argument(
|
||||
if checker.enabled(Rule::BooleanDefaultValueInFunctionDefinition) {
|
||||
flake8_boolean_trap::rules::check_boolean_default_value_in_function_definition(
|
||||
checker,
|
||||
name,
|
||||
decorator_list,
|
||||
@@ -512,16 +519,17 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::SingleStringSlots) {
|
||||
pylint::rules::single_string_slots(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::BadDunderMethodName) {
|
||||
pylint::rules::bad_dunder_method_name(checker, body);
|
||||
}
|
||||
}
|
||||
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||
if checker.enabled(Rule::MultipleImportsOnOneLine) {
|
||||
pycodestyle::rules::multiple_imports_on_one_line(checker, stmt, names);
|
||||
}
|
||||
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
|
||||
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
|
||||
pycodestyle::rules::module_import_not_at_top_of_file(
|
||||
checker,
|
||||
stmt,
|
||||
checker.locator,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
for name in names {
|
||||
@@ -557,29 +565,12 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BannedApi) {
|
||||
flake8_tidy_imports::rules::banned_api(
|
||||
flake8_tidy_imports::rules::name_or_parent_is_banned(
|
||||
checker,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||
flake8_tidy_imports::matchers::MatchNameOrParent {
|
||||
module: &alias.name,
|
||||
},
|
||||
),
|
||||
&alias,
|
||||
&alias.name,
|
||||
alias,
|
||||
);
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::BannedModuleLevelImports) {
|
||||
flake8_tidy_imports::rules::banned_module_level_imports(
|
||||
checker,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||
flake8_tidy_imports::matchers::MatchNameOrParent {
|
||||
module: &alias.name,
|
||||
},
|
||||
),
|
||||
&alias,
|
||||
);
|
||||
}
|
||||
|
||||
if !checker.source_type.is_stub() {
|
||||
if checker.enabled(Rule::UselessImportAlias) {
|
||||
pylint::rules::useless_import_alias(checker, alias);
|
||||
@@ -694,7 +685,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
let module = module.as_deref();
|
||||
let level = level.map(|level| level.to_u32());
|
||||
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
|
||||
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
|
||||
pycodestyle::rules::module_import_not_at_top_of_file(
|
||||
checker,
|
||||
stmt,
|
||||
checker.locator,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
for name in names {
|
||||
@@ -730,56 +725,16 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if let Some(module) =
|
||||
helpers::resolve_imported_module_path(level, module, checker.module_path)
|
||||
{
|
||||
flake8_tidy_imports::rules::banned_api(
|
||||
checker,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||
flake8_tidy_imports::matchers::MatchNameOrParent { module: &module },
|
||||
),
|
||||
&stmt,
|
||||
);
|
||||
flake8_tidy_imports::rules::name_or_parent_is_banned(checker, &module, stmt);
|
||||
|
||||
for alias in names {
|
||||
if &alias.name == "*" {
|
||||
continue;
|
||||
}
|
||||
flake8_tidy_imports::rules::banned_api(
|
||||
flake8_tidy_imports::rules::name_is_banned(
|
||||
checker,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchName(
|
||||
flake8_tidy_imports::matchers::MatchName {
|
||||
module: &module,
|
||||
member: &alias.name,
|
||||
},
|
||||
),
|
||||
&alias,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BannedModuleLevelImports) {
|
||||
if let Some(module) =
|
||||
helpers::resolve_imported_module_path(level, module, checker.module_path)
|
||||
{
|
||||
flake8_tidy_imports::rules::banned_module_level_imports(
|
||||
checker,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||
flake8_tidy_imports::matchers::MatchNameOrParent { module: &module },
|
||||
),
|
||||
&stmt,
|
||||
);
|
||||
|
||||
for alias in names {
|
||||
if &alias.name == "*" {
|
||||
continue;
|
||||
}
|
||||
flake8_tidy_imports::rules::banned_module_level_imports(
|
||||
checker,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchName(
|
||||
flake8_tidy_imports::matchers::MatchName {
|
||||
module: &module,
|
||||
member: &alias.name,
|
||||
},
|
||||
),
|
||||
&alias,
|
||||
format!("{module}.{}", alias.name),
|
||||
alias,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1142,7 +1097,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pygrep_hooks::rules::non_existent_mock_method(checker, test);
|
||||
}
|
||||
}
|
||||
Stmt::With(with_ @ ast::StmtWith { items, body, .. }) => {
|
||||
Stmt::With(ast::StmtWith { items, body, .. })
|
||||
| Stmt::AsyncWith(ast::StmtAsyncWith { items, body, .. }) => {
|
||||
if checker.enabled(Rule::AssertRaisesException) {
|
||||
flake8_bugbear::rules::assert_raises_exception(checker, items);
|
||||
}
|
||||
@@ -1152,7 +1108,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::MultipleWithStatements) {
|
||||
flake8_simplify::rules::multiple_with_statements(
|
||||
checker,
|
||||
with_,
|
||||
stmt,
|
||||
body,
|
||||
checker.semantic.current_statement_parent(),
|
||||
);
|
||||
}
|
||||
@@ -1177,6 +1134,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
iter,
|
||||
orelse,
|
||||
..
|
||||
})
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor {
|
||||
target,
|
||||
body,
|
||||
iter,
|
||||
orelse,
|
||||
..
|
||||
}) => {
|
||||
if checker.any_enabled(&[Rule::UnusedLoopControlVariable, Rule::IncorrectDictIterator])
|
||||
{
|
||||
@@ -1226,7 +1190,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
..
|
||||
range: _,
|
||||
})
|
||||
| Stmt::TryStar(ast::StmtTryStar {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::JumpStatementInFinally) {
|
||||
flake8_bugbear::rules::jump_statement_in_finally(checker, finalbody);
|
||||
@@ -1368,7 +1339,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if !checker
|
||||
.semantic
|
||||
.current_scopes()
|
||||
.any(|scope| scope.kind.is_function())
|
||||
.any(|scope| scope.kind.is_any_function())
|
||||
{
|
||||
if checker.enabled(Rule::UnprefixedTypeParam) {
|
||||
flake8_pyi::rules::prefix_type_params(checker, value, targets);
|
||||
@@ -1433,7 +1404,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if !checker
|
||||
.semantic
|
||||
.current_scopes()
|
||||
.any(|scope| scope.kind.is_function())
|
||||
.any(|scope| scope.kind.is_any_function())
|
||||
{
|
||||
flake8_pyi::rules::annotated_assignment_default_in_stub(
|
||||
checker, target, value, annotation,
|
||||
|
||||
@@ -176,12 +176,13 @@ impl<'a> Checker<'a> {
|
||||
///
|
||||
/// If the current expression in the context is not an f-string, returns ``None``.
|
||||
pub(crate) fn f_string_quote_style(&self) -> Option<Quote> {
|
||||
if !self.semantic.in_f_string() {
|
||||
let model = &self.semantic;
|
||||
if !model.in_f_string() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Find the quote character used to start the containing f-string.
|
||||
let expr = self.semantic.current_expression()?;
|
||||
let expr = model.current_expression()?;
|
||||
let string_range = self.indexer.f_string_range(expr.start())?;
|
||||
let trailing_quote = trailing_quote(self.locator.slice(string_range))?;
|
||||
|
||||
@@ -454,16 +455,22 @@ where
|
||||
|
||||
// Step 2: Traversal
|
||||
match stmt {
|
||||
Stmt::FunctionDef(
|
||||
function_def @ ast::StmtFunctionDef {
|
||||
body,
|
||||
parameters,
|
||||
decorator_list,
|
||||
returns,
|
||||
type_params,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
body,
|
||||
parameters,
|
||||
decorator_list,
|
||||
returns,
|
||||
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
|
||||
// deferred.
|
||||
for decorator in decorator_list {
|
||||
@@ -524,7 +531,8 @@ where
|
||||
}
|
||||
|
||||
let definition = docstrings::extraction::extract_definition(
|
||||
ExtractionTarget::Function(function_def),
|
||||
ExtractionTarget::Function,
|
||||
stmt,
|
||||
self.semantic.definition_id,
|
||||
&self.semantic.definitions,
|
||||
);
|
||||
@@ -532,7 +540,8 @@ where
|
||||
|
||||
self.semantic.push_scope(match &stmt {
|
||||
Stmt::FunctionDef(stmt) => ScopeKind::Function(stmt),
|
||||
_ => unreachable!("Expected Stmt::FunctionDef"),
|
||||
Stmt::AsyncFunctionDef(stmt) => ScopeKind::AsyncFunction(stmt),
|
||||
_ => unreachable!("Expected Stmt::FunctionDef | Stmt::AsyncFunctionDef"),
|
||||
});
|
||||
|
||||
self.deferred.functions.push(self.semantic.snapshot());
|
||||
@@ -566,7 +575,8 @@ where
|
||||
}
|
||||
|
||||
let definition = docstrings::extraction::extract_definition(
|
||||
ExtractionTarget::Class(class_def),
|
||||
ExtractionTarget::Class,
|
||||
stmt,
|
||||
self.semantic.definition_id,
|
||||
&self.semantic.definitions,
|
||||
);
|
||||
@@ -599,7 +609,14 @@ where
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
..
|
||||
range: _,
|
||||
})
|
||||
| Stmt::TryStar(ast::StmtTryStar {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
}) => {
|
||||
let mut handled_exceptions = Exceptions::empty();
|
||||
for type_ in extract_handled_exceptions(handlers) {
|
||||
@@ -726,7 +743,8 @@ where
|
||||
|
||||
// Step 3: Clean-up
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) => {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. })
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { name, .. }) => {
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.deferred.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Function scope
|
||||
@@ -1189,7 +1207,7 @@ where
|
||||
));
|
||||
}
|
||||
}
|
||||
Expr::FString(_) => {
|
||||
Expr::JoinedStr(_) => {
|
||||
self.semantic.flags |= SemanticModelFlags::F_STRING;
|
||||
visitor::walk_expr(self, expr);
|
||||
}
|
||||
@@ -1268,7 +1286,7 @@ where
|
||||
|
||||
fn visit_format_spec(&mut self, format_spec: &'b Expr) {
|
||||
match format_spec {
|
||||
Expr::FString(ast::ExprFString { values, .. }) => {
|
||||
Expr::JoinedStr(ast::ExprJoinedStr { values, range: _ }) => {
|
||||
for value in values {
|
||||
self.visit_expr(value);
|
||||
}
|
||||
@@ -1608,7 +1626,7 @@ impl<'a> Checker<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
if parent.is_for_stmt() {
|
||||
if matches!(parent, Stmt::For(_) | Stmt::AsyncFor(_)) {
|
||||
self.add_binding(
|
||||
id,
|
||||
expr.range(),
|
||||
@@ -1807,14 +1825,19 @@ impl<'a> Checker<'a> {
|
||||
for snapshot in deferred_functions {
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if let Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
body, parameters, ..
|
||||
}) = self.semantic.current_statement()
|
||||
{
|
||||
self.visit_parameters(parameters);
|
||||
self.visit_body(body);
|
||||
} else {
|
||||
unreachable!("Expected Stmt::FunctionDef")
|
||||
match &self.semantic.current_statement() {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
body, parameters, ..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
body, parameters, ..
|
||||
}) => {
|
||||
self.visit_parameters(parameters);
|
||||
self.visit_body(body);
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Expected Stmt::FunctionDef | Stmt::AsyncFunctionDef")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1855,7 +1878,7 @@ impl<'a> Checker<'a> {
|
||||
.map(|binding_id| &self.semantic.bindings[binding_id])
|
||||
.filter_map(|binding| match &binding.kind {
|
||||
BindingKind::Export(Export { names }) => {
|
||||
Some(names.iter().map(|name| (*name, binding.range())))
|
||||
Some(names.iter().map(|name| (*name, binding.range)))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::TokenKind;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pycodestyle::rules::logical_lines::{
|
||||
|
||||
@@ -94,15 +94,8 @@ pub(crate) fn check_noqa(
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce that the noqa directive was actually used (RUF100), unless RUF100 was itself
|
||||
// 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()),
|
||||
})
|
||||
{
|
||||
// Enforce that the noqa directive was actually used (RUF100).
|
||||
if analyze_directives && settings.rules.enabled(Rule::UnusedNOQA) {
|
||||
for line in noqa_directives.lines() {
|
||||
match &line.directive {
|
||||
Directive::All(directive) => {
|
||||
|
||||
@@ -226,10 +226,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "W0711") => (RuleGroup::Unspecified, rules::pylint::rules::BinaryOpException),
|
||||
(Pylint, "W1508") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarDefault),
|
||||
(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, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
|
||||
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
|
||||
(Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax),
|
||||
|
||||
// flake8-async
|
||||
@@ -311,7 +309,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
// flake8-tidy-imports
|
||||
(Flake8TidyImports, "251") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::BannedApi),
|
||||
(Flake8TidyImports, "252") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::RelativeImports),
|
||||
(Flake8TidyImports, "253") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::BannedModuleLevelImports),
|
||||
|
||||
// flake8-return
|
||||
(Flake8Return, "501") => (RuleGroup::Unspecified, rules::flake8_return::rules::UnnecessaryReturnNone),
|
||||
@@ -568,9 +565,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bandit, "701") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
|
||||
|
||||
// flake8-boolean-trap
|
||||
(Flake8BooleanTrap, "001") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanTypeHintPositionalArgument),
|
||||
(Flake8BooleanTrap, "002") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanDefaultValuePositionalArgument),
|
||||
(Flake8BooleanTrap, "003") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanPositionalValueInCall),
|
||||
(Flake8BooleanTrap, "001") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanPositionalArgInFunctionDefinition),
|
||||
(Flake8BooleanTrap, "002") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanDefaultValueInFunctionDefinition),
|
||||
(Flake8BooleanTrap, "003") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanPositionalValueInFunctionCall),
|
||||
|
||||
// flake8-unused-arguments
|
||||
(Flake8UnusedArguments, "001") => (RuleGroup::Unspecified, rules::flake8_unused_arguments::rules::UnusedFunctionArgument),
|
||||
@@ -685,7 +682,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8PytestStyle, "011") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestRaisesTooBroad),
|
||||
(Flake8PytestStyle, "012") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestRaisesWithMultipleStatements),
|
||||
(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, "016") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestFailWithoutMessage),
|
||||
(Flake8PytestStyle, "017") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestAssertInExcept),
|
||||
@@ -698,7 +694,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8PytestStyle, "024") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUnnecessaryAsyncioMarkOnFixture),
|
||||
(Flake8PytestStyle, "025") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestErroneousUseFixturesOnFixture),
|
||||
(Flake8PytestStyle, "026") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUseFixturesWithoutParameters),
|
||||
(Flake8PytestStyle, "027") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUnittestRaisesAssertion),
|
||||
|
||||
// flake8-pie
|
||||
(Flake8Pie, "790") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessaryPass),
|
||||
@@ -816,7 +811,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "014") => (RuleGroup::Nursery, rules::ruff::rules::UnreachableCode),
|
||||
(Ruff, "015") => (RuleGroup::Unspecified, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement),
|
||||
(Ruff, "016") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidIndexType),
|
||||
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
|
||||
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Extract docstrings from an AST.
|
||||
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Stmt};
|
||||
|
||||
use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, MemberKind};
|
||||
|
||||
/// Extract a docstring from a function or class body.
|
||||
@@ -27,48 +28,63 @@ pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
|
||||
match definition {
|
||||
Definition::Module(module) => docstring_from(module.python_ast),
|
||||
Definition::Member(member) => docstring_from(member.body()),
|
||||
Definition::Member(member) => {
|
||||
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)]
|
||||
pub(crate) enum ExtractionTarget<'a> {
|
||||
Class(&'a ast::StmtClassDef),
|
||||
Function(&'a ast::StmtFunctionDef),
|
||||
pub(crate) enum ExtractionTarget {
|
||||
Class,
|
||||
Function,
|
||||
}
|
||||
|
||||
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
||||
pub(crate) fn extract_definition<'a>(
|
||||
target: ExtractionTarget<'a>,
|
||||
target: ExtractionTarget,
|
||||
stmt: &'a Stmt,
|
||||
parent: DefinitionId,
|
||||
definitions: &Definitions<'a>,
|
||||
) -> Member<'a> {
|
||||
match target {
|
||||
ExtractionTarget::Function(function) => match &definitions[parent] {
|
||||
ExtractionTarget::Function => match &definitions[parent] {
|
||||
Definition::Module(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::Function(function),
|
||||
kind: MemberKind::Function,
|
||||
stmt,
|
||||
},
|
||||
Definition::Member(Member {
|
||||
kind: MemberKind::Class(_) | MemberKind::NestedClass(_),
|
||||
kind: MemberKind::Class | MemberKind::NestedClass,
|
||||
..
|
||||
}) => Member {
|
||||
parent,
|
||||
kind: MemberKind::Method(function),
|
||||
kind: MemberKind::Method,
|
||||
stmt,
|
||||
},
|
||||
Definition::Member(_) => Member {
|
||||
Definition::Member(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::NestedFunction(function),
|
||||
kind: MemberKind::NestedFunction,
|
||||
stmt,
|
||||
},
|
||||
},
|
||||
ExtractionTarget::Class(class) => match &definitions[parent] {
|
||||
Definition::Module(_) => Member {
|
||||
ExtractionTarget::Class => match &definitions[parent] {
|
||||
Definition::Module(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::Class(class),
|
||||
kind: MemberKind::Class,
|
||||
stmt,
|
||||
},
|
||||
Definition::Member(_) => Member {
|
||||
Definition::Member(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::NestedClass(class),
|
||||
kind: MemberKind::NestedClass,
|
||||
stmt,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ use std::fmt::{Debug, Formatter};
|
||||
use std::ops::Deref;
|
||||
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_python_semantic::Definition;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
pub(crate) mod extraction;
|
||||
pub(crate) mod google;
|
||||
@@ -27,34 +28,43 @@ impl<'a> Docstring<'a> {
|
||||
DocstringBody { docstring: self }
|
||||
}
|
||||
|
||||
pub(crate) fn start(&self) -> TextSize {
|
||||
self.expr.start()
|
||||
}
|
||||
|
||||
pub(crate) fn end(&self) -> TextSize {
|
||||
self.expr.end()
|
||||
}
|
||||
|
||||
pub(crate) fn range(&self) -> TextRange {
|
||||
self.expr.range()
|
||||
}
|
||||
|
||||
pub(crate) fn leading_quote(&self) -> &'a str {
|
||||
&self.contents[TextRange::up_to(self.body_range.start())]
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for Docstring<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
self.expr.range()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct DocstringBody<'a> {
|
||||
docstring: &'a Docstring<'a>,
|
||||
}
|
||||
|
||||
impl<'a> DocstringBody<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn start(self) -> TextSize {
|
||||
self.range().start()
|
||||
}
|
||||
|
||||
pub(crate) fn range(self) -> TextRange {
|
||||
self.docstring.body_range + self.docstring.start()
|
||||
}
|
||||
|
||||
pub(crate) fn as_str(self) -> &'a str {
|
||||
&self.docstring.contents[self.docstring.body_range]
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for DocstringBody<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
self.docstring.body_range + self.docstring.start()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DocstringBody<'_> {
|
||||
type Target = str;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::fmt::{Debug, Formatter};
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use ruff_python_ast::docstrings::{leading_space, leading_words};
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
@@ -367,12 +366,6 @@ impl<'a> SectionContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for SectionContext<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SectionContext<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SectionContext")
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use globset::GlobMatcher;
|
||||
use log::debug;
|
||||
use path_absolutize::Absolutize;
|
||||
use path_absolutize::{path_dedot, Absolutize};
|
||||
|
||||
use crate::registry::RuleSet;
|
||||
|
||||
@@ -61,13 +61,7 @@ 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.
|
||||
pub fn relativize_path<P: AsRef<Path>>(path: P) -> String {
|
||||
let path = path.as_ref();
|
||||
|
||||
#[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) {
|
||||
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
|
||||
return format!("{}", path.display());
|
||||
}
|
||||
format!("{}", path.display())
|
||||
|
||||
@@ -67,13 +67,9 @@ impl<'a> Insertion<'a> {
|
||||
TextSize::default()
|
||||
};
|
||||
|
||||
// Skip over commented lines, with whitespace separation.
|
||||
// Skip over commented lines.
|
||||
for line in UniversalNewlineIterator::with_offset(locator.after(location), location) {
|
||||
let trimmed_line = line.trim_whitespace_start();
|
||||
if trimmed_line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if trimmed_line.starts_with('#') {
|
||||
if line.trim_whitespace_start().starts_with('#') {
|
||||
location = line.full_end();
|
||||
} else {
|
||||
break;
|
||||
|
||||
@@ -194,7 +194,7 @@ impl<'a> Importer<'a> {
|
||||
// import and the current location, and thus the symbol would not be available). It's also
|
||||
// unclear whether should add an import statement at the start of the file, since it could
|
||||
// be shadowed between the import and the current location.
|
||||
if imported_name.start() > at {
|
||||
if imported_name.range().start() > at {
|
||||
return Some(Err(ResolutionError::ImportAfterUsage));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
||||
use std::iter;
|
||||
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.
|
||||
pub fn round_trip(path: &Path) -> anyhow::Result<String> {
|
||||
let mut notebook = Notebook::from_path(path).map_err(|err| {
|
||||
let mut notebook = Notebook::read(path).map_err(|err| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to read notebook file `{}`: {:?}",
|
||||
path.display(),
|
||||
@@ -120,30 +120,18 @@ pub struct Notebook {
|
||||
|
||||
impl Notebook {
|
||||
/// Read the Jupyter Notebook from the given [`Path`].
|
||||
pub fn from_path(path: &Path) -> Result<Self, Box<Diagnostic>> {
|
||||
Self::from_reader(BufReader::new(File::open(path).map_err(|err| {
|
||||
///
|
||||
/// See also the black implementation
|
||||
/// <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(
|
||||
IOError {
|
||||
message: format!("{err}"),
|
||||
},
|
||||
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 mut buf = [0; 1];
|
||||
reader.read_exact(&mut buf).is_ok_and(|_| buf[0] == b'\n')
|
||||
@@ -156,7 +144,7 @@ impl Notebook {
|
||||
TextRange::default(),
|
||||
)
|
||||
})?;
|
||||
let raw_notebook: RawNotebook = match serde_json::from_reader(reader.by_ref()) {
|
||||
let raw_notebook: RawNotebook = match serde_json::from_reader(reader) {
|
||||
Ok(notebook) => notebook,
|
||||
Err(err) => {
|
||||
// Translate the error into a diagnostic
|
||||
@@ -171,19 +159,14 @@ impl Notebook {
|
||||
Category::Syntax | Category::Eof => {
|
||||
// Maybe someone saved the python sources (those with the `# %%` separator)
|
||||
// as jupyter notebook instead. Let's help them.
|
||||
let mut contents = String::new();
|
||||
reader
|
||||
.rewind()
|
||||
.and_then(|_| reader.read_to_string(&mut contents))
|
||||
.map_err(|err| {
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: format!("{err}"),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let contents = std::fs::read_to_string(path).map_err(|err| {
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: format!("{err}"),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
})?;
|
||||
// Check if tokenizing was successful and the file is non-empty
|
||||
if lex(&contents, Mode::Module).any(|result| result.is_err()) {
|
||||
Diagnostic::new(
|
||||
@@ -199,7 +182,7 @@ impl Notebook {
|
||||
Diagnostic::new(
|
||||
SyntaxError {
|
||||
message: format!(
|
||||
"Expected a Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT}), \
|
||||
"Expected a Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT} extension), \
|
||||
which must be internally stored as JSON, \
|
||||
but found a Python source file: {err}"
|
||||
),
|
||||
@@ -501,22 +484,22 @@ mod tests {
|
||||
fn test_invalid() {
|
||||
let path = Path::new("resources/test/fixtures/jupyter/invalid_extension.ipynb");
|
||||
assert_eq!(
|
||||
Notebook::from_path(path).unwrap_err().kind.body,
|
||||
"SyntaxError: Expected a Jupyter Notebook (.ipynb), \
|
||||
Notebook::read(path).unwrap_err().kind.body,
|
||||
"SyntaxError: Expected a Jupyter Notebook (.ipynb extension), \
|
||||
which must be internally stored as JSON, \
|
||||
but found a Python source file: \
|
||||
expected value at line 1 column 1"
|
||||
);
|
||||
let path = Path::new("resources/test/fixtures/jupyter/not_json.ipynb");
|
||||
assert_eq!(
|
||||
Notebook::from_path(path).unwrap_err().kind.body,
|
||||
Notebook::read(path).unwrap_err().kind.body,
|
||||
"SyntaxError: A Jupyter Notebook (.ipynb) must internally be JSON, \
|
||||
but this file isn't valid JSON: \
|
||||
expected value at line 1 column 1"
|
||||
);
|
||||
let path = Path::new("resources/test/fixtures/jupyter/wrong_schema.ipynb");
|
||||
assert_eq!(
|
||||
Notebook::from_path(path).unwrap_err().kind.body,
|
||||
Notebook::read(path).unwrap_err().kind.body,
|
||||
"SyntaxError: This file does not match the schema expected of Jupyter Notebooks: \
|
||||
missing field `cells` at line 1 column 2"
|
||||
);
|
||||
@@ -588,11 +571,11 @@ print("after empty cells")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ipy_escape_command() -> Result<()> {
|
||||
let path = "ipy_escape_command.ipynb".to_string();
|
||||
fn test_line_magics() -> Result<()> {
|
||||
let path = "line_magics.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
&path,
|
||||
Path::new("ipy_escape_command_expected.ipynb"),
|
||||
Path::new("line_magics_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
assert_messages!(diagnostics, path, source_kind);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff/src/jupyter/notebook.rs
|
||||
---
|
||||
ipy_escape_command.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
||||
line_magics.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
3 | %matplotlib inline
|
||||
4 |
|
||||
@@ -1,5 +1,4 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::num::NonZeroU8;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use ruff_macros::CacheKey;
|
||||
@@ -59,7 +58,7 @@ impl Eq for LineWidth {}
|
||||
|
||||
impl PartialOrd for LineWidth {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
self.width.partial_cmp(&other.width)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +83,7 @@ impl LineWidth {
|
||||
}
|
||||
|
||||
fn update(mut self, chars: impl Iterator<Item = char>) -> Self {
|
||||
let tab_size: usize = self.tab_size.as_usize();
|
||||
let tab_size: usize = self.tab_size.into();
|
||||
for c in chars {
|
||||
match c {
|
||||
'\t' => {
|
||||
@@ -145,22 +144,22 @@ impl PartialOrd<LineLength> for LineWidth {
|
||||
/// The size of a tab.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct TabSize(NonZeroU8);
|
||||
|
||||
impl TabSize {
|
||||
pub(crate) fn as_usize(self) -> usize {
|
||||
self.0.get() as usize
|
||||
}
|
||||
}
|
||||
pub struct TabSize(pub u8);
|
||||
|
||||
impl Default for TabSize {
|
||||
fn default() -> Self {
|
||||
Self(NonZeroU8::new(4).unwrap())
|
||||
Self(4)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonZeroU8> for TabSize {
|
||||
fn from(tab_size: NonZeroU8) -> Self {
|
||||
impl From<u8> for TabSize {
|
||||
fn from(tab_size: u8) -> Self {
|
||||
Self(tab_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TabSize> for usize {
|
||||
fn from(tab_size: TabSize) -> Self {
|
||||
tab_size.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,10 +293,12 @@ impl Display for MessageCodeFrame<'_> {
|
||||
}
|
||||
|
||||
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 last_end = 0;
|
||||
let mut range = annotation_range;
|
||||
let mut line_width = LineWidth::new(TabSize::default());
|
||||
let mut line_width = LineWidth::new(TAB_SIZE);
|
||||
|
||||
for (index, c) in source.char_indices() {
|
||||
let old_width = line_width.get();
|
||||
|
||||
@@ -72,7 +72,7 @@ impl RuleSet {
|
||||
/// let set_1 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
|
||||
/// let set_2 = RuleSet::from_rules(&[
|
||||
/// Rule::BadQuotesInlineString,
|
||||
/// Rule::BooleanPositionalValueInCall,
|
||||
/// Rule::BooleanPositionalValueInFunctionCall,
|
||||
/// ]);
|
||||
///
|
||||
/// let union = set_1.union(&set_2);
|
||||
@@ -80,7 +80,7 @@ impl RuleSet {
|
||||
/// assert!(union.contains(Rule::AmbiguousFunctionName));
|
||||
/// assert!(union.contains(Rule::AnyType));
|
||||
/// assert!(union.contains(Rule::BadQuotesInlineString));
|
||||
/// assert!(union.contains(Rule::BooleanPositionalValueInCall));
|
||||
/// assert!(union.contains(Rule::BooleanPositionalValueInFunctionCall));
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub const fn union(mut self, other: &Self) -> Self {
|
||||
@@ -132,7 +132,7 @@ impl RuleSet {
|
||||
/// ])));
|
||||
///
|
||||
/// assert!(!set_1.intersects(&RuleSet::from_rules(&[
|
||||
/// Rule::BooleanPositionalValueInCall,
|
||||
/// Rule::BooleanPositionalValueInFunctionCall,
|
||||
/// Rule::BadQuotesInlineString
|
||||
/// ])));
|
||||
/// ```
|
||||
|
||||
@@ -4,7 +4,6 @@ use anyhow::{anyhow, Result};
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_python_semantic::{Binding, BindingKind, Scope, ScopeId, SemanticModel};
|
||||
|
||||
pub(crate) struct Renamer;
|
||||
@@ -221,12 +220,12 @@ impl Renamer {
|
||||
BindingKind::Import(_) | BindingKind::FromImport(_) => {
|
||||
if binding.is_alias() {
|
||||
// Ex) Rename `import pandas as alias` to `import pandas as pd`.
|
||||
Some(Edit::range_replacement(target.to_string(), binding.range()))
|
||||
Some(Edit::range_replacement(target.to_string(), binding.range))
|
||||
} else {
|
||||
// Ex) Rename `import pandas` to `import pandas as pd`.
|
||||
Some(Edit::range_replacement(
|
||||
format!("{name} as {target}"),
|
||||
binding.range(),
|
||||
binding.range,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -235,7 +234,7 @@ impl Renamer {
|
||||
let module_name = import.call_path.first().unwrap();
|
||||
Some(Edit::range_replacement(
|
||||
format!("{module_name} as {target}"),
|
||||
binding.range(),
|
||||
binding.range,
|
||||
))
|
||||
}
|
||||
// Avoid renaming builtins and other "special" bindings.
|
||||
@@ -255,7 +254,7 @@ impl Renamer {
|
||||
| BindingKind::FunctionDefinition(_)
|
||||
| BindingKind::Deletion
|
||||
| BindingKind::UnboundException(_) => {
|
||||
Some(Edit::range_replacement(target.to_string(), binding.range()))
|
||||
Some(Edit::range_replacement(target.to_string(), binding.range))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ pub(crate) fn variable_name_task_id(
|
||||
// If the keyword argument is not a string, we can't do anything.
|
||||
let task_id = match &keyword.value {
|
||||
Expr::Constant(constant) => match &constant.value {
|
||||
Constant::Str(ast::StringConstant { value, .. }) => value,
|
||||
Constant::Str(value) => value,
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
use anyhow::{bail, Result};
|
||||
use ruff_python_ast::{PySourceType, Ranged};
|
||||
use ruff_python_ast::{PySourceType, Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
/// ANN204
|
||||
pub(crate) fn add_return_annotation<T: Ranged>(
|
||||
statement: &T,
|
||||
pub(crate) fn add_return_annotation(
|
||||
locator: &Locator,
|
||||
stmt: &Stmt,
|
||||
annotation: &str,
|
||||
source_type: PySourceType,
|
||||
locator: &Locator,
|
||||
) -> Result<Edit> {
|
||||
let contents = &locator.contents()[statement.range()];
|
||||
let contents = &locator.contents()[stmt.range()];
|
||||
|
||||
// Find the colon (following the `def` keyword).
|
||||
let mut seen_lpar = false;
|
||||
let mut seen_rpar = false;
|
||||
let mut count = 0u32;
|
||||
for (tok, range) in
|
||||
lexer::lex_starts_at(contents, source_type.as_mode(), statement.start()).flatten()
|
||||
lexer::lex_starts_at(contents, source_type.as_mode(), stmt.start()).flatten()
|
||||
{
|
||||
if seen_lpar && seen_rpar {
|
||||
if matches!(tok, Tok::Colon) {
|
||||
|
||||
@@ -1,11 +1,53 @@
|
||||
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::{Definition, SemanticModel};
|
||||
use ruff_python_semantic::{Definition, Member, MemberKind, 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.
|
||||
pub(crate) fn overloaded_name(definition: &Definition, semantic: &SemanticModel) -> Option<String> {
|
||||
let function = definition.as_function_def()?;
|
||||
if visibility::is_overload(&function.decorator_list, semantic) {
|
||||
Some(function.name.to_string())
|
||||
if let Definition::Member(Member {
|
||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||
stmt,
|
||||
..
|
||||
}) = definition
|
||||
{
|
||||
if visibility::is_overload(cast::decorator_list(stmt), semantic) {
|
||||
let (name, ..) = match_function_def(stmt);
|
||||
Some(name.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -18,12 +60,19 @@ pub(crate) fn is_overload_impl(
|
||||
overloaded_name: &str,
|
||||
semantic: &SemanticModel,
|
||||
) -> bool {
|
||||
let Some(function) = definition.as_function_def() else {
|
||||
return false;
|
||||
};
|
||||
if visibility::is_overload(&function.decorator_list, semantic) {
|
||||
false
|
||||
if let Definition::Member(Member {
|
||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||
stmt,
|
||||
..
|
||||
}) = definition
|
||||
{
|
||||
if visibility::is_overload(cast::decorator_list(stmt), semantic) {
|
||||
false
|
||||
} else {
|
||||
let (name, ..) = match_function_def(stmt);
|
||||
name == overloaded_name
|
||||
}
|
||||
} else {
|
||||
function.name.as_str() == overloaded_name
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, ParameterWithDefault, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::cast;
|
||||
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
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_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::Definition;
|
||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
||||
use ruff_python_stdlib::typing::simple_magic_return_type;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::flake8_annotations::fixes;
|
||||
use crate::rules::ruff::typing::type_hint_resolves_to_any;
|
||||
|
||||
use super::super::fixes;
|
||||
use super::super::helpers::match_function_def;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that function arguments have type annotations.
|
||||
///
|
||||
@@ -494,23 +498,20 @@ pub(crate) fn definition(
|
||||
definition: &Definition,
|
||||
visibility: visibility::Visibility,
|
||||
) -> Vec<Diagnostic> {
|
||||
let Some(function) = definition.as_function_def() else {
|
||||
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
||||
// 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![];
|
||||
};
|
||||
|
||||
let ast::StmtFunctionDef {
|
||||
range: _,
|
||||
is_async: _,
|
||||
decorator_list,
|
||||
name,
|
||||
type_params: _,
|
||||
parameters,
|
||||
returns,
|
||||
body,
|
||||
} = function;
|
||||
|
||||
let is_method = definition.is_method();
|
||||
let is_method = match kind {
|
||||
MemberKind::Method => true,
|
||||
MemberKind::Function | MemberKind::NestedFunction => false,
|
||||
_ => return vec![],
|
||||
};
|
||||
|
||||
let (name, arguments, returns, body, decorator_list) = match_function_def(stmt);
|
||||
// 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_typed_return = false; // Return value has been typed?
|
||||
@@ -527,19 +528,20 @@ pub(crate) fn definition(
|
||||
parameter,
|
||||
default: _,
|
||||
range: _,
|
||||
} in parameters
|
||||
} in arguments
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
.chain(&arguments.args)
|
||||
.chain(&arguments.kwonlyargs)
|
||||
.skip(
|
||||
// If this is a non-static method, skip `cls` or `self`.
|
||||
usize::from(
|
||||
is_method && !visibility::is_staticmethod(decorator_list, checker.semantic()),
|
||||
is_method
|
||||
&& !visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic()),
|
||||
),
|
||||
)
|
||||
{
|
||||
// ANN401 for dynamically typed parameters
|
||||
// ANN401 for dynamically typed arguments
|
||||
if let Some(annotation) = ¶meter.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||
@@ -570,7 +572,7 @@ pub(crate) fn definition(
|
||||
}
|
||||
|
||||
// ANN002, ANN401
|
||||
if let Some(arg) = ¶meters.vararg {
|
||||
if let Some(arg) = &arguments.vararg {
|
||||
if let Some(expr) = &arg.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
@@ -596,7 +598,7 @@ pub(crate) fn definition(
|
||||
}
|
||||
|
||||
// ANN003, ANN401
|
||||
if let Some(arg) = ¶meters.kwarg {
|
||||
if let Some(arg) = &arguments.kwarg {
|
||||
if let Some(expr) = &arg.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
@@ -627,18 +629,18 @@ pub(crate) fn definition(
|
||||
}
|
||||
|
||||
// ANN101, ANN102
|
||||
if is_method && !visibility::is_staticmethod(decorator_list, checker.semantic()) {
|
||||
if is_method && !visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic()) {
|
||||
if let Some(ParameterWithDefault {
|
||||
parameter,
|
||||
default: _,
|
||||
range: _,
|
||||
}) = parameters
|
||||
}) = arguments
|
||||
.posonlyargs
|
||||
.first()
|
||||
.or_else(|| parameters.args.first())
|
||||
.or_else(|| arguments.args.first())
|
||||
{
|
||||
if parameter.annotation.is_none() {
|
||||
if visibility::is_classmethod(decorator_list, checker.semantic()) {
|
||||
if visibility::is_classmethod(cast::decorator_list(stmt), checker.semantic()) {
|
||||
if checker.enabled(Rule::MissingTypeCls) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeCls {
|
||||
@@ -674,22 +676,24 @@ pub(crate) fn definition(
|
||||
// (explicitly or implicitly).
|
||||
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
|
||||
) {
|
||||
if is_method && visibility::is_classmethod(decorator_list, checker.semantic()) {
|
||||
if is_method && visibility::is_classmethod(cast::decorator_list(stmt), checker.semantic()) {
|
||||
if checker.enabled(Rule::MissingReturnTypeClassMethod) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypeClassMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
} else if is_method && visibility::is_staticmethod(decorator_list, checker.semantic()) {
|
||||
} else if is_method
|
||||
&& visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic())
|
||||
{
|
||||
if checker.enabled(Rule::MissingReturnTypeStaticMethod) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypeStaticMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
} else if is_method && visibility::is_init(name) {
|
||||
@@ -701,15 +705,15 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::add_return_annotation(
|
||||
function,
|
||||
checker.locator(),
|
||||
stmt,
|
||||
"None",
|
||||
checker.source_type,
|
||||
checker.locator(),
|
||||
)
|
||||
.map(Fix::suggested)
|
||||
});
|
||||
@@ -723,16 +727,16 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(return_type) = simple_magic_return_type(name) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::add_return_annotation(
|
||||
function,
|
||||
checker.locator(),
|
||||
stmt,
|
||||
return_type,
|
||||
checker.source_type,
|
||||
checker.locator(),
|
||||
)
|
||||
.map(Fix::suggested)
|
||||
});
|
||||
@@ -748,7 +752,7 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeUndocumentedPublicFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -758,7 +762,7 @@ pub(crate) fn definition(
|
||||
MissingReturnTypePrivateFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,21 +112,21 @@ fn py_stat(call_path: &CallPath) -> Option<u16> {
|
||||
}
|
||||
}
|
||||
|
||||
fn int_value(expr: &Expr, semantic: &SemanticModel) -> Option<u16> {
|
||||
fn int_value(expr: &Expr, model: &SemanticModel) -> Option<u16> {
|
||||
match expr {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) => value.to_u16(),
|
||||
Expr::Attribute(_) => semantic.resolve_call_path(expr).as_ref().and_then(py_stat),
|
||||
Expr::Attribute(_) => model.resolve_call_path(expr).as_ref().and_then(py_stat),
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
left,
|
||||
op,
|
||||
right,
|
||||
range: _,
|
||||
}) => {
|
||||
let left_value = int_value(left, semantic)?;
|
||||
let right_value = int_value(right, semantic)?;
|
||||
let left_value = int_value(left, model)?;
|
||||
let right_value = int_value(right, model)?;
|
||||
match op {
|
||||
Operator::BitAnd => 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)
|
||||
}
|
||||
|
||||
fn matches_string_format_expression(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
fn matches_string_format_expression(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
match expr {
|
||||
// "select * from table where val = " + "str" + ...
|
||||
// "select * from table where val = %s" % ...
|
||||
@@ -62,7 +62,7 @@ fn matches_string_format_expression(expr: &Expr, semantic: &SemanticModel) -> bo
|
||||
..
|
||||
}) => {
|
||||
// Only evaluate the full BinOp, not the nested components.
|
||||
if semantic
|
||||
if model
|
||||
.current_expression_parent()
|
||||
.map_or(true, |parent| !parent.is_bin_op_expr())
|
||||
{
|
||||
@@ -80,7 +80,7 @@ fn matches_string_format_expression(expr: &Expr, semantic: &SemanticModel) -> bo
|
||||
attr == "format" && string_literal(value).is_some()
|
||||
}
|
||||
// f"select * from table where val = {val}"
|
||||
Expr::FString(_) => true,
|
||||
Expr::JoinedStr(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged};
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of hardcoded temporary file or directory paths.
|
||||
///
|
||||
@@ -51,33 +49,19 @@ impl Violation for HardcodedTempFile {
|
||||
}
|
||||
|
||||
/// S108
|
||||
pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, expr: &Expr, value: &str) {
|
||||
if !checker
|
||||
.settings
|
||||
.flake8_bandit
|
||||
.hardcoded_tmp_directory
|
||||
.iter()
|
||||
.any(|prefix| value.starts_with(prefix))
|
||||
{
|
||||
return;
|
||||
pub(crate) fn hardcoded_tmp_directory(
|
||||
expr: &Expr,
|
||||
value: &str,
|
||||
prefixes: &[String],
|
||||
) -> Option<Diagnostic> {
|
||||
if prefixes.iter().any(|prefix| value.starts_with(prefix)) {
|
||||
Some(Diagnostic::new(
|
||||
HardcodedTempFile {
|
||||
string: value.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
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 {
|
||||
string: value.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ impl Violation for SuspiciousPickleUsage {
|
||||
/// import marshal
|
||||
///
|
||||
/// with open("foo.marshal", "rb") as file:
|
||||
/// foo = marshal.load(file)
|
||||
/// foo = pickle.load(file)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// Returns `true` if a function call is allowed to use a boolean trap.
|
||||
pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
||||
@@ -58,13 +62,18 @@ pub(super) fn allow_boolean_trap(func: &Expr) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns `true` if an expression is a boolean literal.
|
||||
pub(super) const fn is_boolean(expr: &Expr) -> bool {
|
||||
const fn is_boolean_arg(arg: &Expr) -> bool {
|
||||
matches!(
|
||||
&expr,
|
||||
&arg,
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
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::{assert_messages, settings};
|
||||
|
||||
#[test_case(Rule::BooleanTypeHintPositionalArgument, Path::new("FBT.py"))]
|
||||
#[test_case(Rule::BooleanDefaultValuePositionalArgument, Path::new("FBT.py"))]
|
||||
#[test_case(Rule::BooleanPositionalValueInCall, Path::new("FBT.py"))]
|
||||
#[test_case(Rule::BooleanPositionalArgInFunctionDefinition, Path::new("FBT.py"))]
|
||||
#[test_case(Rule::BooleanDefaultValueInFunctionDefinition, Path::new("FBT.py"))]
|
||||
#[test_case(Rule::BooleanPositionalValueInFunctionCall, Path::new("FBT.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
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,9 +1,11 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_python_ast::Expr;
|
||||
|
||||
use ruff_diagnostics::Violation;
|
||||
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::{allow_boolean_trap, is_boolean};
|
||||
use crate::rules::flake8_boolean_trap::helpers::{add_if_boolean, allow_boolean_trap};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for boolean positional arguments in function calls.
|
||||
@@ -15,42 +17,44 @@ use crate::rules::flake8_boolean_trap::helpers::{allow_boolean_trap, is_boolean}
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def func(flag: bool) -> None:
|
||||
/// def foo(flag: bool) -> None:
|
||||
/// ...
|
||||
///
|
||||
///
|
||||
/// func(True)
|
||||
/// foo(True)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def func(flag: bool) -> None:
|
||||
/// def foo(flag: bool) -> None:
|
||||
/// ...
|
||||
///
|
||||
///
|
||||
/// func(flag=True)
|
||||
/// foo(flag=True)
|
||||
/// ```
|
||||
///
|
||||
/// ## 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 BooleanPositionalValueInCall;
|
||||
pub struct BooleanPositionalValueInFunctionCall;
|
||||
|
||||
impl Violation for BooleanPositionalValueInCall {
|
||||
impl Violation for BooleanPositionalValueInFunctionCall {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Boolean positional value in function call")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn boolean_positional_value_in_call(checker: &mut Checker, args: &[Expr], func: &Expr) {
|
||||
pub(crate) fn check_boolean_positional_value_in_function_call(
|
||||
checker: &mut Checker,
|
||||
args: &[Expr],
|
||||
func: &Expr,
|
||||
) {
|
||||
if allow_boolean_trap(func) {
|
||||
return;
|
||||
}
|
||||
for arg in args.iter().filter(|arg| is_boolean(arg)) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(BooleanPositionalValueInCall, arg.range()));
|
||||
for arg in args {
|
||||
add_if_boolean(checker, arg, BooleanPositionalValueInFunctionCall.into());
|
||||
}
|
||||
}
|
||||
@@ -11,22 +11,16 @@ use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of boolean positional arguments in function definitions,
|
||||
/// as determined by the presence of a `bool` type hint.
|
||||
/// Checks for boolean positional arguments in function definitions.
|
||||
///
|
||||
/// ## 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
|
||||
/// 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
|
||||
@@ -71,33 +65,20 @@ 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
|
||||
/// - [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 BooleanTypeHintPositionalArgument;
|
||||
pub struct BooleanPositionalArgInFunctionDefinition;
|
||||
|
||||
impl Violation for BooleanTypeHintPositionalArgument {
|
||||
impl Violation for BooleanPositionalArgInFunctionDefinition {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Boolean-typed positional argument in function definition")
|
||||
format!("Boolean positional arg in function definition")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn boolean_type_hint_positional_argument(
|
||||
pub(crate) fn check_positional_boolean_in_def(
|
||||
checker: &mut Checker,
|
||||
name: &str,
|
||||
decorator_list: &[Decorator],
|
||||
@@ -120,25 +101,28 @@ pub(crate) fn boolean_type_hint_positional_argument(
|
||||
range: _,
|
||||
} in parameters.posonlyargs.iter().chain(¶meters.args)
|
||||
{
|
||||
let Some(annotation) = parameter.annotation.as_ref() else {
|
||||
if parameter.annotation.is_none() {
|
||||
continue;
|
||||
}
|
||||
let Some(expr) = ¶meter.annotation else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// check for both bool (python class) and 'bool' (string annotation)
|
||||
let hint = match annotation.as_ref() {
|
||||
let hint = match expr.as_ref() {
|
||||
Expr::Name(name) => &name.id == "bool",
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
}) => value == "bool",
|
||||
_ => false,
|
||||
};
|
||||
if !hint || !checker.semantic().is_builtin("bool") {
|
||||
if !hint {
|
||||
continue;
|
||||
}
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BooleanTypeHintPositionalArgument,
|
||||
parameter.name.range(),
|
||||
BooleanPositionalArgInFunctionDefinition,
|
||||
parameter.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
pub(crate) use boolean_default_value_positional_argument::*;
|
||||
pub(crate) use boolean_positional_value_in_call::*;
|
||||
pub(crate) use boolean_type_hint_positional_argument::*;
|
||||
pub(crate) use check_boolean_default_value_in_function_definition::*;
|
||||
pub(crate) use check_boolean_positional_value_in_function_call::*;
|
||||
pub(crate) use check_positional_boolean_in_def::*;
|
||||
|
||||
mod boolean_default_value_positional_argument;
|
||||
mod boolean_positional_value_in_call;
|
||||
mod boolean_type_hint_positional_argument;
|
||||
mod check_boolean_default_value_in_function_definition;
|
||||
mod check_boolean_positional_value_in_function_call;
|
||||
mod check_positional_boolean_in_def;
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_boolean_trap/mod.rs
|
||||
---
|
||||
FBT.py:4:5: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:4:5: FBT001 Boolean positional arg in function definition
|
||||
|
|
||||
2 | posonly_nohint,
|
||||
3 | posonly_nonboolhint: int,
|
||||
4 | posonly_boolhint: bool,
|
||||
| ^^^^^^^^^^^^^^^^ FBT001
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
5 | posonly_boolstrhint: "bool",
|
||||
6 | /,
|
||||
|
|
||||
|
||||
FBT.py:5:5: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:5:5: FBT001 Boolean positional arg in function definition
|
||||
|
|
||||
3 | posonly_nonboolhint: int,
|
||||
4 | posonly_boolhint: bool,
|
||||
5 | posonly_boolstrhint: "bool",
|
||||
| ^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
6 | /,
|
||||
7 | offset,
|
||||
|
|
||||
|
||||
FBT.py:10:5: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:10:5: FBT001 Boolean positional arg in function definition
|
||||
|
|
||||
8 | posorkw_nonvalued_nohint,
|
||||
9 | posorkw_nonvalued_nonboolhint: int,
|
||||
10 | posorkw_nonvalued_boolhint: bool,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
|
|
||||
|
||||
FBT.py:11:5: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:11:5: FBT001 Boolean positional arg in function definition
|
||||
|
|
||||
9 | posorkw_nonvalued_nonboolhint: int,
|
||||
10 | posorkw_nonvalued_boolhint: bool,
|
||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
|
|
||||
|
||||
FBT.py:14:5: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:14:5: FBT001 Boolean positional arg in function definition
|
||||
|
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
|
|
||||
|
||||
FBT.py:15:5: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:15:5: FBT001 Boolean positional arg in function definition
|
||||
|
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
|
|
||||
|
||||
FBT.py:18:5: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:18:5: FBT001 Boolean positional arg in function definition
|
||||
|
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
18 | posorkw_nonboolvalued_boolhint: bool = 3,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
||||
20 | *,
|
||||
|
|
||||
|
||||
FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:19:5: FBT001 Boolean positional arg in function definition
|
||||
|
|
||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
18 | posorkw_nonboolvalued_boolhint: bool = 3,
|
||||
19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
20 | *,
|
||||
21 | kwonly_nonvalued_nohint,
|
||||
|
|
||||
|
||||
FBT.py:86:19: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:86:19: FBT001 Boolean positional arg in function definition
|
||||
|
|
||||
85 | # FBT001: Boolean positional arg in function definition
|
||||
86 | def foo(self, value: bool) -> None:
|
||||
| ^^^^^ FBT001
|
||||
| ^^^^^^^^^^^ FBT001
|
||||
87 | pass
|
||||
|
|
||||
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_boolean_trap/mod.rs
|
||||
---
|
||||
FBT.py:12:5: FBT002 Boolean default positional argument in function definition
|
||||
FBT.py:12:31: FBT002 Boolean default value in function definition
|
||||
|
|
||||
10 | posorkw_nonvalued_boolhint: bool,
|
||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ FBT002
|
||||
| ^^^^ FBT002
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||
|
|
||||
|
||||
FBT.py:13:5: FBT002 Boolean default positional argument in function definition
|
||||
FBT.py:13:43: FBT002 Boolean default value in function definition
|
||||
|
|
||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT002
|
||||
| ^^^^ FBT002
|
||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||
|
|
||||
|
||||
FBT.py:14:5: FBT002 Boolean default positional argument in function definition
|
||||
FBT.py:14:41: FBT002 Boolean default value in function definition
|
||||
|
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT002
|
||||
| ^^^^ FBT002
|
||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
|
|
||||
|
||||
FBT.py:15:5: FBT002 Boolean default positional argument in function definition
|
||||
FBT.py:15:46: FBT002 Boolean default value in function definition
|
||||
|
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT002
|
||||
| ^^^^ FBT002
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
|
|
||||
|
||||
@@ -49,6 +49,7 @@ mod tests {
|
||||
#[test_case(Rule::UselessComparison, Path::new("B015.py"))]
|
||||
#[test_case(Rule::UselessContextlibSuppress, Path::new("B022.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<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
@@ -59,17 +60,6 @@ mod tests {
|
||||
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]
|
||||
fn extend_immutable_calls() -> Result<()> {
|
||||
let snapshot = "extend_immutable_calls".to_string();
|
||||
@@ -82,7 +72,7 @@ mod tests {
|
||||
"fastapi.Query".to_string(),
|
||||
],
|
||||
},
|
||||
..Settings::for_rule(Rule::FunctionCallInDefaultArgument)
|
||||
..Settings::for_rules(vec![Rule::FunctionCallInDefaultArgument])
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
|
||||
@@ -159,12 +159,18 @@ pub(crate) fn abstract_base_class(
|
||||
continue;
|
||||
}
|
||||
|
||||
let Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
let (Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
name: method_name,
|
||||
..
|
||||
}) = stmt
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
name: method_name,
|
||||
..
|
||||
})) = stmt
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -150,9 +150,7 @@ fn duplicate_handler_exceptions<'a>(
|
||||
if unique_elts.len() == 1 {
|
||||
checker.generator().expr(unique_elts[0])
|
||||
} else {
|
||||
// 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)))
|
||||
checker.generator().expr(&type_pattern(unique_elts))
|
||||
},
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
if !value.is_f_string_expr() {
|
||||
if !value.is_joined_str_expr() {
|
||||
return;
|
||||
}
|
||||
checker
|
||||
|
||||
@@ -67,9 +67,9 @@ struct ArgumentDefaultVisitor<'a> {
|
||||
}
|
||||
|
||||
impl<'a> ArgumentDefaultVisitor<'a> {
|
||||
fn new(semantic: &'a SemanticModel<'a>, extend_immutable_calls: Vec<CallPath<'a>>) -> Self {
|
||||
fn new(model: &'a SemanticModel<'a>, extend_immutable_calls: Vec<CallPath<'a>>) -> Self {
|
||||
Self {
|
||||
semantic,
|
||||
semantic: model,
|
||||
extend_immutable_calls,
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
|
||||
@@ -86,6 +86,9 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
parameters, body, ..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
parameters, body, ..
|
||||
}) => {
|
||||
// Collect all loaded variable names.
|
||||
let mut visitor = LoadedNamesVisitor::default();
|
||||
@@ -233,7 +236,7 @@ struct AssignedNamesVisitor<'a> {
|
||||
/// `Visitor` to collect all used identifiers in a statement.
|
||||
impl<'a> Visitor<'a> for AssignedNamesVisitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
if stmt.is_function_def_stmt() {
|
||||
if matches!(stmt, Stmt::FunctionDef(_) | Stmt::AsyncFunctionDef(_)) {
|
||||
// Don't recurse.
|
||||
return;
|
||||
}
|
||||
@@ -248,7 +251,8 @@ impl<'a> Visitor<'a> for AssignedNamesVisitor<'a> {
|
||||
}
|
||||
Stmt::AugAssign(ast::StmtAugAssign { 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();
|
||||
visitor.visit_expr(target);
|
||||
self.names.extend(visitor.names);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user