Compare commits
123 Commits
v0.0.283
...
evanritten
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e268c91ab | ||
|
|
dbe62cc741 | ||
|
|
e38e8c0a51 | ||
|
|
036035bc50 | ||
|
|
97ae9e7433 | ||
|
|
98b9f2e705 | ||
|
|
6253d8e2c8 | ||
|
|
0a5be74be3 | ||
|
|
fdbb2fbdba | ||
|
|
d0b8e4f701 | ||
|
|
12f3c4c931 | ||
|
|
7ee2ae8395 | ||
|
|
95f78821ad | ||
|
|
daac31d2b9 | ||
|
|
86ccdcc9d9 | ||
|
|
e28858bb29 | ||
|
|
2d86e78bfc | ||
|
|
d9a81f4fbb | ||
|
|
897cce83b3 | ||
|
|
9bf6713b76 | ||
|
|
3f1658a25b | ||
|
|
097db2fcce | ||
|
|
a3d4f08f29 | ||
|
|
29c0b9f91c | ||
|
|
81b1176f99 | ||
|
|
b1c4c7be69 | ||
|
|
84d178a219 | ||
|
|
455db84a59 | ||
|
|
232b44a8ca | ||
|
|
e1e213decf | ||
|
|
5f709cd3e0 | ||
|
|
17e7eae2f9 | ||
|
|
7f7df852e8 | ||
|
|
9a0d2f5afd | ||
|
|
ebda5fcd99 | ||
|
|
b1870b2b16 | ||
|
|
a51d1ac980 | ||
|
|
1a52b548e7 | ||
|
|
a3bf6d9cb7 | ||
|
|
70696061cd | ||
|
|
cd634a9489 | ||
|
|
5ddf143cae | ||
|
|
46862473b9 | ||
|
|
96d310fbab | ||
|
|
09c8b17661 | ||
|
|
278a4f6e14 | ||
|
|
c3a9151eb5 | ||
|
|
3711f8ad59 | ||
|
|
a7cf8f0b77 | ||
|
|
40407dcce5 | ||
|
|
f16e780e0a | ||
|
|
fc0c9507d0 | ||
|
|
680d171ae5 | ||
|
|
01eceaf0dc | ||
|
|
910dbbd9b6 | ||
|
|
9584f613b9 | ||
|
|
c39bcbadff | ||
|
|
24f42f0894 | ||
|
|
51ae47ad56 | ||
|
|
1a9536c4e2 | ||
|
|
bf4c6473c8 | ||
|
|
768686148f | ||
|
|
eb24f5a0b9 | ||
|
|
446ceed1ad | ||
|
|
8660e5057c | ||
|
|
808e09180e | ||
|
|
dbf003fde4 | ||
|
|
010293ddcc | ||
|
|
4974964ad3 | ||
|
|
b49c80f8c8 | ||
|
|
c03e2acadb | ||
|
|
a1da9da0ef | ||
|
|
c6ad364d8b | ||
|
|
5b47350c25 | ||
|
|
e91caea490 | ||
|
|
53246b725e | ||
|
|
d616c9b870 | ||
|
|
7c4aa3948b | ||
|
|
b6d786fb10 | ||
|
|
0c9ded9d84 | ||
|
|
c434bdd2bd | ||
|
|
8b24238d19 | ||
|
|
f2939c678b | ||
|
|
b05574babd | ||
|
|
0ef6af807b | ||
|
|
f091b46497 | ||
|
|
2cedb401bd | ||
|
|
2e5c81b202 | ||
|
|
a12a71a845 | ||
|
|
cc151c35a8 | ||
|
|
563374503f | ||
|
|
95dea5c868 | ||
|
|
eb68addf97 | ||
|
|
9ff80a82b4 | ||
|
|
84ae00c395 | ||
|
|
1050c4e104 | ||
|
|
6706ae4828 | ||
|
|
dc3275fe7f | ||
|
|
50dab9cea6 | ||
|
|
4811af0f0b | ||
|
|
39beeb61f7 | ||
|
|
e2f7862404 | ||
|
|
ac5c8bb3b6 | ||
|
|
c1bc67686c | ||
|
|
7eea0e94a2 | ||
|
|
0252995973 | ||
|
|
627f475b91 | ||
|
|
395bb31247 | ||
|
|
3ecd263b4d | ||
|
|
6acf07c5c4 | ||
|
|
38b9fb8bbd | ||
|
|
e4f57434a2 | ||
|
|
6a64f2289b | ||
|
|
3bf1c66cda | ||
|
|
eaada0345c | ||
|
|
a39dd76d95 | ||
|
|
e257c5af32 | ||
|
|
887a47cad9 | ||
|
|
a2758513de | ||
|
|
1b9fed8397 | ||
|
|
55d6fd53cd | ||
|
|
d33618062e | ||
|
|
c7703e205d |
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -328,7 +328,7 @@ jobs:
|
||||
name: "Formatter ecosystem and progress checks"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.formatter == 'true'
|
||||
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -338,6 +338,6 @@ jobs:
|
||||
- name: "Formatter progress"
|
||||
run: scripts/formatter_ecosystem_checks.sh
|
||||
- name: "Github step summary"
|
||||
run: grep "similarity index" target/progress_projects_log.txt | sort > $GITHUB_STEP_SUMMARY
|
||||
run: cat target/progress_projects_stats.txt > $GITHUB_STEP_SUMMARY
|
||||
- name: "Remove checkouts from cache"
|
||||
run: rm -r target/progress_projects
|
||||
|
||||
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@2.0.0
|
||||
uses: cloudflare/wrangler-action@3.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@2.0.0
|
||||
uses: cloudflare/wrangler-action@3.0.0
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.283
|
||||
## 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))
|
||||
|
||||
@@ -8,6 +8,8 @@ Previously, when a target Python version was not specified, Ruff would use a def
|
||||
|
||||
(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))
|
||||
|
||||
@@ -571,7 +571,7 @@ An alternative is to convert the perf data to `flamegraph.svg` using
|
||||
[flamegraph](https://github.com/flamegraph-rs/flamegraph) (`cargo install flamegraph`):
|
||||
|
||||
```shell
|
||||
flamegraph --perfdata perf.data
|
||||
flamegraph --perfdata perf.data --no-inline
|
||||
```
|
||||
|
||||
#### Mac
|
||||
|
||||
33
Cargo.lock
generated
33
Cargo.lock
generated
@@ -14,6 +14,18 @@ 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"
|
||||
@@ -800,7 +812,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -991,6 +1003,16 @@ 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"
|
||||
@@ -2042,7 +2064,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2119,6 +2141,7 @@ dependencies = [
|
||||
"ruff",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_index",
|
||||
"ruff_python_parser",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2141,7 +2164,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2197,6 +2220,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"ignore",
|
||||
"imara-diff",
|
||||
"indicatif",
|
||||
"indoc",
|
||||
"itertools",
|
||||
@@ -2330,6 +2354,7 @@ dependencies = [
|
||||
"similar",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2353,7 +2378,6 @@ dependencies = [
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"lexical-parse-float",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"unic-ucd-category",
|
||||
@@ -2400,6 +2424,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"ruff_index",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
|
||||
@@ -49,6 +49,7 @@ 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
|
||||
|
||||
@@ -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 [500 built-in rules](https://beta.ruff.rs/docs/rules/)
|
||||
- 📏 Over [600 built-in rules](https://beta.ruff.rs/docs/rules/)
|
||||
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
|
||||
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.283
|
||||
rev: v0.0.284
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -233,7 +233,7 @@ linting command.
|
||||
|
||||
<!-- Begin section: Rules -->
|
||||
|
||||
**Ruff supports over 500 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
**Ruff supports over 600 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
|
||||
Rust as a first-party feature.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -62,8 +62,6 @@ 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 }
|
||||
@@ -77,7 +75,7 @@ strum_macros = { workspace = true }
|
||||
thiserror = { version = "1.0.43" }
|
||||
toml = { workspace = true }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unicode-width = { version = "0.1.10" }
|
||||
unicode-width = { workspace = true }
|
||||
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
|
||||
@@ -14,3 +14,19 @@ 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,6 +68,20 @@ def this_is_also_wrong(value={}):
|
||||
...
|
||||
|
||||
|
||||
class Foo:
|
||||
@staticmethod
|
||||
def this_is_also_wrong_and_more_indented(value={}):
|
||||
pass
|
||||
|
||||
|
||||
def multiline_arg_wrong(value={
|
||||
|
||||
}):
|
||||
...
|
||||
|
||||
def single_line_func_wrong(value = {}): ...
|
||||
|
||||
|
||||
def and_this(value=set()):
|
||||
...
|
||||
|
||||
@@ -261,3 +275,32 @@ 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"""
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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]
|
||||
@@ -9,7 +8,9 @@ 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]
|
||||
@@ -17,4 +18,14 @@ y: builtins.type[int, str, complex]
|
||||
z: Union[float, complex]
|
||||
|
||||
|
||||
def func(arg: type[int, float] | str) -> None: ...
|
||||
def func(arg: type[int, float] | str) -> None:
|
||||
...
|
||||
|
||||
|
||||
# OK
|
||||
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||
|
||||
|
||||
def func():
|
||||
# PYI055
|
||||
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import builtins
|
||||
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
|
||||
@@ -16,5 +14,11 @@ 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,3 +80,15 @@ 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
|
||||
|
||||
26
crates/ruff/resources/test/fixtures/flake8_pytest_style/PT014.py
vendored
Normal file
26
crates/ruff/resources/test/fixtures/flake8_pytest_style/PT014.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [1, 1, 2])
|
||||
def test_error_literal(x):
|
||||
...
|
||||
|
||||
|
||||
a = 1
|
||||
b = 2
|
||||
c = 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [a, a, b, b, b, c])
|
||||
def test_error_expr_simple(x):
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [(a, b), (a, b), (b, c)])
|
||||
def test_error_expr_complex(x):
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [1, 2])
|
||||
def test_ok(x):
|
||||
...
|
||||
48
crates/ruff/resources/test/fixtures/flake8_pytest_style/PT027_0.py
vendored
Normal file
48
crates/ruff/resources/test/fixtures/flake8_pytest_style/PT027_0.py
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import unittest
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
def test_errors(self):
|
||||
with self.assertRaises(ValueError):
|
||||
raise ValueError
|
||||
with self.assertRaises(expected_exception=ValueError):
|
||||
raise ValueError
|
||||
|
||||
with self.failUnlessRaises(ValueError):
|
||||
raise ValueError
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "test"):
|
||||
raise ValueError("test")
|
||||
|
||||
with self.assertRaisesRegex(ValueError, expected_regex="test"):
|
||||
raise ValueError("test")
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
expected_exception=ValueError, expected_regex="test"
|
||||
):
|
||||
raise ValueError("test")
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
expected_regex="test", expected_exception=ValueError
|
||||
):
|
||||
raise ValueError("test")
|
||||
|
||||
with self.assertRaisesRegexp(ValueError, "test"):
|
||||
raise ValueError("test")
|
||||
|
||||
def test_unfixable_errors(self):
|
||||
with self.assertRaises(ValueError, msg="msg"):
|
||||
raise ValueError
|
||||
|
||||
with self.assertRaises(
|
||||
# comment
|
||||
ValueError
|
||||
):
|
||||
raise ValueError
|
||||
|
||||
with (
|
||||
self
|
||||
# comment
|
||||
.assertRaises(ValueError)
|
||||
):
|
||||
raise ValueError
|
||||
12
crates/ruff/resources/test/fixtures/flake8_pytest_style/PT027_1.py
vendored
Normal file
12
crates/ruff/resources/test/fixtures/flake8_pytest_style/PT027_1.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import unittest
|
||||
import pytest
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
def test_pytest_raises(self):
|
||||
with pytest.raises(ValueError):
|
||||
raise ValueError
|
||||
|
||||
def test_errors(self):
|
||||
with self.assertRaises(ValueError):
|
||||
raise ValueError
|
||||
@@ -73,3 +73,7 @@ print(foo.__dict__)
|
||||
print(foo.__str__())
|
||||
print(foo().__class__)
|
||||
print(foo._asdict())
|
||||
|
||||
import os
|
||||
|
||||
os._exit()
|
||||
|
||||
31
crates/ruff/resources/test/fixtures/flake8_tidy_imports/TID253.py
vendored
Normal file
31
crates/ruff/resources/test/fixtures/flake8_tidy_imports/TID253.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
## Banned modules ##
|
||||
import torch
|
||||
|
||||
from torch import *
|
||||
|
||||
from tensorflow import a, b, c
|
||||
|
||||
import torch as torch_wearing_a_trenchcoat
|
||||
|
||||
# this should count as module level
|
||||
x = 1; import tensorflow
|
||||
|
||||
# banning a module also bans any submodules
|
||||
import torch.foo.bar
|
||||
|
||||
from tensorflow.foo import bar
|
||||
|
||||
from torch.foo.bar import *
|
||||
|
||||
# unlike TID251, inline imports are *not* banned
|
||||
def my_cool_function():
|
||||
import tensorflow.foo.bar
|
||||
|
||||
def another_cool_function():
|
||||
from torch.foo import bar
|
||||
|
||||
def import_alias():
|
||||
from torch.foo import bar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import torch
|
||||
12
crates/ruff/resources/test/fixtures/flake8_type_checking/runtime_evaluated_base_classes_4.py
vendored
Normal file
12
crates/ruff/resources/test/fixtures/flake8_type_checking/runtime_evaluated_base_classes_4.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
|
||||
|
||||
class Birthday(DeclarativeBase):
|
||||
|
||||
__tablename__ = "birthday"
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
day: Mapped[date]
|
||||
@@ -202,3 +202,14 @@ class C:
|
||||
###
|
||||
def f(x: None) -> None:
|
||||
_ = cast(Any, _identity)(x=x)
|
||||
|
||||
###
|
||||
# Unused arguments with `locals`.
|
||||
###
|
||||
def f(bar: str):
|
||||
print(locals())
|
||||
|
||||
|
||||
class C:
|
||||
def __init__(self, x) -> None:
|
||||
print(locals())
|
||||
|
||||
6
crates/ruff/resources/test/fixtures/isort/required_imports/comments_and_newlines.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/isort/required_imports/comments_and_newlines.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# A copyright notice could go here
|
||||
|
||||
# A linter directive could go here
|
||||
|
||||
x = 1
|
||||
@@ -21,3 +21,29 @@ while i < 10:
|
||||
print("error")
|
||||
|
||||
i += 1
|
||||
|
||||
# OK - no other way to write this
|
||||
for i in range(10):
|
||||
try:
|
||||
print(f"{i}")
|
||||
break
|
||||
except:
|
||||
print("error")
|
||||
|
||||
# OK - no other way to write this
|
||||
for i in range(10):
|
||||
try:
|
||||
print(f"{i}")
|
||||
continue
|
||||
except:
|
||||
print("error")
|
||||
|
||||
|
||||
# OK - no other way to write this
|
||||
for i in range(10):
|
||||
try:
|
||||
print(f"{i}")
|
||||
if i > 0:
|
||||
break
|
||||
except:
|
||||
print("error")
|
||||
|
||||
@@ -30,3 +30,10 @@ def foo() -> None:
|
||||
|
||||
if __name__ == "__main__":
|
||||
import g
|
||||
|
||||
import h; import i
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import j; \
|
||||
import k
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
a = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
a = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
# aaaa
|
||||
# aaaaa
|
||||
# a
|
||||
# a
|
||||
# aa
|
||||
# aaa
|
||||
# aaaa
|
||||
# a
|
||||
# aa
|
||||
# aaa
|
||||
|
||||
b = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
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"""
|
||||
if True: # noqa: E501
|
||||
[12]
|
||||
[12 ]
|
||||
[1,2]
|
||||
[1, 2]
|
||||
|
||||
@@ -61,3 +61,30 @@ 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:
|
||||
...
|
||||
|
||||
@@ -92,3 +92,10 @@ 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,3 +70,13 @@ 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,3 +145,9 @@ def f() -> None:
|
||||
obj = Foo()
|
||||
obj.do_thing()
|
||||
|
||||
|
||||
def f():
|
||||
try:
|
||||
pass
|
||||
except Exception as _:
|
||||
pass
|
||||
|
||||
46
crates/ruff/resources/test/fixtures/pylint/bad_dunder_method_name.py
vendored
Normal file
46
crates/ruff/resources/test/fixtures/pylint/bad_dunder_method_name.py
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
class Apples:
|
||||
def _init_(self): # [bad-dunder-name]
|
||||
pass
|
||||
|
||||
def __hello__(self): # [bad-dunder-name]
|
||||
print("hello")
|
||||
|
||||
def __init_(self): # [bad-dunder-name]
|
||||
# author likely unintentionally misspelled the correct init dunder.
|
||||
pass
|
||||
|
||||
def _init_(self): # [bad-dunder-name]
|
||||
# author likely unintentionally misspelled the correct init dunder.
|
||||
pass
|
||||
|
||||
def ___neg__(self): # [bad-dunder-name]
|
||||
# author likely accidentally added an additional `_`
|
||||
pass
|
||||
|
||||
def __inv__(self): # [bad-dunder-name]
|
||||
# author likely meant to call the invert dunder method
|
||||
pass
|
||||
|
||||
def hello(self):
|
||||
print("hello")
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def init(self):
|
||||
# valid name even though someone could accidentally mean __init__
|
||||
pass
|
||||
|
||||
def _protected_method(self):
|
||||
print("Protected")
|
||||
|
||||
def __private_method(self):
|
||||
print("Private")
|
||||
|
||||
@property
|
||||
def __doc__(self):
|
||||
return "Docstring"
|
||||
|
||||
|
||||
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
|
||||
...
|
||||
@@ -13,6 +13,7 @@ print("foo %(foo)d bar %(bar)d" % {"foo": "1", "bar": "2"})
|
||||
"%(key)d" % {"key": []}
|
||||
print("%d" % ("%s" % ("nested",),))
|
||||
"%d" % ((1, 2, 3),)
|
||||
"%d" % (1 if x > 0 else [])
|
||||
|
||||
# False negatives
|
||||
WORD = "abc"
|
||||
@@ -55,3 +56,4 @@ 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,3 +10,4 @@ os.getenv("AA", "GOOD" + "BAD")
|
||||
os.getenv("AA", "GOOD" + 1)
|
||||
os.getenv("AA", "GOOD %s" % "BAD")
|
||||
os.getenv("B", Z)
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ 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,6 +19,10 @@ 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,6 +15,10 @@ 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")
|
||||
|
||||
13
crates/ruff/resources/test/fixtures/pylint/subprocess_run_without_check.py
vendored
Normal file
13
crates/ruff/resources/test/fixtures/pylint/subprocess_run_without_check.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import subprocess
|
||||
|
||||
# Errors.
|
||||
subprocess.run("ls")
|
||||
subprocess.run("ls", shell=True)
|
||||
|
||||
# Non-errors.
|
||||
subprocess.run("ls", check=True)
|
||||
subprocess.run("ls", check=False)
|
||||
subprocess.run("ls", shell=True, check=True)
|
||||
subprocess.run("ls", shell=True, check=False)
|
||||
foo.run("ls") # Not a subprocess.run call.
|
||||
subprocess.bar("ls") # Not a subprocess.run call.
|
||||
14
crates/ruff/resources/test/fixtures/ruff/RUF017.py
vendored
Normal file
14
crates/ruff/resources/test/fixtures/ruff/RUF017.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
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]])
|
||||
@@ -52,3 +52,7 @@ def good(a: int):
|
||||
def another_good(a):
|
||||
if a % 2 == 0:
|
||||
raise GoodArgCantBeEven(a)
|
||||
|
||||
|
||||
def another_good():
|
||||
raise NotImplementedError("This is acceptable too")
|
||||
|
||||
@@ -209,14 +209,7 @@ 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)
|
||||
|
||||
@@ -16,9 +16,15 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||
return;
|
||||
}
|
||||
|
||||
for binding in checker.semantic.bindings.iter() {
|
||||
for binding in &*checker.semantic.bindings {
|
||||
if checker.enabled(Rule::UnusedVariable) {
|
||||
if binding.kind.is_bound_exception() && !binding.is_used() {
|
||||
if binding.kind.is_bound_exception()
|
||||
&& !binding.is_used()
|
||||
&& !checker
|
||||
.settings
|
||||
.dummy_variable_rgx
|
||||
.is_match(binding.name(checker.locator))
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
pyflakes::rules::UnusedVariable {
|
||||
name: binding.name(checker.locator).to_string(),
|
||||
|
||||
@@ -171,7 +171,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
expr.start(),
|
||||
));
|
||||
|
||||
if pydocstyle::helpers::should_ignore_docstring(contents) {
|
||||
if pydocstyle::helpers::should_ignore_docstring(expr) {
|
||||
#[allow(deprecated)]
|
||||
let location = checker.locator.compute_source_location(expr.start());
|
||||
warn_user!(
|
||||
|
||||
@@ -261,7 +261,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::load_before_global_declaration(checker, id, expr);
|
||||
}
|
||||
}
|
||||
Expr::Attribute(ast::ExprAttribute { attr, value, .. }) => {
|
||||
Expr::Attribute(attribute) => {
|
||||
// Ex) typing.List[...]
|
||||
if checker.any_enabled(&[
|
||||
Rule::FutureRewritableTypeAnnotation,
|
||||
@@ -323,7 +323,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::CollectionsNamedTuple) {
|
||||
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
||||
}
|
||||
pandas_vet::rules::attr(checker, attr, value, expr);
|
||||
pandas_vet::rules::attr(checker, attribute);
|
||||
}
|
||||
Expr::Call(
|
||||
call @ ast::ExprCall {
|
||||
@@ -418,9 +418,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
|
||||
if checker.enabled(Rule::BadStringFormatCharacter) {
|
||||
pylint::rules::bad_string_format_character::call(
|
||||
checker,
|
||||
val.as_str(),
|
||||
location,
|
||||
checker, val, location,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -675,10 +673,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
checker, expr, func, args, keywords,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::BooleanPositionalValueInFunctionCall) {
|
||||
flake8_boolean_trap::rules::check_boolean_positional_value_in_function_call(
|
||||
checker, args, func,
|
||||
);
|
||||
if checker.enabled(Rule::BooleanPositionalValueInCall) {
|
||||
flake8_boolean_trap::rules::boolean_positional_value_in_call(checker, args, func);
|
||||
}
|
||||
if checker.enabled(Rule::Debugger) {
|
||||
flake8_debugger::rules::debugger_call(checker, expr, func);
|
||||
@@ -760,9 +756,19 @@ 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,
|
||||
@@ -867,6 +873,9 @@ 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,
|
||||
@@ -915,7 +924,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::await_outside_async(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::FString(ast::ExprFString { values, range: _ }) => {
|
||||
Expr::FString(ast::ExprFString { values, .. }) => {
|
||||
if checker.enabled(Rule::FStringMissingPlaceholders) {
|
||||
pyflakes::rules::f_string_missing_placeholders(expr, values, checker);
|
||||
}
|
||||
@@ -942,7 +951,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
range: _,
|
||||
}) => {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
..
|
||||
}) = left.as_ref()
|
||||
{
|
||||
@@ -1095,22 +1104,15 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
);
|
||||
Expr::UnaryOp(
|
||||
unary_op @ ast::ExprUnaryOp {
|
||||
op,
|
||||
operand,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[Rule::NotInTest, Rule::NotIsTest]) {
|
||||
pycodestyle::rules::not_tests(checker, unary_op);
|
||||
}
|
||||
if checker.enabled(Rule::UnaryPrefixIncrementDecrement) {
|
||||
flake8_bugbear::rules::unary_prefix_increment_decrement(
|
||||
@@ -1135,18 +1137,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
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.any_enabled(&[Rule::NoneComparison, Rule::TrueFalseComparison]) {
|
||||
pycodestyle::rules::literal_comparisons(checker, compare);
|
||||
}
|
||||
if checker.enabled(Rule::IsLiteral) {
|
||||
pyflakes::rules::invalid_literal_comparison(checker, left, ops, comparators, expr);
|
||||
@@ -1229,13 +1221,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::HardcodedTempFile) {
|
||||
if let Some(diagnostic) = flake8_bandit::rules::hardcoded_tmp_directory(
|
||||
expr,
|
||||
value,
|
||||
&checker.settings.flake8_bandit.hardcoded_tmp_directory,
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
flake8_bandit::rules::hardcoded_tmp_directory(checker, expr, value);
|
||||
}
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, kind.as_deref());
|
||||
|
||||
@@ -6,9 +6,6 @@ use crate::rules::{flake8_bugbear, flake8_pyi, ruff};
|
||||
|
||||
/// Run lint rules over a [`Parameters`] syntax node.
|
||||
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,16 +69,18 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
is_async,
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
parameters,
|
||||
body,
|
||||
type_params,
|
||||
range: _,
|
||||
}) => {
|
||||
Stmt::FunctionDef(
|
||||
function_def @ ast::StmtFunctionDef {
|
||||
is_async,
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
parameters,
|
||||
body,
|
||||
type_params,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
|
||||
flake8_django::rules::non_leading_receiver_decorator(checker, decorator_list);
|
||||
}
|
||||
@@ -204,6 +206,9 @@ 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,
|
||||
@@ -295,6 +300,7 @@ 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);
|
||||
}
|
||||
@@ -304,16 +310,16 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
]) {
|
||||
flake8_pytest_style::rules::marks(checker, decorator_list);
|
||||
}
|
||||
if checker.enabled(Rule::BooleanPositionalArgInFunctionDefinition) {
|
||||
flake8_boolean_trap::rules::check_positional_boolean_in_def(
|
||||
if checker.enabled(Rule::BooleanTypeHintPositionalArgument) {
|
||||
flake8_boolean_trap::rules::boolean_type_hint_positional_argument(
|
||||
checker,
|
||||
name,
|
||||
decorator_list,
|
||||
parameters,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::BooleanDefaultValueInFunctionDefinition) {
|
||||
flake8_boolean_trap::rules::check_boolean_default_value_in_function_definition(
|
||||
if checker.enabled(Rule::BooleanDefaultValuePositionalArgument) {
|
||||
flake8_boolean_trap::rules::boolean_default_value_positional_argument(
|
||||
checker,
|
||||
name,
|
||||
decorator_list,
|
||||
@@ -506,17 +512,16 @@ 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,
|
||||
checker.locator,
|
||||
);
|
||||
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
for name in names {
|
||||
@@ -552,12 +557,29 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BannedApi) {
|
||||
flake8_tidy_imports::rules::name_or_parent_is_banned(
|
||||
flake8_tidy_imports::rules::banned_api(
|
||||
checker,
|
||||
&alias.name,
|
||||
alias,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||
flake8_tidy_imports::matchers::MatchNameOrParent {
|
||||
module: &alias.name,
|
||||
},
|
||||
),
|
||||
&alias,
|
||||
);
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::BannedModuleLevelImports) {
|
||||
flake8_tidy_imports::rules::banned_module_level_imports(
|
||||
checker,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||
flake8_tidy_imports::matchers::MatchNameOrParent {
|
||||
module: &alias.name,
|
||||
},
|
||||
),
|
||||
&alias,
|
||||
);
|
||||
}
|
||||
|
||||
if !checker.source_type.is_stub() {
|
||||
if checker.enabled(Rule::UselessImportAlias) {
|
||||
pylint::rules::useless_import_alias(checker, alias);
|
||||
@@ -672,11 +694,7 @@ 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,
|
||||
checker.locator,
|
||||
);
|
||||
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
for name in names {
|
||||
@@ -712,16 +730,56 @@ 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::name_or_parent_is_banned(checker, &module, stmt);
|
||||
flake8_tidy_imports::rules::banned_api(
|
||||
checker,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||
flake8_tidy_imports::matchers::MatchNameOrParent { module: &module },
|
||||
),
|
||||
&stmt,
|
||||
);
|
||||
|
||||
for alias in names {
|
||||
if &alias.name == "*" {
|
||||
continue;
|
||||
}
|
||||
flake8_tidy_imports::rules::name_is_banned(
|
||||
flake8_tidy_imports::rules::banned_api(
|
||||
checker,
|
||||
format!("{module}.{}", alias.name),
|
||||
alias,
|
||||
&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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1168,14 +1226,7 @@ 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);
|
||||
|
||||
@@ -599,14 +599,7 @@ 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) {
|
||||
@@ -1275,7 +1268,7 @@ where
|
||||
|
||||
fn visit_format_spec(&mut self, format_spec: &'b Expr) {
|
||||
match format_spec {
|
||||
Expr::FString(ast::ExprFString { values, range: _ }) => {
|
||||
Expr::FString(ast::ExprFString { values, .. }) => {
|
||||
for value in values {
|
||||
self.visit_expr(value);
|
||||
}
|
||||
|
||||
@@ -226,8 +226,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "W0711") => (RuleGroup::Unspecified, rules::pylint::rules::BinaryOpException),
|
||||
(Pylint, "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
|
||||
@@ -309,6 +311,7 @@ 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),
|
||||
@@ -565,9 +568,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bandit, "701") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
|
||||
|
||||
// flake8-boolean-trap
|
||||
(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),
|
||||
(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),
|
||||
|
||||
// flake8-unused-arguments
|
||||
(Flake8UnusedArguments, "001") => (RuleGroup::Unspecified, rules::flake8_unused_arguments::rules::UnusedFunctionArgument),
|
||||
@@ -682,6 +685,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8PytestStyle, "011") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestRaisesTooBroad),
|
||||
(Flake8PytestStyle, "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),
|
||||
@@ -694,6 +698,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8PytestStyle, "024") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUnnecessaryAsyncioMarkOnFixture),
|
||||
(Flake8PytestStyle, "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),
|
||||
@@ -811,6 +816,7 @@ 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),
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use globset::GlobMatcher;
|
||||
use log::debug;
|
||||
use path_absolutize::{path_dedot, Absolutize};
|
||||
use path_absolutize::Absolutize;
|
||||
|
||||
use crate::registry::RuleSet;
|
||||
|
||||
@@ -61,7 +61,13 @@ pub fn normalize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root:
|
||||
/// Convert an absolute path to be relative to the current working directory.
|
||||
pub fn relativize_path<P: AsRef<Path>>(path: P) -> String {
|
||||
let path = path.as_ref();
|
||||
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let cwd = Path::new(".");
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let cwd = path_absolutize::path_dedot::CWD.as_path();
|
||||
|
||||
if let Ok(path) = path.strip_prefix(cwd) {
|
||||
return format!("{}", path.display());
|
||||
}
|
||||
format!("{}", path.display())
|
||||
|
||||
@@ -67,9 +67,13 @@ impl<'a> Insertion<'a> {
|
||||
TextSize::default()
|
||||
};
|
||||
|
||||
// Skip over commented lines.
|
||||
// Skip over commented lines, with whitespace separation.
|
||||
for line in UniversalNewlineIterator::with_offset(locator.after(location), location) {
|
||||
if line.trim_whitespace_start().starts_with('#') {
|
||||
let trimmed_line = line.trim_whitespace_start();
|
||||
if trimmed_line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if trimmed_line.starts_with('#') {
|
||||
location = line.full_end();
|
||||
} else {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
||||
use std::io::{BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::iter;
|
||||
use std::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::read(path).map_err(|err| {
|
||||
let mut notebook = Notebook::from_path(path).map_err(|err| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to read notebook file `{}`: {:?}",
|
||||
path.display(),
|
||||
@@ -120,18 +120,30 @@ pub struct Notebook {
|
||||
|
||||
impl Notebook {
|
||||
/// Read the Jupyter Notebook from the given [`Path`].
|
||||
///
|
||||
/// 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| {
|
||||
pub fn from_path(path: &Path) -> Result<Self, Box<Diagnostic>> {
|
||||
Self::from_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')
|
||||
@@ -144,7 +156,7 @@ impl Notebook {
|
||||
TextRange::default(),
|
||||
)
|
||||
})?;
|
||||
let raw_notebook: RawNotebook = match serde_json::from_reader(reader) {
|
||||
let raw_notebook: RawNotebook = match serde_json::from_reader(reader.by_ref()) {
|
||||
Ok(notebook) => notebook,
|
||||
Err(err) => {
|
||||
// Translate the error into a diagnostic
|
||||
@@ -159,14 +171,19 @@ 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 contents = std::fs::read_to_string(path).map_err(|err| {
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: format!("{err}"),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
})?;
|
||||
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(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Check if tokenizing was successful and the file is non-empty
|
||||
if lex(&contents, Mode::Module).any(|result| result.is_err()) {
|
||||
Diagnostic::new(
|
||||
@@ -182,7 +199,7 @@ impl Notebook {
|
||||
Diagnostic::new(
|
||||
SyntaxError {
|
||||
message: format!(
|
||||
"Expected a Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT} extension), \
|
||||
"Expected a Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT}), \
|
||||
which must be internally stored as JSON, \
|
||||
but found a Python source file: {err}"
|
||||
),
|
||||
@@ -484,22 +501,22 @@ mod tests {
|
||||
fn test_invalid() {
|
||||
let path = Path::new("resources/test/fixtures/jupyter/invalid_extension.ipynb");
|
||||
assert_eq!(
|
||||
Notebook::read(path).unwrap_err().kind.body,
|
||||
"SyntaxError: Expected a Jupyter Notebook (.ipynb extension), \
|
||||
Notebook::from_path(path).unwrap_err().kind.body,
|
||||
"SyntaxError: Expected a Jupyter Notebook (.ipynb), \
|
||||
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::read(path).unwrap_err().kind.body,
|
||||
Notebook::from_path(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::read(path).unwrap_err().kind.body,
|
||||
Notebook::from_path(path).unwrap_err().kind.body,
|
||||
"SyntaxError: This file does not match the schema expected of Jupyter Notebooks: \
|
||||
missing field `cells` at line 1 column 2"
|
||||
);
|
||||
@@ -571,11 +588,11 @@ print("after empty cells")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_line_magics() -> Result<()> {
|
||||
let path = "line_magics.ipynb".to_string();
|
||||
fn test_ipy_escape_command() -> Result<()> {
|
||||
let path = "ipy_escape_command.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
&path,
|
||||
Path::new("line_magics_expected.ipynb"),
|
||||
Path::new("ipy_escape_command_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
|
||||
---
|
||||
line_magics.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
||||
ipy_escape_command.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
3 | %matplotlib inline
|
||||
4 |
|
||||
@@ -1,4 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::num::NonZeroU8;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use ruff_macros::CacheKey;
|
||||
@@ -58,7 +59,7 @@ impl Eq for LineWidth {}
|
||||
|
||||
impl PartialOrd for LineWidth {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.width.partial_cmp(&other.width)
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ impl LineWidth {
|
||||
}
|
||||
|
||||
fn update(mut self, chars: impl Iterator<Item = char>) -> Self {
|
||||
let tab_size: usize = self.tab_size.into();
|
||||
let tab_size: usize = self.tab_size.as_usize();
|
||||
for c in chars {
|
||||
match c {
|
||||
'\t' => {
|
||||
@@ -144,22 +145,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(pub u8);
|
||||
pub struct TabSize(NonZeroU8);
|
||||
|
||||
impl TabSize {
|
||||
pub(crate) fn as_usize(self) -> usize {
|
||||
self.0.get() as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TabSize {
|
||||
fn default() -> Self {
|
||||
Self(4)
|
||||
Self(NonZeroU8::new(4).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for TabSize {
|
||||
fn from(tab_size: u8) -> Self {
|
||||
impl From<NonZeroU8> for TabSize {
|
||||
fn from(tab_size: NonZeroU8) -> Self {
|
||||
Self(tab_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TabSize> for usize {
|
||||
fn from(tab_size: TabSize) -> Self {
|
||||
tab_size.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,12 +293,10 @@ impl Display for MessageCodeFrame<'_> {
|
||||
}
|
||||
|
||||
fn replace_whitespace(source: &str, annotation_range: TextRange) -> SourceCode {
|
||||
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(TAB_SIZE);
|
||||
let mut line_width = LineWidth::new(TabSize::default());
|
||||
|
||||
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::BooleanPositionalValueInFunctionCall,
|
||||
/// Rule::BooleanPositionalValueInCall,
|
||||
/// ]);
|
||||
///
|
||||
/// 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::BooleanPositionalValueInFunctionCall));
|
||||
/// assert!(union.contains(Rule::BooleanPositionalValueInCall));
|
||||
/// ```
|
||||
#[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::BooleanPositionalValueInFunctionCall,
|
||||
/// Rule::BooleanPositionalValueInCall,
|
||||
/// Rule::BadQuotesInlineString
|
||||
/// ])));
|
||||
/// ```
|
||||
|
||||
@@ -80,7 +80,7 @@ pub(crate) fn variable_name_task_id(
|
||||
// If the keyword argument is not a string, we can't do anything.
|
||||
let task_id = match &keyword.value {
|
||||
Expr::Constant(constant) => match &constant.value {
|
||||
Constant::Str(value) => value,
|
||||
Constant::Str(ast::StringConstant { value, .. }) => value,
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of hardcoded temporary file or directory paths.
|
||||
///
|
||||
@@ -49,19 +51,33 @@ impl Violation for HardcodedTempFile {
|
||||
}
|
||||
|
||||
/// S108
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
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 = pickle.load(file)
|
||||
/// foo = marshal.load(file)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
|
||||
/// Returns `true` if a function call is allowed to use a boolean trap.
|
||||
pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
||||
@@ -62,18 +58,13 @@ pub(super) fn allow_boolean_trap(func: &Expr) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
const fn is_boolean_arg(arg: &Expr) -> bool {
|
||||
/// Returns `true` if an expression is a boolean literal.
|
||||
pub(super) const fn is_boolean(expr: &Expr) -> bool {
|
||||
matches!(
|
||||
&arg,
|
||||
&expr,
|
||||
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::BooleanPositionalArgInFunctionDefinition, Path::new("FBT.py"))]
|
||||
#[test_case(Rule::BooleanDefaultValueInFunctionDefinition, Path::new("FBT.py"))]
|
||||
#[test_case(Rule::BooleanPositionalValueInFunctionCall, Path::new("FBT.py"))]
|
||||
#[test_case(Rule::BooleanTypeHintPositionalArgument, Path::new("FBT.py"))]
|
||||
#[test_case(Rule::BooleanDefaultValuePositionalArgument, Path::new("FBT.py"))]
|
||||
#[test_case(Rule::BooleanPositionalValueInCall, 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(
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::call_path::collect_call_path;
|
||||
use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::{is_allowed_func_def, is_boolean};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of boolean positional arguments in function definitions,
|
||||
/// as determined by the presence of a boolean default value.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Calling a function with boolean positional arguments is confusing as the
|
||||
/// meaning of the boolean value is not clear to the caller and to future
|
||||
/// readers of the code.
|
||||
///
|
||||
/// The use of a boolean will also limit the function to only two possible
|
||||
/// behaviors, which makes the function difficult to extend in the future.
|
||||
///
|
||||
/// Instead, consider refactoring into separate implementations for the
|
||||
/// `True` and `False` cases, using an `Enum`, or making the argument a
|
||||
/// keyword-only argument, to force callers to be explicit when providing
|
||||
/// the argument.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from math import ceil, floor
|
||||
///
|
||||
///
|
||||
/// def round_number(number, up=True):
|
||||
/// return ceil(number) if up else floor(number)
|
||||
///
|
||||
///
|
||||
/// round_number(1.5, True) # What does `True` mean?
|
||||
/// round_number(1.5, False) # What does `False` mean?
|
||||
/// ```
|
||||
///
|
||||
/// Instead, refactor into separate implementations:
|
||||
/// ```python
|
||||
/// from math import ceil, floor
|
||||
///
|
||||
///
|
||||
/// def round_up(number):
|
||||
/// return ceil(number)
|
||||
///
|
||||
///
|
||||
/// def round_down(number):
|
||||
/// return floor(number)
|
||||
///
|
||||
///
|
||||
/// round_up(1.5)
|
||||
/// round_down(1.5)
|
||||
/// ```
|
||||
///
|
||||
/// Or, refactor to use an `Enum`:
|
||||
/// ```python
|
||||
/// from enum import Enum
|
||||
///
|
||||
///
|
||||
/// class RoundingMethod(Enum):
|
||||
/// UP = 1
|
||||
/// DOWN = 2
|
||||
///
|
||||
///
|
||||
/// def round_number(value, method):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Or, make the argument a keyword-only argument:
|
||||
/// ```python
|
||||
/// from math import ceil, floor
|
||||
///
|
||||
///
|
||||
/// def round_number(number, *, up=True):
|
||||
/// return ceil(number) if up else floor(number)
|
||||
///
|
||||
///
|
||||
/// round_number(1.5, up=True)
|
||||
/// round_number(1.5, up=False)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||
#[violation]
|
||||
pub struct BooleanDefaultValuePositionalArgument;
|
||||
|
||||
impl Violation for BooleanDefaultValuePositionalArgument {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Boolean default positional argument in function definition")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn boolean_default_value_positional_argument(
|
||||
checker: &mut Checker,
|
||||
name: &str,
|
||||
decorator_list: &[Decorator],
|
||||
parameters: &Parameters,
|
||||
) {
|
||||
if is_allowed_func_def(name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if decorator_list.iter().any(|decorator| {
|
||||
collect_call_path(&decorator.expression)
|
||||
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"])
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ParameterWithDefault {
|
||||
parameter,
|
||||
default,
|
||||
range: _,
|
||||
} in parameters.posonlyargs.iter().chain(¶meters.args)
|
||||
{
|
||||
if default.as_ref().is_some_and(|default| is_boolean(default)) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BooleanDefaultValuePositionalArgument,
|
||||
parameter.name.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
use ruff_python_ast::Expr;
|
||||
|
||||
use ruff_diagnostics::Violation;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, 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::{add_if_boolean, allow_boolean_trap};
|
||||
use crate::rules::flake8_boolean_trap::helpers::{allow_boolean_trap, is_boolean};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for boolean positional arguments in function calls.
|
||||
@@ -17,44 +15,42 @@ use crate::rules::flake8_boolean_trap::helpers::{add_if_boolean, allow_boolean_t
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(flag: bool) -> None:
|
||||
/// def func(flag: bool) -> None:
|
||||
/// ...
|
||||
///
|
||||
///
|
||||
/// foo(True)
|
||||
/// func(True)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo(flag: bool) -> None:
|
||||
/// def func(flag: bool) -> None:
|
||||
/// ...
|
||||
///
|
||||
///
|
||||
/// foo(flag=True)
|
||||
/// func(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 BooleanPositionalValueInFunctionCall;
|
||||
pub struct BooleanPositionalValueInCall;
|
||||
|
||||
impl Violation for BooleanPositionalValueInFunctionCall {
|
||||
impl Violation for BooleanPositionalValueInCall {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Boolean positional value in function call")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_boolean_positional_value_in_function_call(
|
||||
checker: &mut Checker,
|
||||
args: &[Expr],
|
||||
func: &Expr,
|
||||
) {
|
||||
pub(crate) fn boolean_positional_value_in_call(checker: &mut Checker, args: &[Expr], func: &Expr) {
|
||||
if allow_boolean_trap(func) {
|
||||
return;
|
||||
}
|
||||
for arg in args {
|
||||
add_if_boolean(checker, arg, BooleanPositionalValueInFunctionCall.into());
|
||||
for arg in args.iter().filter(|arg| is_boolean(arg)) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(BooleanPositionalValueInCall, arg.range()));
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,22 @@ use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for boolean positional arguments in function definitions.
|
||||
/// Checks for the use of boolean positional arguments in function definitions,
|
||||
/// as determined by the presence of a `bool` type hint.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// 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
|
||||
@@ -65,20 +71,33 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Or, make the argument a keyword-only argument:
|
||||
/// ```python
|
||||
/// from math import ceil, floor
|
||||
///
|
||||
///
|
||||
/// def round_number(number: float, *, up: bool) -> int:
|
||||
/// return ceil(number) if up else floor(number)
|
||||
///
|
||||
///
|
||||
/// round_number(1.5, up=True)
|
||||
/// round_number(1.5, up=False)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [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 BooleanPositionalArgInFunctionDefinition;
|
||||
pub struct BooleanTypeHintPositionalArgument;
|
||||
|
||||
impl Violation for BooleanPositionalArgInFunctionDefinition {
|
||||
impl Violation for BooleanTypeHintPositionalArgument {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Boolean positional arg in function definition")
|
||||
format!("Boolean-typed positional argument in function definition")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_positional_boolean_in_def(
|
||||
pub(crate) fn boolean_type_hint_positional_argument(
|
||||
checker: &mut Checker,
|
||||
name: &str,
|
||||
decorator_list: &[Decorator],
|
||||
@@ -101,28 +120,25 @@ pub(crate) fn check_positional_boolean_in_def(
|
||||
range: _,
|
||||
} in parameters.posonlyargs.iter().chain(¶meters.args)
|
||||
{
|
||||
if parameter.annotation.is_none() {
|
||||
continue;
|
||||
}
|
||||
let Some(expr) = ¶meter.annotation else {
|
||||
let Some(annotation) = parameter.annotation.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// check for both bool (python class) and 'bool' (string annotation)
|
||||
let hint = match expr.as_ref() {
|
||||
let hint = match annotation.as_ref() {
|
||||
Expr::Name(name) => &name.id == "bool",
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
..
|
||||
}) => value == "bool",
|
||||
_ => false,
|
||||
};
|
||||
if !hint {
|
||||
if !hint || !checker.semantic().is_builtin("bool") {
|
||||
continue;
|
||||
}
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BooleanPositionalArgInFunctionDefinition,
|
||||
parameter.range(),
|
||||
BooleanTypeHintPositionalArgument,
|
||||
parameter.name.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters};
|
||||
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::call_path::collect_call_path;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::{add_if_boolean, is_allowed_func_def};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of booleans as default values in function definitions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Calling a function with boolean default means that the keyword argument
|
||||
/// argument can be omitted, which makes the function call ambiguous.
|
||||
///
|
||||
/// Instead, define the relevant argument as keyword-only.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from math import ceil, floor
|
||||
///
|
||||
///
|
||||
/// def round_number(number: float, *, up: bool = True) -> int:
|
||||
/// return ceil(number) if up else floor(number)
|
||||
///
|
||||
///
|
||||
/// round_number(1.5)
|
||||
/// round_number(1.5, up=False)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from math import ceil, floor
|
||||
///
|
||||
///
|
||||
/// def round_number(number: float, *, up: bool) -> int:
|
||||
/// return ceil(number) if up else floor(number)
|
||||
///
|
||||
///
|
||||
/// round_number(1.5, up=True)
|
||||
/// round_number(1.5, up=False)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||
#[violation]
|
||||
pub struct BooleanDefaultValueInFunctionDefinition;
|
||||
|
||||
impl Violation for BooleanDefaultValueInFunctionDefinition {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Boolean default value in function definition")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_boolean_default_value_in_function_definition(
|
||||
checker: &mut Checker,
|
||||
name: &str,
|
||||
decorator_list: &[Decorator],
|
||||
parameters: &Parameters,
|
||||
) {
|
||||
if is_allowed_func_def(name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if decorator_list.iter().any(|decorator| {
|
||||
collect_call_path(&decorator.expression)
|
||||
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"])
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ParameterWithDefault {
|
||||
parameter: _,
|
||||
default,
|
||||
range: _,
|
||||
} in parameters.args.iter().chain(¶meters.posonlyargs)
|
||||
{
|
||||
let Some(default) = default else {
|
||||
continue;
|
||||
};
|
||||
add_if_boolean(
|
||||
checker,
|
||||
default,
|
||||
BooleanDefaultValueInFunctionDefinition.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
pub(crate) use check_boolean_default_value_in_function_definition::*;
|
||||
pub(crate) use check_boolean_positional_value_in_function_call::*;
|
||||
pub(crate) use check_positional_boolean_in_def::*;
|
||||
pub(crate) use boolean_default_value_positional_argument::*;
|
||||
pub(crate) use boolean_positional_value_in_call::*;
|
||||
pub(crate) use 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;
|
||||
mod boolean_default_value_positional_argument;
|
||||
mod boolean_positional_value_in_call;
|
||||
mod boolean_type_hint_positional_argument;
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_boolean_trap/mod.rs
|
||||
---
|
||||
FBT.py:4:5: FBT001 Boolean positional arg in function definition
|
||||
FBT.py:4:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
2 | posonly_nohint,
|
||||
3 | posonly_nonboolhint: int,
|
||||
4 | posonly_boolhint: bool,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
| ^^^^^^^^^^^^^^^^ FBT001
|
||||
5 | posonly_boolstrhint: "bool",
|
||||
6 | /,
|
||||
|
|
||||
|
||||
FBT.py:5:5: FBT001 Boolean positional arg in function definition
|
||||
FBT.py:5:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
3 | posonly_nonboolhint: int,
|
||||
4 | posonly_boolhint: bool,
|
||||
5 | posonly_boolstrhint: "bool",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
| ^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
6 | /,
|
||||
7 | offset,
|
||||
|
|
||||
|
||||
FBT.py:10:5: FBT001 Boolean positional arg in function definition
|
||||
FBT.py:10:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
8 | posorkw_nonvalued_nohint,
|
||||
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 positional arg in function definition
|
||||
FBT.py:11:5: FBT001 Boolean-typed positional argument 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 positional arg in function definition
|
||||
FBT.py:14:5: FBT001 Boolean-typed positional argument 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 positional arg in function definition
|
||||
FBT.py:15:5: FBT001 Boolean-typed positional argument 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 positional arg in function definition
|
||||
FBT.py:18:5: FBT001 Boolean-typed positional argument 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 positional arg in function definition
|
||||
FBT.py:19:5: FBT001 Boolean-typed positional argument 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 positional arg in function definition
|
||||
FBT.py:86:19: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
85 | # FBT001: Boolean positional arg in function definition
|
||||
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:31: FBT002 Boolean default value in function definition
|
||||
FBT.py:12:5: FBT002 Boolean default positional argument in function definition
|
||||
|
|
||||
10 | posorkw_nonvalued_boolhint: bool,
|
||||
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:43: FBT002 Boolean default value in function definition
|
||||
FBT.py:13:5: FBT002 Boolean default positional argument in function definition
|
||||
|
|
||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||
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:41: FBT002 Boolean default value in function definition
|
||||
FBT.py:14:5: FBT002 Boolean default positional argument in function definition
|
||||
|
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
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:46: FBT002 Boolean default value in function definition
|
||||
FBT.py:15:5: FBT002 Boolean default positional argument in function definition
|
||||
|
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
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,7 +49,6 @@ 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(
|
||||
@@ -60,6 +59,17 @@ 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();
|
||||
@@ -72,7 +82,7 @@ mod tests {
|
||||
"fastapi.Query".to_string(),
|
||||
],
|
||||
},
|
||||
..Settings::for_rules(vec![Rule::FunctionCallInDefaultArgument])
|
||||
..Settings::for_rule(Rule::FunctionCallInDefaultArgument)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
|
||||
@@ -64,7 +64,7 @@ pub(crate) fn getattr_with_constant(
|
||||
return;
|
||||
};
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
..
|
||||
}) = arg
|
||||
else {
|
||||
|
||||
@@ -74,7 +74,6 @@ fn walk_stmt(checker: &mut Checker, body: &[Stmt], f: fn(&Stmt) -> bool) {
|
||||
}
|
||||
Stmt::If(ast::StmtIf { body, .. })
|
||||
| Stmt::Try(ast::StmtTry { body, .. })
|
||||
| Stmt::TryStar(ast::StmtTryStar { body, .. })
|
||||
| Stmt::With(ast::StmtWith { body, .. }) => {
|
||||
walk_stmt(checker, body, f);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use ruff_python_ast::{ParameterWithDefault, Parameters, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||
use ruff_python_ast::{self as ast, Expr, Parameter, ParameterWithDefault, Ranged};
|
||||
use ruff_python_codegen::{Generator, Stylist};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr};
|
||||
use ruff_python_trivia::{indentation_at_offset, textwrap};
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of mutable objects as function argument defaults.
|
||||
@@ -50,24 +55,30 @@ use crate::checkers::ast::Checker;
|
||||
pub struct MutableArgumentDefault;
|
||||
|
||||
impl Violation for MutableArgumentDefault {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Do not use mutable data structures for argument defaults")
|
||||
}
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(format!("Replace with `None`; initialize within function"))
|
||||
}
|
||||
}
|
||||
|
||||
/// B006
|
||||
pub(crate) fn mutable_argument_default(checker: &mut Checker, parameters: &Parameters) {
|
||||
pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast::StmtFunctionDef) {
|
||||
// Scan in reverse order to right-align zip().
|
||||
for ParameterWithDefault {
|
||||
parameter,
|
||||
default,
|
||||
range: _,
|
||||
} in parameters
|
||||
} in function_def
|
||||
.parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
.chain(&function_def.parameters.args)
|
||||
.chain(&function_def.parameters.kwonlyargs)
|
||||
{
|
||||
let Some(default) = default else {
|
||||
continue;
|
||||
@@ -79,9 +90,84 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, parameters: &Param
|
||||
.as_ref()
|
||||
.is_some_and(|expr| is_immutable_annotation(expr, checker.semantic()))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(MutableArgumentDefault, default.range()));
|
||||
let mut diagnostic = Diagnostic::new(MutableArgumentDefault, default.range());
|
||||
|
||||
// If the function body is on the same line as the function def, do not fix
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(fix) = move_initialization(
|
||||
function_def,
|
||||
parameter,
|
||||
default,
|
||||
checker.locator(),
|
||||
checker.stylist(),
|
||||
checker.indexer(),
|
||||
checker.generator(),
|
||||
) {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to move a mutable argument default initialization
|
||||
/// into the function body.
|
||||
fn move_initialization(
|
||||
function_def: &ast::StmtFunctionDef,
|
||||
parameter: &Parameter,
|
||||
default: &Expr,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
indexer: &Indexer,
|
||||
generator: Generator,
|
||||
) -> Option<Fix> {
|
||||
let mut body = function_def.body.iter();
|
||||
|
||||
let statement = body.next()?;
|
||||
if indexer.preceded_by_multi_statement_line(statement, locator) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Determine the indentation depth of the function body.
|
||||
let indentation = indentation_at_offset(statement.start(), locator)?;
|
||||
|
||||
// Set the default argument value to `None`.
|
||||
let default_edit = Edit::range_replacement("None".to_string(), default.range());
|
||||
|
||||
// Add an `if`, to set the argument to its original value if still `None`.
|
||||
let mut content = String::new();
|
||||
content.push_str(&format!("if {} is None:", parameter.name.as_str()));
|
||||
content.push_str(stylist.line_ending().as_str());
|
||||
content.push_str(stylist.indentation());
|
||||
content.push_str(&format!(
|
||||
"{} = {}",
|
||||
parameter.name.as_str(),
|
||||
generator.expr(default)
|
||||
));
|
||||
content.push_str(stylist.line_ending().as_str());
|
||||
|
||||
// Indent the edit to match the body indentation.
|
||||
let content = textwrap::indent(&content, indentation).to_string();
|
||||
|
||||
let initialization_edit = if is_docstring_stmt(statement) {
|
||||
// If the first statement in the function is a docstring, insert _after_ it.
|
||||
if let Some(statement) = body.next() {
|
||||
// If there's a second statement, insert _before_ it, but ensure this isn't a
|
||||
// multi-statement line.
|
||||
if indexer.preceded_by_multi_statement_line(statement, locator) {
|
||||
return None;
|
||||
}
|
||||
Edit::insertion(content, locator.line_start(statement.start()))
|
||||
} else {
|
||||
// If the docstring is the only statement, insert _before_ it.
|
||||
Edit::insertion(content, locator.full_line_end(statement.end()))
|
||||
}
|
||||
} else {
|
||||
// Otherwise, insert before the first statement.
|
||||
let at = locator.line_start(statement.start());
|
||||
Edit::insertion(content, at)
|
||||
};
|
||||
|
||||
Some(Fix::manual_edits(default_edit, [initialization_edit]))
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ pub(crate) fn setattr_with_constant(
|
||||
if !is_identifier(name) {
|
||||
return;
|
||||
}
|
||||
if is_mangled_private(name.as_str()) {
|
||||
if is_mangled_private(name) {
|
||||
return;
|
||||
}
|
||||
// We can only replace a `setattr` call (which is an `Expr`) with an assignment
|
||||
|
||||
@@ -68,13 +68,13 @@ pub(crate) fn unreliable_callable_check(
|
||||
return;
|
||||
};
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(string),
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
..
|
||||
}) = attr
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if string != "__call__" {
|
||||
if value != "__call__" {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,123 +1,479 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B006_B008.py:63:25: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:63:25: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
63 | def this_is_wrong(value=[1, 2, 3]):
|
||||
| ^^^^^^^^^ B006
|
||||
64 | ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:67:30: B006 Do not use mutable data structures for argument defaults
|
||||
ℹ Possible fix
|
||||
60 60 | # Flag mutable literals/comprehensions
|
||||
61 61 |
|
||||
62 62 |
|
||||
63 |-def this_is_wrong(value=[1, 2, 3]):
|
||||
63 |+def this_is_wrong(value=None):
|
||||
64 |+ if value is None:
|
||||
65 |+ value = [1, 2, 3]
|
||||
64 66 | ...
|
||||
65 67 |
|
||||
66 68 |
|
||||
|
||||
B006_B008.py:67:30: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
67 | def this_is_also_wrong(value={}):
|
||||
| ^^ B006
|
||||
68 | ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:71:20: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
71 | def and_this(value=set()):
|
||||
| ^^^^^ B006
|
||||
72 | ...
|
||||
|
|
||||
ℹ Possible fix
|
||||
64 64 | ...
|
||||
65 65 |
|
||||
66 66 |
|
||||
67 |-def this_is_also_wrong(value={}):
|
||||
67 |+def this_is_also_wrong(value=None):
|
||||
68 |+ if value is None:
|
||||
69 |+ value = {}
|
||||
68 70 | ...
|
||||
69 71 |
|
||||
70 72 |
|
||||
|
||||
B006_B008.py:75:20: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:73:52: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
75 | def this_too(value=collections.OrderedDict()):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
76 | ...
|
||||
71 | class Foo:
|
||||
72 | @staticmethod
|
||||
73 | def this_is_also_wrong_and_more_indented(value={}):
|
||||
| ^^ B006
|
||||
74 | pass
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:79:32: B006 Do not use mutable data structures for argument defaults
|
||||
ℹ Possible fix
|
||||
70 70 |
|
||||
71 71 | class Foo:
|
||||
72 72 | @staticmethod
|
||||
73 |- def this_is_also_wrong_and_more_indented(value={}):
|
||||
73 |+ def this_is_also_wrong_and_more_indented(value=None):
|
||||
74 |+ if value is None:
|
||||
75 |+ value = {}
|
||||
74 76 | pass
|
||||
75 77 |
|
||||
76 78 |
|
||||
|
||||
B006_B008.py:77:31: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
77 | def multiline_arg_wrong(value={
|
||||
| _______________________________^
|
||||
78 | |
|
||||
79 | | }):
|
||||
| |_^ B006
|
||||
80 | ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
74 74 | pass
|
||||
75 75 |
|
||||
76 76 |
|
||||
77 |-def multiline_arg_wrong(value={
|
||||
78 |-
|
||||
79 |-}):
|
||||
77 |+def multiline_arg_wrong(value=None):
|
||||
78 |+ if value is None:
|
||||
79 |+ value = {}
|
||||
80 80 | ...
|
||||
81 81 |
|
||||
82 82 | def single_line_func_wrong(value = {}): ...
|
||||
|
||||
B006_B008.py:82:36: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
79 | async def async_this_too(value=collections.defaultdict()):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
80 | ...
|
||||
81 |
|
||||
82 | def single_line_func_wrong(value = {}): ...
|
||||
| ^^ B006
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:83:26: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:85:20: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
83 | def dont_forget_me(value=collections.deque()):
|
||||
85 | def and_this(value=set()):
|
||||
| ^^^^^ B006
|
||||
86 | ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
82 82 | def single_line_func_wrong(value = {}): ...
|
||||
83 83 |
|
||||
84 84 |
|
||||
85 |-def and_this(value=set()):
|
||||
85 |+def and_this(value=None):
|
||||
86 |+ if value is None:
|
||||
87 |+ value = set()
|
||||
86 88 | ...
|
||||
87 89 |
|
||||
88 90 |
|
||||
|
||||
B006_B008.py:89:20: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
89 | def this_too(value=collections.OrderedDict()):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
90 | ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
86 86 | ...
|
||||
87 87 |
|
||||
88 88 |
|
||||
89 |-def this_too(value=collections.OrderedDict()):
|
||||
89 |+def this_too(value=None):
|
||||
90 |+ if value is None:
|
||||
91 |+ value = collections.OrderedDict()
|
||||
90 92 | ...
|
||||
91 93 |
|
||||
92 94 |
|
||||
|
||||
B006_B008.py:93:32: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
93 | async def async_this_too(value=collections.defaultdict()):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
94 | ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
90 90 | ...
|
||||
91 91 |
|
||||
92 92 |
|
||||
93 |-async def async_this_too(value=collections.defaultdict()):
|
||||
93 |+async def async_this_too(value=None):
|
||||
94 |+ if value is None:
|
||||
95 |+ value = collections.defaultdict()
|
||||
94 96 | ...
|
||||
95 97 |
|
||||
96 98 |
|
||||
|
||||
B006_B008.py:97:26: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
97 | def dont_forget_me(value=collections.deque()):
|
||||
| ^^^^^^^^^^^^^^^^^^^ B006
|
||||
84 | ...
|
||||
98 | ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:88:46: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
87 | # N.B. we're also flagging the function call in the comprehension
|
||||
88 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
89 | pass
|
||||
|
|
||||
ℹ Possible fix
|
||||
94 94 | ...
|
||||
95 95 |
|
||||
96 96 |
|
||||
97 |-def dont_forget_me(value=collections.deque()):
|
||||
97 |+def dont_forget_me(value=None):
|
||||
98 |+ if value is None:
|
||||
99 |+ value = collections.deque()
|
||||
98 100 | ...
|
||||
99 101 |
|
||||
100 102 |
|
||||
|
||||
B006_B008.py:92:46: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
92 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
93 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:96:45: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
96 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
97 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:100:33: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:102:46: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
100 | def kwonlyargs_mutable(*, value=[]):
|
||||
101 | # N.B. we're also flagging the function call in the comprehension
|
||||
102 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
103 | pass
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
99 99 |
|
||||
100 100 |
|
||||
101 101 | # N.B. we're also flagging the function call in the comprehension
|
||||
102 |-def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
||||
102 |+def list_comprehension_also_not_okay(default=None):
|
||||
103 |+ if default is None:
|
||||
104 |+ default = [i ** 2 for i in range(3)]
|
||||
103 105 | pass
|
||||
104 106 |
|
||||
105 107 |
|
||||
|
||||
B006_B008.py:106:46: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
106 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
107 | pass
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
103 103 | pass
|
||||
104 104 |
|
||||
105 105 |
|
||||
106 |-def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
||||
106 |+def dict_comprehension_also_not_okay(default=None):
|
||||
107 |+ if default is None:
|
||||
108 |+ default = {i: i ** 2 for i in range(3)}
|
||||
107 109 | pass
|
||||
108 110 |
|
||||
109 111 |
|
||||
|
||||
B006_B008.py:110:45: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
110 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
111 | pass
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
107 107 | pass
|
||||
108 108 |
|
||||
109 109 |
|
||||
110 |-def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
||||
110 |+def set_comprehension_also_not_okay(default=None):
|
||||
111 |+ if default is None:
|
||||
112 |+ default = {i ** 2 for i in range(3)}
|
||||
111 113 | pass
|
||||
112 114 |
|
||||
113 115 |
|
||||
|
||||
B006_B008.py:114:33: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
114 | def kwonlyargs_mutable(*, value=[]):
|
||||
| ^^ B006
|
||||
101 | ...
|
||||
115 | ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:221:20: B006 Do not use mutable data structures for argument defaults
|
||||
ℹ Possible fix
|
||||
111 111 | pass
|
||||
112 112 |
|
||||
113 113 |
|
||||
114 |-def kwonlyargs_mutable(*, value=[]):
|
||||
114 |+def kwonlyargs_mutable(*, value=None):
|
||||
115 |+ if value is None:
|
||||
116 |+ value = []
|
||||
115 117 | ...
|
||||
116 118 |
|
||||
117 119 |
|
||||
|
||||
B006_B008.py:235:20: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
219 | # B006 and B008
|
||||
220 | # We should handle arbitrary nesting of these B008.
|
||||
221 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
233 | # B006 and B008
|
||||
234 | # We should handle arbitrary nesting of these B008.
|
||||
235 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
222 | pass
|
||||
236 | pass
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:258:27: B006 Do not use mutable data structures for argument defaults
|
||||
ℹ Possible fix
|
||||
232 232 |
|
||||
233 233 | # B006 and B008
|
||||
234 234 | # We should handle arbitrary nesting of these B008.
|
||||
235 |-def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
235 |+def nested_combo(a=None):
|
||||
236 |+ if a is None:
|
||||
237 |+ a = [float(3), dt.datetime.now()]
|
||||
236 238 | pass
|
||||
237 239 |
|
||||
238 240 |
|
||||
|
||||
B006_B008.py:272:27: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
257 | def mutable_annotations(
|
||||
258 | a: list[int] | None = [],
|
||||
271 | def mutable_annotations(
|
||||
272 | a: list[int] | None = [],
|
||||
| ^^ B006
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
273 | b: Optional[Dict[int, int]] = {},
|
||||
274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:259:35: B006 Do not use mutable data structures for argument defaults
|
||||
ℹ Possible fix
|
||||
269 269 |
|
||||
270 270 |
|
||||
271 271 | def mutable_annotations(
|
||||
272 |- a: list[int] | None = [],
|
||||
272 |+ a: list[int] | None = None,
|
||||
273 273 | b: Optional[Dict[int, int]] = {},
|
||||
274 274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
275 275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 276 | ):
|
||||
277 |+ if a is None:
|
||||
278 |+ a = []
|
||||
277 279 | pass
|
||||
278 280 |
|
||||
279 281 |
|
||||
|
||||
B006_B008.py:273:35: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
257 | def mutable_annotations(
|
||||
258 | a: list[int] | None = [],
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
271 | def mutable_annotations(
|
||||
272 | a: list[int] | None = [],
|
||||
273 | b: Optional[Dict[int, int]] = {},
|
||||
| ^^ B006
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:260:62: B006 Do not use mutable data structures for argument defaults
|
||||
ℹ Possible fix
|
||||
270 270 |
|
||||
271 271 | def mutable_annotations(
|
||||
272 272 | a: list[int] | None = [],
|
||||
273 |- b: Optional[Dict[int, int]] = {},
|
||||
273 |+ b: Optional[Dict[int, int]] = None,
|
||||
274 274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
275 275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 276 | ):
|
||||
277 |+ if b is None:
|
||||
278 |+ b = {}
|
||||
277 279 | pass
|
||||
278 280 |
|
||||
279 281 |
|
||||
|
||||
B006_B008.py:274:62: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
258 | a: list[int] | None = [],
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
272 | a: list[int] | None = [],
|
||||
273 | b: Optional[Dict[int, int]] = {},
|
||||
274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^ B006
|
||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
262 | ):
|
||||
275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 | ):
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:261:80: B006 Do not use mutable data structures for argument defaults
|
||||
ℹ Possible fix
|
||||
271 271 | def mutable_annotations(
|
||||
272 272 | a: list[int] | None = [],
|
||||
273 273 | b: Optional[Dict[int, int]] = {},
|
||||
274 |- c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
274 |+ c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
275 275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 276 | ):
|
||||
277 |+ if c is None:
|
||||
278 |+ c = set()
|
||||
277 279 | pass
|
||||
278 280 |
|
||||
279 281 |
|
||||
|
||||
B006_B008.py:275:80: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
273 | b: Optional[Dict[int, int]] = {},
|
||||
274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
275 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^ B006
|
||||
262 | ):
|
||||
263 | pass
|
||||
276 | ):
|
||||
277 | pass
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
272 272 | a: list[int] | None = [],
|
||||
273 273 | b: Optional[Dict[int, int]] = {},
|
||||
274 274 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
275 |- d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
275 |+ d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
276 276 | ):
|
||||
277 |+ if d is None:
|
||||
278 |+ d = set()
|
||||
277 279 | pass
|
||||
278 280 |
|
||||
279 281 |
|
||||
|
||||
B006_B008.py:280:52: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
280 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^ B006
|
||||
281 | """Docstring"""
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
277 277 | pass
|
||||
278 278 |
|
||||
279 279 |
|
||||
280 |-def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
280 |+def single_line_func_wrong(value: dict[str, str] = None):
|
||||
281 281 | """Docstring"""
|
||||
282 |+ if value is None:
|
||||
283 |+ value = {}
|
||||
282 284 |
|
||||
283 285 |
|
||||
284 286 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
|
||||
B006_B008.py:284:52: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
284 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^ B006
|
||||
285 | """Docstring"""
|
||||
286 | ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
281 281 | """Docstring"""
|
||||
282 282 |
|
||||
283 283 |
|
||||
284 |-def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
284 |+def single_line_func_wrong(value: dict[str, str] = None):
|
||||
285 285 | """Docstring"""
|
||||
286 |+ if value is None:
|
||||
287 |+ value = {}
|
||||
286 288 | ...
|
||||
287 289 |
|
||||
288 290 |
|
||||
|
||||
B006_B008.py:289:52: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
289 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^ B006
|
||||
290 | """Docstring"""; ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:293:52: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
293 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^ B006
|
||||
294 | """Docstring"""; \
|
||||
295 | ...
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:298:52: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
298 | def single_line_func_wrong(value: dict[str, str] = {
|
||||
| ____________________________________________________^
|
||||
299 | | # This is a comment
|
||||
300 | | }):
|
||||
| |_^ B006
|
||||
301 | """Docstring"""
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
295 295 | ...
|
||||
296 296 |
|
||||
297 297 |
|
||||
298 |-def single_line_func_wrong(value: dict[str, str] = {
|
||||
299 |- # This is a comment
|
||||
300 |-}):
|
||||
298 |+def single_line_func_wrong(value: dict[str, str] = None):
|
||||
301 299 | """Docstring"""
|
||||
300 |+ if value is None:
|
||||
301 |+ value = {}
|
||||
302 302 |
|
||||
303 303 |
|
||||
304 304 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
|
||||
B006_B008.py:304:52: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
304 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
| ^^ B006
|
||||
305 | : \
|
||||
306 | """Docstring"""
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
|
||||
|
||||
@@ -1,83 +1,83 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B006_B008.py:88:61: B008 Do not perform function call `range` in argument defaults
|
||||
|
|
||||
87 | # N.B. we're also flagging the function call in the comprehension
|
||||
88 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
||||
| ^^^^^^^^ B008
|
||||
89 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:92:64: B008 Do not perform function call `range` in argument defaults
|
||||
|
|
||||
92 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
||||
| ^^^^^^^^ B008
|
||||
93 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:96:60: B008 Do not perform function call `range` in argument defaults
|
||||
|
|
||||
96 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
||||
| ^^^^^^^^ B008
|
||||
97 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:112:39: B008 Do not perform function call `time.time` in argument defaults
|
||||
B006_B008.py:102:61: B008 Do not perform function call `range` in argument defaults
|
||||
|
|
||||
110 | # B008
|
||||
111 | # Flag function calls as default args (including if they are part of a sub-expression)
|
||||
112 | def in_fact_all_calls_are_wrong(value=time.time()):
|
||||
101 | # N.B. we're also flagging the function call in the comprehension
|
||||
102 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
||||
| ^^^^^^^^ B008
|
||||
103 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:106:64: B008 Do not perform function call `range` in argument defaults
|
||||
|
|
||||
106 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
||||
| ^^^^^^^^ B008
|
||||
107 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:110:60: B008 Do not perform function call `range` in argument defaults
|
||||
|
|
||||
110 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
||||
| ^^^^^^^^ B008
|
||||
111 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:126:39: B008 Do not perform function call `time.time` in argument defaults
|
||||
|
|
||||
124 | # B008
|
||||
125 | # Flag function calls as default args (including if they are part of a sub-expression)
|
||||
126 | def in_fact_all_calls_are_wrong(value=time.time()):
|
||||
| ^^^^^^^^^^^ B008
|
||||
113 | ...
|
||||
127 | ...
|
||||
|
|
||||
|
||||
B006_B008.py:116:12: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
B006_B008.py:130:12: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
|
|
||||
116 | def f(when=dt.datetime.now() + dt.timedelta(days=7)):
|
||||
130 | def f(when=dt.datetime.now() + dt.timedelta(days=7)):
|
||||
| ^^^^^^^^^^^^^^^^^ B008
|
||||
117 | pass
|
||||
131 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:120:30: B008 Do not perform function call in argument defaults
|
||||
B006_B008.py:134:30: B008 Do not perform function call in argument defaults
|
||||
|
|
||||
120 | def can_even_catch_lambdas(a=(lambda x: x)()):
|
||||
134 | def can_even_catch_lambdas(a=(lambda x: x)()):
|
||||
| ^^^^^^^^^^^^^^^ B008
|
||||
121 | ...
|
||||
135 | ...
|
||||
|
|
||||
|
||||
B006_B008.py:221:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
B006_B008.py:235:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
|
|
||||
219 | # B006 and B008
|
||||
220 | # We should handle arbitrary nesting of these B008.
|
||||
221 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
233 | # B006 and B008
|
||||
234 | # We should handle arbitrary nesting of these B008.
|
||||
235 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^ B008
|
||||
222 | pass
|
||||
236 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:227:22: B008 Do not perform function call `map` in argument defaults
|
||||
B006_B008.py:241:22: B008 Do not perform function call `map` in argument defaults
|
||||
|
|
||||
225 | # Don't flag nested B006 since we can't guarantee that
|
||||
226 | # it isn't made mutable by the outer operation.
|
||||
227 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
239 | # Don't flag nested B006 since we can't guarantee that
|
||||
240 | # it isn't made mutable by the outer operation.
|
||||
241 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008
|
||||
228 | pass
|
||||
242 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:232:19: B008 Do not perform function call `random.randint` in argument defaults
|
||||
B006_B008.py:246:19: B008 Do not perform function call `random.randint` in argument defaults
|
||||
|
|
||||
231 | # B008-ception.
|
||||
232 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
245 | # B008-ception.
|
||||
246 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008
|
||||
233 | pass
|
||||
247 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:232:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
B006_B008.py:246:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
|
|
||||
231 | # B008-ception.
|
||||
232 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
245 | # B008-ception.
|
||||
246 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
| ^^^^^^^^^^^^^^^^^ B008
|
||||
233 | pass
|
||||
247 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Expr, Keyword};
|
||||
|
||||
pub(super) fn expr_name(func: &Expr) -> Option<&str> {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
use ruff_python_ast::{Expr, Keyword};
|
||||
|
||||
pub(super) fn exactly_one_argument_with_matching_function<'a>(
|
||||
name: &str,
|
||||
@@ -20,7 +12,8 @@ pub(super) fn exactly_one_argument_with_matching_function<'a>(
|
||||
if !keywords.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if expr_name(func)? != name {
|
||||
let func = func.as_name_expr()?;
|
||||
if func.id != name {
|
||||
return None;
|
||||
}
|
||||
Some(arg)
|
||||
@@ -31,8 +24,8 @@ pub(super) fn first_argument_with_matching_function<'a>(
|
||||
func: &Expr,
|
||||
args: &'a [Expr],
|
||||
) -> Option<&'a Expr> {
|
||||
if expr_name(func)? == name {
|
||||
Some(args.first()?)
|
||||
if func.as_name_expr().is_some_and(|func| func.id == name) {
|
||||
args.first()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::rules::flake8_comprehensions::fixes;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary `list` or `reversed` calls around `sorted`
|
||||
/// calls.
|
||||
@@ -57,10 +54,10 @@ pub(crate) fn unnecessary_call_around_sorted(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
) {
|
||||
let Some(outer) = helpers::expr_name(func) else {
|
||||
let Some(outer) = func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if !(outer == "list" || outer == "reversed") {
|
||||
if !matches!(outer.id.as_str(), "list" | "reversed") {
|
||||
return;
|
||||
}
|
||||
let Some(arg) = args.first() else {
|
||||
@@ -69,18 +66,18 @@ pub(crate) fn unnecessary_call_around_sorted(
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = arg else {
|
||||
return;
|
||||
};
|
||||
let Some(inner) = helpers::expr_name(func) else {
|
||||
let Some(inner) = func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if inner != "sorted" {
|
||||
if inner.id != "sorted" {
|
||||
return;
|
||||
}
|
||||
if !checker.semantic().is_builtin(inner) || !checker.semantic().is_builtin(outer) {
|
||||
if !checker.semantic().is_builtin(&inner.id) || !checker.semantic().is_builtin(&outer.id) {
|
||||
return;
|
||||
}
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryCallAroundSorted {
|
||||
func: outer.to_string(),
|
||||
func: outer.id.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
@@ -91,7 +88,7 @@ pub(crate) fn unnecessary_call_around_sorted(
|
||||
checker.stylist(),
|
||||
expr,
|
||||
)?;
|
||||
if outer == "reversed" {
|
||||
if outer.id == "reversed" {
|
||||
Ok(Fix::suggested(edit))
|
||||
} else {
|
||||
Ok(Fix::automatic(edit))
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use ruff_python_ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::rules::flake8_comprehensions::fixes;
|
||||
use crate::rules::flake8_comprehensions::settings::Settings;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary `dict`, `list` or `tuple` calls that can be
|
||||
/// rewritten as empty literals.
|
||||
@@ -63,10 +60,10 @@ pub(crate) fn unnecessary_collection_call(
|
||||
if !args.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(id) = helpers::expr_name(func) else {
|
||||
let Some(func) = func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
match id {
|
||||
match func.id.as_str() {
|
||||
"dict"
|
||||
if keywords.is_empty()
|
||||
|| (!settings.allow_dict_calls_with_keyword_arguments
|
||||
@@ -79,12 +76,12 @@ pub(crate) fn unnecessary_collection_call(
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
if !checker.semantic().is_builtin(id) {
|
||||
if !checker.semantic().is_builtin(func.id.as_str()) {
|
||||
return;
|
||||
}
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryCollectionCall {
|
||||
obj_type: id.to_string(),
|
||||
obj_type: func.id.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
use ruff_python_ast::{self as ast, Comprehension, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Comprehension, Expr, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::rules::flake8_comprehensions::fixes;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary `dict`, `list`, and `set` comprehension.
|
||||
///
|
||||
@@ -88,28 +85,28 @@ pub(crate) fn unnecessary_dict_comprehension(
|
||||
if !generator.ifs.is_empty() || generator.is_async {
|
||||
return;
|
||||
}
|
||||
let Some(key_id) = helpers::expr_name(key) else {
|
||||
let Some(key) = key.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(value_id) = helpers::expr_name(value) else {
|
||||
let Some(value) = value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = &generator.target else {
|
||||
return;
|
||||
};
|
||||
if elts.len() != 2 {
|
||||
return;
|
||||
}
|
||||
let Some(target_key_id) = helpers::expr_name(&elts[0]) else {
|
||||
let [target_key, target_value] = elts.as_slice() else {
|
||||
return;
|
||||
};
|
||||
if target_key_id != key_id {
|
||||
return;
|
||||
}
|
||||
let Some(target_value_id) = helpers::expr_name(&elts[1]) else {
|
||||
let Some(target_key) = target_key.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if target_value_id != value_id {
|
||||
let Some(target_value) = target_value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if target_key.id != key.id {
|
||||
return;
|
||||
}
|
||||
if target_value.id != value.id {
|
||||
return;
|
||||
}
|
||||
add_diagnostic(checker, expr);
|
||||
@@ -128,13 +125,13 @@ pub(crate) fn unnecessary_list_set_comprehension(
|
||||
if !generator.ifs.is_empty() || generator.is_async {
|
||||
return;
|
||||
}
|
||||
let Some(elt_id) = helpers::expr_name(elt) else {
|
||||
let Some(elt) = elt.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(target_id) = helpers::expr_name(&generator.target) else {
|
||||
let Some(target) = generator.target.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if elt_id != target_id {
|
||||
if elt.id != target.id {
|
||||
return;
|
||||
}
|
||||
add_diagnostic(checker, expr);
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableKeyword;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::rules::flake8_comprehensions::fixes;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary `list`, `reversed`, `set`, `sorted`, and `tuple`
|
||||
/// call within `list`, `set`, `sorted`, and `tuple` calls.
|
||||
@@ -72,15 +69,13 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||
args: &[Expr],
|
||||
outer_kw: &[Keyword],
|
||||
) {
|
||||
let Some(outer) = helpers::expr_name(func) else {
|
||||
let Some(outer) = func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if !(outer == "list"
|
||||
|| outer == "tuple"
|
||||
|| outer == "set"
|
||||
|| outer == "reversed"
|
||||
|| outer == "sorted")
|
||||
{
|
||||
if !matches!(
|
||||
outer.id.as_str(),
|
||||
"list" | "tuple" | "set" | "reversed" | "sorted"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let Some(arg) = args.first() else {
|
||||
@@ -96,16 +91,16 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(inner) = helpers::expr_name(func) else {
|
||||
let Some(inner) = func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if !checker.semantic().is_builtin(inner) || !checker.semantic().is_builtin(outer) {
|
||||
if !checker.semantic().is_builtin(&inner.id) || !checker.semantic().is_builtin(&outer.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid collapsing nested `sorted` calls with non-identical keyword arguments
|
||||
// (i.e., `key`, `reverse`).
|
||||
if inner == "sorted" && outer == "sorted" {
|
||||
if inner.id == "sorted" && outer.id == "sorted" {
|
||||
if inner_kw.len() != outer_kw.len() {
|
||||
return;
|
||||
}
|
||||
@@ -118,18 +113,19 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||
}
|
||||
}
|
||||
|
||||
// Ex) set(tuple(...))
|
||||
// Ex) list(tuple(...))
|
||||
// Ex) set(set(...))
|
||||
if ((outer == "set" || outer == "sorted")
|
||||
&& (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "sorted"))
|
||||
|| (outer == "set" && inner == "set")
|
||||
|| ((outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple"))
|
||||
{
|
||||
// Ex) `set(tuple(...))`
|
||||
// Ex) `list(tuple(...))`
|
||||
// Ex) `set(set(...))`
|
||||
if matches!(
|
||||
(outer.id.as_str(), inner.id.as_str()),
|
||||
("set" | "sorted", "list" | "tuple" | "reversed" | "sorted")
|
||||
| ("set", "set")
|
||||
| ("list" | "tuple", "list" | "tuple")
|
||||
) {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryDoubleCastOrProcess {
|
||||
inner: inner.to_string(),
|
||||
outer: outer.to_string(),
|
||||
inner: inner.id.to_string(),
|
||||
outer: outer.id.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
|
||||
@@ -70,7 +70,7 @@ pub(crate) fn unnecessary_literal_dict(
|
||||
// Accept `dict((1, 2), ...))` `dict([(1, 2), ...])`.
|
||||
if !elts
|
||||
.iter()
|
||||
.all(|elt| matches!(&elt, Expr::Tuple(ast::ExprTuple { elts, .. } )if elts.len() == 2))
|
||||
.all(|elt| matches!(&elt, Expr::Tuple(ast::ExprTuple { elts, .. }) if elts.len() == 2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,11 +68,11 @@ pub(crate) fn unnecessary_map(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
) {
|
||||
let Some(id) = helpers::expr_name(func) else {
|
||||
let Some(func) = func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let object_type = match id {
|
||||
let object_type = match func.id.as_str() {
|
||||
"map" => ObjectType::Generator,
|
||||
"list" => ObjectType::List,
|
||||
"set" => ObjectType::Set,
|
||||
@@ -80,20 +80,20 @@ pub(crate) fn unnecessary_map(
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !checker.semantic().is_builtin(id) {
|
||||
if !checker.semantic().is_builtin(&func.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
match object_type {
|
||||
ObjectType::Generator => {
|
||||
// Exclude the parent if already matched by other arms.
|
||||
if let Some(Expr::Call(ast::ExprCall { func, .. })) = parent {
|
||||
if let Some(name) = helpers::expr_name(func) {
|
||||
if matches!(name, "list" | "set" | "dict") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
if parent
|
||||
.and_then(ruff_python_ast::Expr::as_call_expr)
|
||||
.and_then(|call| call.func.as_name_expr())
|
||||
.is_some_and(|name| matches!(name.id.as_str(), "list" | "set" | "dict"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only flag, e.g., `map(lambda x: x + 1, iterable)`.
|
||||
let [Expr::Lambda(ast::ExprLambda {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use num_bigint::BigInt;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Ranged, UnaryOp};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Ranged, UnaryOp};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary subscript reversal of iterable.
|
||||
///
|
||||
@@ -52,13 +50,13 @@ pub(crate) fn unnecessary_subscript_reversal(
|
||||
let Some(first_arg) = args.first() else {
|
||||
return;
|
||||
};
|
||||
let Some(id) = helpers::expr_name(func) else {
|
||||
let Some(func) = func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if !(id == "set" || id == "sorted" || id == "reversed") {
|
||||
if !matches!(func.id.as_str(), "reversed" | "set" | "sorted") {
|
||||
return;
|
||||
}
|
||||
if !checker.semantic().is_builtin(id) {
|
||||
if !checker.semantic().is_builtin(&func.id) {
|
||||
return;
|
||||
}
|
||||
let Expr::Subscript(ast::ExprSubscript { slice, .. }) = first_arg else {
|
||||
@@ -99,7 +97,7 @@ pub(crate) fn unnecessary_subscript_reversal(
|
||||
};
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UnnecessarySubscriptReversal {
|
||||
func: id.to_string(),
|
||||
func: func.id.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
|
||||
@@ -8,6 +8,44 @@ use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usage of `datetime.datetime.fromtimestamp()` without a `tz`
|
||||
/// argument.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python datetime objects can be naive or timezone-aware. While an aware
|
||||
/// object represents a specific moment in time, a naive object does not
|
||||
/// contain enough information to unambiguously locate itself relative to other
|
||||
/// datetime objects. Since this can lead to errors, it is recommended to
|
||||
/// always use timezone-aware objects.
|
||||
///
|
||||
/// `datetime.datetime.fromtimestamp(ts)` returns a naive datetime object.
|
||||
/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=)` to return a
|
||||
/// timezone-aware object.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc)
|
||||
/// ```
|
||||
///
|
||||
/// Or, for Python 3.11 and later:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[violation]
|
||||
pub struct CallDatetimeFromtimestamp;
|
||||
|
||||
@@ -20,7 +58,6 @@ impl Violation for CallDatetimeFromtimestamp {
|
||||
}
|
||||
}
|
||||
|
||||
/// DTZ006
|
||||
pub(crate) fn call_datetime_fromtimestamp(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if !checker
|
||||
.semantic()
|
||||
|
||||
@@ -8,6 +8,42 @@ use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usage of `datetime.datetime.now()` without a `tz` argument.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python datetime objects can be naive or timezone-aware. While an aware
|
||||
/// object represents a specific moment in time, a naive object does not
|
||||
/// contain enough information to unambiguously locate itself relative to other
|
||||
/// datetime objects. Since this can lead to errors, it is recommended to
|
||||
/// always use timezone-aware objects.
|
||||
///
|
||||
/// `datetime.datetime.now()` returns a naive datetime object. Instead, use
|
||||
/// `datetime.datetime.now(tz=)` to return a timezone-aware object.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.now()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.now(tz=datetime.timezone.utc)
|
||||
/// ```
|
||||
///
|
||||
/// Or, for Python 3.11 and later:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.now(tz=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[violation]
|
||||
pub struct CallDatetimeNowWithoutTzinfo;
|
||||
|
||||
@@ -18,7 +54,6 @@ impl Violation for CallDatetimeNowWithoutTzinfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// DTZ005
|
||||
pub(crate) fn call_datetime_now_without_tzinfo(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if !checker
|
||||
.semantic()
|
||||
|
||||
@@ -83,13 +83,13 @@ pub(crate) fn all_with_model_form(
|
||||
continue;
|
||||
};
|
||||
match value {
|
||||
Constant::Str(s) => {
|
||||
if s == "__all__" {
|
||||
Constant::Str(ast::StringConstant { value, .. }) => {
|
||||
if value == "__all__" {
|
||||
return Some(Diagnostic::new(DjangoAllWithModelForm, element.range()));
|
||||
}
|
||||
}
|
||||
Constant::Bytes(b) => {
|
||||
if b == "__all__".as_bytes() {
|
||||
Constant::Bytes(ast::BytesConstant { value, .. }) => {
|
||||
if value == "__all__".as_bytes() {
|
||||
return Some(Diagnostic::new(DjangoAllWithModelForm, element.range()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@ use crate::checkers::ast::Checker;
|
||||
/// annotations at evaluation time, making the code compatible with both past
|
||||
/// and future Python versions.
|
||||
///
|
||||
/// This rule respects the [`target-version`] setting. For example, if your
|
||||
/// project targets Python 3.10 and above, adding `from __future__ import annotations`
|
||||
/// does not impact your ability to leverage PEP 604-style unions (e.g., to
|
||||
/// convert `Optional[str]` to `str | None`). As such, this rule will only
|
||||
/// flag such usages if your project targets Python 3.9 or below.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def func(obj: dict[str, int | None]) -> None:
|
||||
@@ -34,6 +40,9 @@ use crate::checkers::ast::Checker;
|
||||
/// def func(obj: dict[str, int | None]) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `target-version`
|
||||
#[violation]
|
||||
pub struct FutureRequiredTypeAnnotation {
|
||||
reason: Reason,
|
||||
|
||||
@@ -13,14 +13,25 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// PEP 563 enabled the use of a number of convenient type annotations, such as
|
||||
/// `list[str]` instead of `List[str]`, or `str | None` instead of
|
||||
/// `Optional[str]`. However, these annotations are only available on Python
|
||||
/// 3.9 and higher, _unless_ the `from __future__ import annotations` import is present.
|
||||
/// `list[str]` instead of `List[str]`. However, these annotations are only
|
||||
/// available on Python 3.9 and higher, _unless_ the `from __future__ import annotations`
|
||||
/// import is present.
|
||||
///
|
||||
/// Similarly, PEP 604 enabled the use of the `|` operator for unions, such as
|
||||
/// `str | None` instead of `Optional[str]`. However, these annotations are only
|
||||
/// available on Python 3.10 and higher, _unless_ the `from __future__ import annotations`
|
||||
/// import is present.
|
||||
///
|
||||
/// By adding the `__future__` import, the pyupgrade rules can automatically
|
||||
/// migrate existing code to use the new syntax, even for older Python versions.
|
||||
/// This rule thus pairs well with pyupgrade and with Ruff's pyupgrade rules.
|
||||
///
|
||||
/// This rule respects the [`target-version`] setting. For example, if your
|
||||
/// project targets Python 3.10 and above, adding `from __future__ import annotations`
|
||||
/// does not impact your ability to leverage PEP 604-style unions (e.g., to
|
||||
/// convert `Optional[str]` to `str | None`). As such, this rule will only
|
||||
/// flag such usages if your project targets Python 3.9 or below.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import List, Dict, Optional
|
||||
|
||||
@@ -43,6 +43,11 @@ impl Violation for UnnecessaryTypeUnion {
|
||||
|
||||
/// PYI055
|
||||
pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr) {
|
||||
// The `|` operator isn't always safe to allow to runtime-evaluated annotations.
|
||||
if checker.semantic().execution_context().is_runtime() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut type_exprs = Vec::new();
|
||||
|
||||
// Check if `union` is a PEP604 union (e.g. `float | int`) or a `typing.Union[float, int]`
|
||||
|
||||
@@ -123,7 +123,7 @@ pub(crate) fn unrecognized_platform(checker: &mut Checker, test: &Expr) {
|
||||
}
|
||||
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
..
|
||||
}) = right
|
||||
{
|
||||
|
||||
@@ -1,4 +1,25 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI050.py:13:24: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
13 | def foo_no_return(arg: NoReturn):
|
||||
| ^^^^^^^^ PYI050
|
||||
14 | ...
|
||||
|
|
||||
|
||||
PYI050.py:23:44: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
23 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn):
|
||||
| ^^^^^^^^ PYI050
|
||||
24 | ...
|
||||
|
|
||||
|
||||
PYI050.py:27:47: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
27 | def foo_no_return_pos_only(arg: int, /, arg2: NoReturn):
|
||||
| ^^^^^^^^ PYI050
|
||||
28 | ...
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI050.pyi:6:24: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations
|
||||
PYI050.pyi:6:24: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
4 | def foo(arg): ...
|
||||
5 | def foo_int(arg: int): ...
|
||||
@@ -11,7 +11,7 @@ PYI050.pyi:6:24: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for arg
|
||||
8 | arg: typing_extensions.NoReturn,
|
||||
|
|
||||
|
||||
PYI050.pyi:10:44: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations
|
||||
PYI050.pyi:10:44: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
8 | arg: typing_extensions.NoReturn,
|
||||
9 | ): ... # Error: PYI050
|
||||
@@ -21,7 +21,7 @@ PYI050.pyi:10:44: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for ar
|
||||
12 | def foo_never(arg: Never): ...
|
||||
|
|
||||
|
||||
PYI050.pyi:11:47: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations
|
||||
PYI050.pyi:11:47: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
9 | ): ... # Error: PYI050
|
||||
10 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050
|
||||
|
||||
@@ -1,56 +1,12 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI055.py:5:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
|
||||
|
|
||||
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
6 | x: type[int] | type[str] | type[float]
|
||||
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
|
|
||||
|
||||
PYI055.py:6:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | float]`.
|
||||
|
|
||||
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
6 | x: type[int] | type[str] | type[float]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
8 | z: Union[type[float], type[complex]]
|
||||
|
|
||||
|
||||
PYI055.py:7:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
|
||||
|
|
||||
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
6 | x: type[int] | type[str] | type[float]
|
||||
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
8 | z: Union[type[float], type[complex]]
|
||||
9 | z: Union[type[float, int], type[complex]]
|
||||
|
|
||||
|
||||
PYI055.py:8:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, complex]]`.
|
||||
|
|
||||
6 | x: type[int] | type[str] | type[float]
|
||||
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
8 | z: Union[type[float], type[complex]]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
9 | z: Union[type[float, int], type[complex]]
|
||||
|
|
||||
|
||||
PYI055.py:9:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`.
|
||||
|
|
||||
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
8 | z: Union[type[float], type[complex]]
|
||||
9 | z: Union[type[float, int], type[complex]]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
|
|
||||
|
||||
PYI055.py:12:15: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | float]`.
|
||||
PYI055.py:31:11: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
|
||||
|
|
||||
12 | def func(arg: type[int] | str | type[float]) -> None: ...
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
13 |
|
||||
14 | # OK
|
||||
29 | def func():
|
||||
30 | # PYI055
|
||||
31 | item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,56 +1,79 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI055.pyi:5:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
|
||||
PYI055.pyi:4:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
|
||||
|
|
||||
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
2 | from typing import Union
|
||||
3 |
|
||||
4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
6 | x: type[int] | type[str] | type[float]
|
||||
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
5 | x: type[int] | type[str] | type[float]
|
||||
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
|
|
||||
|
||||
PYI055.pyi:6:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | float]`.
|
||||
PYI055.pyi:5:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | float]`.
|
||||
|
|
||||
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
6 | x: type[int] | type[str] | type[float]
|
||||
4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
5 | x: type[int] | type[str] | type[float]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
8 | z: Union[type[float], type[complex]]
|
||||
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
7 | z: Union[type[float], type[complex]]
|
||||
|
|
||||
|
||||
PYI055.pyi:7:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
|
||||
PYI055.pyi:6:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
|
||||
|
|
||||
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
6 | x: type[int] | type[str] | type[float]
|
||||
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
5 | x: type[int] | type[str] | type[float]
|
||||
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
8 | z: Union[type[float], type[complex]]
|
||||
9 | z: Union[type[float, int], type[complex]]
|
||||
7 | z: Union[type[float], type[complex]]
|
||||
8 | z: Union[type[float, int], type[complex]]
|
||||
|
|
||||
|
||||
PYI055.pyi:8:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, complex]]`.
|
||||
PYI055.pyi:7:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, complex]]`.
|
||||
|
|
||||
6 | x: type[int] | type[str] | type[float]
|
||||
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
8 | z: Union[type[float], type[complex]]
|
||||
5 | x: type[int] | type[str] | type[float]
|
||||
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
7 | z: Union[type[float], type[complex]]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
9 | z: Union[type[float, int], type[complex]]
|
||||
8 | z: Union[type[float, int], type[complex]]
|
||||
|
|
||||
|
||||
PYI055.pyi:9:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`.
|
||||
|
|
||||
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
8 | z: Union[type[float], type[complex]]
|
||||
9 | z: Union[type[float, int], type[complex]]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
|
|
||||
|
||||
PYI055.pyi:12:15: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | float]`.
|
||||
PYI055.pyi:8:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`.
|
||||
|
|
||||
12 | def func(arg: type[int] | str | type[float]) -> None: ...
|
||||
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
7 | z: Union[type[float], type[complex]]
|
||||
8 | z: Union[type[float, int], type[complex]]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
9 |
|
||||
10 | def func(arg: type[int] | str | type[float]) -> None: ...
|
||||
|
|
||||
|
||||
PYI055.pyi:10:15: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | float]`.
|
||||
|
|
||||
8 | z: Union[type[float, int], type[complex]]
|
||||
9 |
|
||||
10 | def func(arg: type[int] | str | type[float]) -> None: ...
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
13 |
|
||||
14 | # OK
|
||||
11 |
|
||||
12 | # OK
|
||||
|
|
||||
|
||||
PYI055.pyi:20:7: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
|
||||
|
|
||||
19 | # OK
|
||||
20 | item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
21 |
|
||||
22 | def func():
|
||||
|
|
||||
|
||||
PYI055.pyi:24:11: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
|
||||
|
|
||||
22 | def func():
|
||||
23 | # PYI055
|
||||
24 | item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -169,6 +169,12 @@ mod tests {
|
||||
Settings::default(),
|
||||
"PT013"
|
||||
)]
|
||||
#[test_case(
|
||||
Rule::PytestDuplicateParametrizeTestCases,
|
||||
Path::new("PT014.py"),
|
||||
Settings::default(),
|
||||
"PT014"
|
||||
)]
|
||||
#[test_case(
|
||||
Rule::PytestAssertAlwaysFalse,
|
||||
Path::new("PT015.py"),
|
||||
@@ -250,6 +256,18 @@ mod tests {
|
||||
Settings::default(),
|
||||
"PT026"
|
||||
)]
|
||||
#[test_case(
|
||||
Rule::PytestUnittestRaisesAssertion,
|
||||
Path::new("PT027_0.py"),
|
||||
Settings::default(),
|
||||
"PT027_0"
|
||||
)]
|
||||
#[test_case(
|
||||
Rule::PytestUnittestRaisesAssertion,
|
||||
Path::new("PT027_1.py"),
|
||||
Settings::default(),
|
||||
"PT027_1"
|
||||
)]
|
||||
fn test_pytest_style(
|
||||
rule_code: Rule,
|
||||
path: &Path,
|
||||
|
||||
@@ -7,12 +7,14 @@ use libcst_native::{
|
||||
ParenthesizedNode, SimpleStatementLine, SimpleWhitespace, SmallStatement, Statement,
|
||||
TrailingWhitespace, UnaryOperation,
|
||||
};
|
||||
use ruff_python_ast::{self as ast, BoolOp, ExceptHandler, Expr, Keyword, Ranged, Stmt, UnaryOp};
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::Truthiness;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arguments, BoolOp, ExceptHandler, Expr, Keyword, Ranged, Stmt, UnaryOp,
|
||||
};
|
||||
use ruff_python_ast::{visitor, whitespace};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_source_file::Locator;
|
||||
@@ -21,6 +23,7 @@ use crate::autofix::codemods::CodegenStylist;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::cst::matchers::match_indented_block;
|
||||
use crate::cst::matchers::match_module;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
use super::unittest_assert::UnittestAssert;
|
||||
@@ -89,6 +92,9 @@ impl Violation for PytestCompositeAssertion {
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// def test_foo():
|
||||
/// with pytest.raises(ZeroDivisionError) as exc_info:
|
||||
/// 1 / 0
|
||||
@@ -127,6 +133,9 @@ impl Violation for PytestAssertInExcept {
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// def test_foo():
|
||||
/// if some_condition:
|
||||
/// pytest.fail("some_condition was True")
|
||||
@@ -145,6 +154,36 @@ impl Violation for PytestAssertAlwaysFalse {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of assertion methods from the `unittest` module.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// To make use of `pytest`'s assertion rewriting, a regular `assert` statement
|
||||
/// is preferred over `unittest`'s assertion methods.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import unittest
|
||||
///
|
||||
///
|
||||
/// class TestFoo(unittest.TestCase):
|
||||
/// def test_foo(self):
|
||||
/// self.assertEqual(a, b)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import unittest
|
||||
///
|
||||
///
|
||||
/// class TestFoo(unittest.TestCase):
|
||||
/// def test_foo(self):
|
||||
/// assert a == b
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [`pytest` documentation: Assertion introspection details](https://docs.pytest.org/en/7.1.x/how-to/assert.html#assertion-introspection-details)
|
||||
|
||||
#[violation]
|
||||
pub struct PytestUnittestAssertion {
|
||||
assertion: String,
|
||||
@@ -267,6 +306,186 @@ pub(crate) fn unittest_assertion(
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of exception-related assertion methods from the `unittest`
|
||||
/// module.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// To enforce the assertion style recommended by `pytest`, `pytest.raises` is
|
||||
/// preferred over the exception-related assertion methods in `unittest`, like
|
||||
/// `assertRaises`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import unittest
|
||||
///
|
||||
///
|
||||
/// class TestFoo(unittest.TestCase):
|
||||
/// def test_foo(self):
|
||||
/// with self.assertRaises(ValueError):
|
||||
/// raise ValueError("foo")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import unittest
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// class TestFoo(unittest.TestCase):
|
||||
/// def test_foo(self):
|
||||
/// with pytest.raises(ValueError):
|
||||
/// raise ValueError("foo")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [`pytest` documentation: Assertions about expected exceptions](https://docs.pytest.org/en/latest/how-to/assert.html#assertions-about-expected-exceptions)
|
||||
#[violation]
|
||||
pub struct PytestUnittestRaisesAssertion {
|
||||
assertion: String,
|
||||
}
|
||||
|
||||
impl Violation for PytestUnittestRaisesAssertion {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let PytestUnittestRaisesAssertion { assertion } = self;
|
||||
format!("Use `pytest.raises` instead of unittest-style `{assertion}`")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
let PytestUnittestRaisesAssertion { assertion } = self;
|
||||
Some(format!("Replace `{assertion}` with `pytest.raises`"))
|
||||
}
|
||||
}
|
||||
|
||||
/// PT027
|
||||
pub(crate) fn unittest_raises_assertion(
|
||||
checker: &Checker,
|
||||
call: &ast::ExprCall,
|
||||
) -> Option<Diagnostic> {
|
||||
let Expr::Attribute(ast::ExprAttribute { attr, .. }) = call.func.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if !matches!(
|
||||
attr.as_str(),
|
||||
"assertRaises" | "failUnlessRaises" | "assertRaisesRegex" | "assertRaisesRegexp"
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
PytestUnittestRaisesAssertion {
|
||||
assertion: attr.to_string(),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule())
|
||||
&& !checker.indexer().has_comments(call, checker.locator())
|
||||
{
|
||||
if let Some(args) = to_pytest_raises_args(checker, attr.as_str(), &call.arguments) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("pytest", "raises"),
|
||||
call.func.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let edit = Edit::range_replacement(format!("{binding}({args})"), call.range());
|
||||
Ok(Fix::suggested_edits(import_edit, [edit]))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Some(diagnostic)
|
||||
}
|
||||
|
||||
fn to_pytest_raises_args<'a>(
|
||||
checker: &Checker<'a>,
|
||||
attr: &str,
|
||||
arguments: &Arguments,
|
||||
) -> Option<Cow<'a, str>> {
|
||||
let args = match attr {
|
||||
"assertRaises" | "failUnlessRaises" => {
|
||||
match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
|
||||
// Ex) `assertRaises(Exception)`
|
||||
([arg], []) => Cow::Borrowed(checker.locator().slice(arg.range())),
|
||||
// Ex) `assertRaises(expected_exception=Exception)`
|
||||
([], [kwarg])
|
||||
if kwarg
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|id| id.as_str() == "expected_exception") =>
|
||||
{
|
||||
Cow::Borrowed(checker.locator().slice(kwarg.value.range()))
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
"assertRaisesRegex" | "assertRaisesRegexp" => {
|
||||
match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
|
||||
// Ex) `assertRaisesRegex(Exception, regex)`
|
||||
([arg1, arg2], []) => Cow::Owned(format!(
|
||||
"{}, match={}",
|
||||
checker.locator().slice(arg1.range()),
|
||||
checker.locator().slice(arg2.range())
|
||||
)),
|
||||
// Ex) `assertRaisesRegex(Exception, expected_regex=regex)`
|
||||
([arg], [kwarg])
|
||||
if kwarg
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|arg| arg.as_str() == "expected_regex") =>
|
||||
{
|
||||
Cow::Owned(format!(
|
||||
"{}, match={}",
|
||||
checker.locator().slice(arg.range()),
|
||||
checker.locator().slice(kwarg.value.range())
|
||||
))
|
||||
}
|
||||
// Ex) `assertRaisesRegex(expected_exception=Exception, expected_regex=regex)`
|
||||
([], [kwarg1, kwarg2])
|
||||
if kwarg1
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|id| id.as_str() == "expected_exception")
|
||||
&& kwarg2
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|id| id.as_str() == "expected_regex") =>
|
||||
{
|
||||
Cow::Owned(format!(
|
||||
"{}, match={}",
|
||||
checker.locator().slice(kwarg1.value.range()),
|
||||
checker.locator().slice(kwarg2.value.range())
|
||||
))
|
||||
}
|
||||
// Ex) `assertRaisesRegex(expected_regex=regex, expected_exception=Exception)`
|
||||
([], [kwarg1, kwarg2])
|
||||
if kwarg1
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|id| id.as_str() == "expected_regex")
|
||||
&& kwarg2
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|id| id.as_str() == "expected_exception") =>
|
||||
{
|
||||
Cow::Owned(format!(
|
||||
"{}, match={}",
|
||||
checker.locator().slice(kwarg2.value.range()),
|
||||
checker.locator().slice(kwarg1.value.range())
|
||||
))
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(args)
|
||||
}
|
||||
|
||||
/// PT015
|
||||
pub(crate) fn assert_falsy(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
|
||||
if Truthiness::from_expr(test, |id| checker.semantic().is_builtin(id)).is_falsey() {
|
||||
|
||||
@@ -35,6 +35,9 @@ use super::helpers::{
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture
|
||||
/// def my_fixture():
|
||||
/// ...
|
||||
@@ -42,6 +45,9 @@ use super::helpers::{
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def my_fixture():
|
||||
/// ...
|
||||
@@ -74,6 +80,35 @@ impl AlwaysAutofixableViolation for PytestFixtureIncorrectParenthesesStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pytest.fixture` calls with positional arguments.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// For clarity and consistency, prefer using keyword arguments to specify
|
||||
/// fixture configuration.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture("module")
|
||||
/// def my_fixture():
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture(scope="module")
|
||||
/// def my_fixture():
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [`pytest` documentation: `@pytest.fixture` functions](https://docs.pytest.org/en/latest/reference/reference.html#pytest-fixture)
|
||||
#[violation]
|
||||
pub struct PytestFixturePositionalArgs {
|
||||
function: String,
|
||||
@@ -87,6 +122,34 @@ impl Violation for PytestFixturePositionalArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pytest.fixture` calls with `scope="function"`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `scope="function"` can be omitted, as it is the default.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture(scope="function")
|
||||
/// def my_fixture():
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def my_fixture():
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [`pytest` documentation: `@pytest.fixture` functions](https://docs.pytest.org/en/latest/reference/reference.html#pytest-fixture)
|
||||
#[violation]
|
||||
pub struct PytestExtraneousScopeFunction;
|
||||
|
||||
@@ -101,6 +164,50 @@ impl AlwaysAutofixableViolation for PytestExtraneousScopeFunction {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pytest` fixtures that do not return a value, but are not named
|
||||
/// with a leading underscore.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// By convention, fixtures that don't return a value should be named with a
|
||||
/// leading underscore, while fixtures that do return a value should not.
|
||||
///
|
||||
/// This rule ignores abstract fixtures and generators.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def patch_something(mocker):
|
||||
/// mocker.patch("module.object")
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def use_context():
|
||||
/// with create_context():
|
||||
/// yield
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def _patch_something(mocker):
|
||||
/// mocker.patch("module.object")
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def _use_context():
|
||||
/// with create_context():
|
||||
/// yield
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [`pytest` documentation: `@pytest.fixture` functions](https://docs.pytest.org/en/latest/reference/reference.html#pytest-fixture)
|
||||
#[violation]
|
||||
pub struct PytestMissingFixtureNameUnderscore {
|
||||
function: String,
|
||||
@@ -114,6 +221,52 @@ impl Violation for PytestMissingFixtureNameUnderscore {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pytest` fixtures that return a value, but are named with a
|
||||
/// leading underscore.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// By convention, fixtures that don't return a value should be named with a
|
||||
/// leading underscore, while fixtures that do return a value should not.
|
||||
///
|
||||
/// This rule ignores abstract fixtures.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def _some_object():
|
||||
/// return SomeClass()
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def _some_object_with_cleanup():
|
||||
/// obj = SomeClass()
|
||||
/// yield obj
|
||||
/// obj.cleanup()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def some_object():
|
||||
/// return SomeClass()
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def some_object_with_cleanup():
|
||||
/// obj = SomeClass()
|
||||
/// yield obj
|
||||
/// obj.cleanup()
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [`pytest` documentation: `@pytest.fixture` functions](https://docs.pytest.org/en/latest/reference/reference.html#pytest-fixture)
|
||||
#[violation]
|
||||
pub struct PytestIncorrectFixtureNameUnderscore {
|
||||
function: String,
|
||||
@@ -145,6 +298,9 @@ impl Violation for PytestIncorrectFixtureNameUnderscore {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture
|
||||
/// def _patch_something():
|
||||
/// ...
|
||||
@@ -156,6 +312,9 @@ impl Violation for PytestIncorrectFixtureNameUnderscore {
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture
|
||||
/// def _patch_something():
|
||||
/// ...
|
||||
@@ -239,6 +398,9 @@ impl Violation for PytestDeprecatedYieldFixture {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def my_fixture(request):
|
||||
/// resource = acquire_resource()
|
||||
@@ -248,6 +410,9 @@ impl Violation for PytestDeprecatedYieldFixture {
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def my_fixture():
|
||||
/// resource = acquire_resource()
|
||||
@@ -288,6 +453,9 @@ impl Violation for PytestFixtureFinalizerCallback {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def my_fixture():
|
||||
/// resource = acquire_resource()
|
||||
@@ -296,6 +464,9 @@ impl Violation for PytestFixtureFinalizerCallback {
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def my_fixture_with_teardown():
|
||||
/// resource = acquire_resource()
|
||||
@@ -337,6 +508,9 @@ impl AlwaysAutofixableViolation for PytestUselessYieldFixture {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def a():
|
||||
/// pass
|
||||
@@ -350,6 +524,9 @@ impl AlwaysAutofixableViolation for PytestUselessYieldFixture {
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// def a():
|
||||
/// pass
|
||||
@@ -384,6 +561,9 @@ impl AlwaysAutofixableViolation for PytestErroneousUseFixturesOnFixture {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.mark.asyncio()
|
||||
/// @pytest.fixture()
|
||||
/// async def my_fixture():
|
||||
@@ -392,6 +572,9 @@ impl AlwaysAutofixableViolation for PytestErroneousUseFixturesOnFixture {
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// @pytest.fixture()
|
||||
/// async def my_fixture():
|
||||
/// return 0
|
||||
|
||||
@@ -46,11 +46,11 @@ pub(super) fn is_pytest_parametrize(decorator: &Decorator, semantic: &SemanticMo
|
||||
|
||||
pub(super) fn keyword_is_literal(keyword: &Keyword, literal: &str) -> bool {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(string),
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
..
|
||||
}) = &keyword.value
|
||||
{
|
||||
string == literal
|
||||
value == literal
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -63,7 +63,7 @@ pub(super) fn is_empty_or_null_string(expr: &Expr) -> bool {
|
||||
..
|
||||
}) => string.is_empty(),
|
||||
Expr::Constant(constant) if constant.value.is_none() => true,
|
||||
Expr::FString(ast::ExprFString { values, range: _ }) => {
|
||||
Expr::FString(ast::ExprFString { values, .. }) => {
|
||||
values.iter().all(is_empty_or_null_string)
|
||||
}
|
||||
_ => false,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user