Compare commits
176 Commits
v0.0.284
...
fix/format
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b0b1ca26 | ||
|
|
6d88ac4ca2 | ||
|
|
268be0cf8e | ||
|
|
5df9ba716f | ||
|
|
0e3284244b | ||
|
|
d272874dfd | ||
|
|
ccac9681e1 | ||
|
|
b52cc84df6 | ||
|
|
fec6fc2fab | ||
|
|
ba4c27598a | ||
|
|
0f9ccfcad9 | ||
|
|
fa32cd9b6f | ||
|
|
0aad0c41f6 | ||
|
|
424b8d4ad2 | ||
|
|
abc5065fc7 | ||
|
|
37f4920e1e | ||
|
|
c0df99b965 | ||
|
|
7650c6ee45 | ||
|
|
7b14d17e39 | ||
|
|
4678f7dafe | ||
|
|
b182368008 | ||
|
|
e032fbd2e7 | ||
|
|
575b77aa52 | ||
|
|
17a26e6ff3 | ||
|
|
d5a51b4e45 | ||
|
|
83f68891e0 | ||
|
|
aafde6db28 | ||
|
|
2405536d03 | ||
|
|
f017555d53 | ||
|
|
be96e0041a | ||
|
|
3c2dd5e42e | ||
|
|
8b347cdaa9 | ||
|
|
2a8d24dd4b | ||
|
|
bb5fbb1b5c | ||
|
|
086e11087f | ||
|
|
1b7e4a12a9 | ||
|
|
da1697121e | ||
|
|
419615f29b | ||
|
|
a742a562fd | ||
|
|
129b19050a | ||
|
|
0dc23da1d0 | ||
|
|
c62e544cba | ||
|
|
7e9023b6f8 | ||
|
|
a489b96a65 | ||
|
|
17af12e57c | ||
|
|
648333b8b2 | ||
|
|
3849fa0cf1 | ||
|
|
59e533047a | ||
|
|
053b1145f0 | ||
|
|
6a5acde226 | ||
|
|
ea72d5feba | ||
|
|
2aeb27334d | ||
|
|
0cea4975fc | ||
|
|
3ceb6fbeb0 | ||
|
|
8e18f8018f | ||
|
|
8228429a70 | ||
|
|
1811312722 | ||
|
|
26bba11be6 | ||
|
|
a128fe5148 | ||
|
|
5892c691ea | ||
|
|
82e0a97b34 | ||
|
|
a8d7bbae6f | ||
|
|
1050142a58 | ||
|
|
db1c556508 | ||
|
|
a70807e1e1 | ||
|
|
d9bb51dee4 | ||
|
|
d0f2a8e424 | ||
|
|
1334232168 | ||
|
|
fa7442da2f | ||
|
|
4dc32a00d0 | ||
|
|
e3ecbe660e | ||
|
|
8c3a8c4fc6 | ||
|
|
dcc7226685 | ||
|
|
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 | ||
|
|
0c9ded9d84 | ||
|
|
c434bdd2bd | ||
|
|
8b24238d19 | ||
|
|
f2939c678b | ||
|
|
b05574babd | ||
|
|
0ef6af807b | ||
|
|
f091b46497 | ||
|
|
2cedb401bd | ||
|
|
2e5c81b202 | ||
|
|
cc151c35a8 | ||
|
|
563374503f | ||
|
|
95dea5c868 | ||
|
|
eb68addf97 | ||
|
|
9ff80a82b4 | ||
|
|
84ae00c395 | ||
|
|
1050c4e104 | ||
|
|
6706ae4828 | ||
|
|
dc3275fe7f | ||
|
|
50dab9cea6 | ||
|
|
4811af0f0b | ||
|
|
39beeb61f7 | ||
|
|
e2f7862404 | ||
|
|
ac5c8bb3b6 | ||
|
|
c1bc67686c | ||
|
|
7eea0e94a2 | ||
|
|
0252995973 | ||
|
|
627f475b91 | ||
|
|
395bb31247 |
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@3.0.0
|
||||
uses: cloudflare/wrangler-action@v3.1.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@3.0.0
|
||||
uses: cloudflare/wrangler-action@v3.1.0
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
@@ -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.284"
|
||||
version = "0.0.285"
|
||||
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.284"
|
||||
version = "0.0.285"
|
||||
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.284"
|
||||
version = "0.0.285"
|
||||
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.284
|
||||
rev: v0.0.285
|
||||
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.284"
|
||||
version = "0.0.285"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.284"
|
||||
version = "0.0.285"
|
||||
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" }
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ g_action.set_enabled(True)
|
||||
settings.set_enable_developer_extras(True)
|
||||
foo.is_(True)
|
||||
bar.is_not(False)
|
||||
next(iter([]), False)
|
||||
|
||||
class Registry:
|
||||
def __init__(self) -> None:
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -22,6 +22,10 @@ tuple(
|
||||
"o"]
|
||||
)
|
||||
)
|
||||
set(set())
|
||||
set(list())
|
||||
set(tuple())
|
||||
sorted(reversed())
|
||||
|
||||
# Nested sorts with differing keyword arguments. Not flagged.
|
||||
sorted(sorted(x, key=lambda y: y))
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import math # not checked
|
||||
def not_checked():
|
||||
import math
|
||||
|
||||
import altair # unconventional
|
||||
import matplotlib.pyplot # unconventional
|
||||
import numpy # unconventional
|
||||
import pandas # unconventional
|
||||
import seaborn # unconventional
|
||||
import tkinter # unconventional
|
||||
|
||||
import altair as altr # unconventional
|
||||
import matplotlib.pyplot as plot # unconventional
|
||||
import numpy as nmp # unconventional
|
||||
import pandas as pdas # unconventional
|
||||
import seaborn as sbrn # unconventional
|
||||
import tkinter as tkr # unconventional
|
||||
def unconventional():
|
||||
import altair
|
||||
import matplotlib.pyplot
|
||||
import numpy
|
||||
import pandas
|
||||
import seaborn
|
||||
import tkinter
|
||||
|
||||
import altair as alt # conventional
|
||||
import matplotlib.pyplot as plt # conventional
|
||||
import numpy as np # conventional
|
||||
import pandas as pd # conventional
|
||||
import seaborn as sns # conventional
|
||||
import tkinter as tk # conventional
|
||||
|
||||
def unconventional_aliases():
|
||||
import altair as altr
|
||||
import matplotlib.pyplot as plot
|
||||
import numpy as nmp
|
||||
import pandas as pdas
|
||||
import seaborn as sbrn
|
||||
import tkinter as tkr
|
||||
|
||||
|
||||
def conventional_aliases():
|
||||
import altair as alt
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import seaborn as sns
|
||||
import tkinter as tk
|
||||
|
||||
13
crates/ruff/resources/test/fixtures/flake8_pie/PIE808.py
vendored
Normal file
13
crates/ruff/resources/test/fixtures/flake8_pie/PIE808.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# PIE808
|
||||
range(0, 10)
|
||||
|
||||
# OK
|
||||
range(x, 10)
|
||||
range(-15, 10)
|
||||
range(10)
|
||||
range(0)
|
||||
range(0, 10, x)
|
||||
range(0, 10, 1)
|
||||
range(0, 10, step=1)
|
||||
range(start=0, stop=10)
|
||||
range(0, stop=10)
|
||||
@@ -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
|
||||
|
||||
@@ -64,3 +64,8 @@ def test_implicit_str_concat_no_parens(param1, param2, param3):
|
||||
@pytest.mark.parametrize((("param1, " "param2, " "param3")), [(1, 2, 3), (4, 5, 6)])
|
||||
def test_implicit_str_concat_with_multi_parens(param1, param2, param3):
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("param1,param2"), [(1, 2), (3, 4)])
|
||||
def test_csv_with_parens(param1, param2):
|
||||
...
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from pickle import PicklingError, UnpicklingError
|
||||
import socket
|
||||
|
||||
import pytest
|
||||
@@ -20,6 +21,12 @@ def test_error_no_argument_given():
|
||||
with pytest.raises(socket.error):
|
||||
raise ValueError("Can't divide 1 by 0")
|
||||
|
||||
with pytest.raises(PicklingError):
|
||||
raise PicklingError("Can't pickle")
|
||||
|
||||
with pytest.raises(UnpicklingError):
|
||||
raise UnpicklingError("Can't unpickle")
|
||||
|
||||
|
||||
def test_error_match_is_empty():
|
||||
with pytest.raises(ValueError, match=None):
|
||||
|
||||
53
crates/ruff/resources/test/fixtures/flake8_pytest_style/PT014.py
vendored
Normal file
53
crates/ruff/resources/test/fixtures/flake8_pytest_style/PT014.py
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [1, 1, 2])
|
||||
def test_error_literal(x):
|
||||
...
|
||||
|
||||
|
||||
a = 1
|
||||
b = 2
|
||||
c = 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [a, a, b, b, b, c])
|
||||
def test_error_expr_simple(x):
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"x",
|
||||
[
|
||||
(a, b),
|
||||
# comment
|
||||
(a, b),
|
||||
(b, c),
|
||||
],
|
||||
)
|
||||
def test_error_expr_complex(x):
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [a, b, (a), c, ((a))])
|
||||
def test_error_parentheses(x):
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"x",
|
||||
[
|
||||
a,
|
||||
b,
|
||||
(a),
|
||||
c,
|
||||
((a)),
|
||||
],
|
||||
)
|
||||
def test_error_parentheses_trailing_comma(x):
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [1, 2])
|
||||
def test_ok(x):
|
||||
...
|
||||
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
|
||||
@@ -19,11 +19,20 @@ raise TypeError ()
|
||||
raise TypeError \
|
||||
()
|
||||
|
||||
# RSE102
|
||||
raise TypeError \
|
||||
();
|
||||
|
||||
# RSE102
|
||||
raise TypeError(
|
||||
|
||||
)
|
||||
|
||||
# RSE102
|
||||
raise (TypeError) (
|
||||
|
||||
)
|
||||
|
||||
# RSE102
|
||||
raise TypeError(
|
||||
# Hello, world!
|
||||
@@ -52,3 +61,10 @@ class Class:
|
||||
|
||||
# OK
|
||||
raise Class.error()
|
||||
|
||||
|
||||
import ctypes
|
||||
|
||||
|
||||
# OK
|
||||
raise ctypes.WinError(1)
|
||||
|
||||
@@ -73,3 +73,7 @@ print(foo.__dict__)
|
||||
print(foo.__str__())
|
||||
print(foo().__class__)
|
||||
print(foo._asdict())
|
||||
|
||||
import os
|
||||
|
||||
os._exit()
|
||||
|
||||
@@ -31,6 +31,8 @@ for key in list(obj.keys()):
|
||||
|
||||
key in (obj or {}).keys() # SIM118
|
||||
|
||||
(key) in (obj or {}).keys() # SIM118
|
||||
|
||||
from typing import KeysView
|
||||
|
||||
|
||||
|
||||
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]
|
||||
@@ -27,6 +27,8 @@ def f(cls, x):
|
||||
###
|
||||
lambda x: print("Hello, world!")
|
||||
|
||||
lambda: print("Hello, world!")
|
||||
|
||||
|
||||
class C:
|
||||
###
|
||||
@@ -202,3 +204,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")
|
||||
|
||||
@@ -28,3 +28,6 @@ mdtypes_template = {
|
||||
'tag_full': [('mdtype', 'u4'), ('byte_count', 'u4')],
|
||||
'tag_smalldata':[('byte_count_mdtype', 'u4'), ('data', 'S4')],
|
||||
}
|
||||
|
||||
#: Okay
|
||||
a = (1,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -25,6 +25,12 @@ if (True) == TrueElement or x == TrueElement:
|
||||
if res == True != False:
|
||||
pass
|
||||
|
||||
if(True) == TrueElement or x == TrueElement:
|
||||
pass
|
||||
|
||||
if (yield i) == True:
|
||||
print("even")
|
||||
|
||||
#: Okay
|
||||
if x not in y:
|
||||
pass
|
||||
|
||||
@@ -61,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:
|
||||
...
|
||||
|
||||
@@ -133,3 +133,8 @@ def scope():
|
||||
from collections.abc import Callable
|
||||
|
||||
f: Callable[[str, int, list[str]], list[str]] = lambda a, b, /, c: [*c, a * b]
|
||||
|
||||
|
||||
class TemperatureScales(Enum):
|
||||
CELSIUS = (lambda deg_c: deg_c)
|
||||
FAHRENHEIT = (lambda deg_c: deg_c * 9 / 5 + 32)
|
||||
|
||||
@@ -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
|
||||
...
|
||||
@@ -14,7 +14,12 @@
|
||||
|
||||
"{:s} {:y}".format("hello", "world") # [bad-format-character]
|
||||
|
||||
"{:*^30s}".format("centered")
|
||||
"{:*^30s}".format("centered") # OK
|
||||
"{:{s}}".format("hello", s="s") # OK (nested replacement value not checked)
|
||||
"{:{s:y}}".format("hello", s="s") # [bad-format-character] (nested replacement format spec checked)
|
||||
"{0:.{prec}g}".format(1.23, prec=15) # OK
|
||||
"{0:.{foo}x{bar}y{foobar}g}".format(...) # OK (all nested replacements are consumed without considering in between chars)
|
||||
"{0:.{foo}{bar}{foobar}y}".format(...) # [bad-format-character] (check value after replacements)
|
||||
|
||||
## f-strings
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
class Person:
|
||||
class Person: # [eq-without-hash]
|
||||
def __init__(self):
|
||||
self.name = "monty"
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Person) and other.name == self.name
|
||||
|
||||
# OK
|
||||
class Language:
|
||||
def __init__(self):
|
||||
self.name = "python"
|
||||
@@ -14,3 +15,9 @@ class Language:
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
class MyClass:
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
|
||||
__hash__ = None
|
||||
|
||||
@@ -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")
|
||||
|
||||
62
crates/ruff/resources/test/fixtures/pylint/no_self_use.py
vendored
Normal file
62
crates/ruff/resources/test/fixtures/pylint/no_self_use.py
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import abc
|
||||
|
||||
|
||||
class Person:
|
||||
def developer_greeting(self, name): # [no-self-use]
|
||||
print(f"Greetings {name}!")
|
||||
|
||||
def greeting_1(self): # [no-self-use]
|
||||
print("Hello!")
|
||||
|
||||
def greeting_2(self): # [no-self-use]
|
||||
print("Hi!")
|
||||
|
||||
|
||||
# OK
|
||||
def developer_greeting():
|
||||
print("Greetings developer!")
|
||||
|
||||
|
||||
# OK
|
||||
class Person:
|
||||
name = "Paris"
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __cmp__(self, other):
|
||||
print(24)
|
||||
|
||||
def __repr__(self):
|
||||
return "Person"
|
||||
|
||||
def func(self):
|
||||
...
|
||||
|
||||
def greeting_1(self):
|
||||
print(f"Hello from {self.name} !")
|
||||
|
||||
@staticmethod
|
||||
def greeting_2():
|
||||
print("Hi!")
|
||||
|
||||
|
||||
class Base(abc.ABC):
|
||||
"""abstract class"""
|
||||
|
||||
@abstractmethod
|
||||
def abstract_method(self):
|
||||
"""abstract method could not be a function"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Sub(Base):
|
||||
@override
|
||||
def abstract_method(self):
|
||||
print("concret method")
|
||||
|
||||
|
||||
class Prop:
|
||||
@property
|
||||
def count(self):
|
||||
return 24
|
||||
@@ -32,3 +32,7 @@ foo not in {"a", "b", "c"} # Uses membership test already.
|
||||
foo == "a" # Single comparison.
|
||||
|
||||
foo != "a" # Single comparison.
|
||||
|
||||
foo == "a" == "b" or foo == "c" # Multiple comparisons.
|
||||
|
||||
foo == bar == "b" or foo == "c" # Multiple comparisons.
|
||||
|
||||
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.
|
||||
3
crates/ruff/resources/test/fixtures/pylint/sys_exit_alias_11.py
vendored
Normal file
3
crates/ruff/resources/test/fixtures/pylint/sys_exit_alias_11.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
from sys import *
|
||||
|
||||
exit(0)
|
||||
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")
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
//! Interface for generating autofix edits from higher-level actions (e.g., "remove an argument").
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arguments, ExceptHandler, Expr, Keyword, PySourceType, Ranged, Stmt,
|
||||
};
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Ranged, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::{lexer, AsMode};
|
||||
use ruff_python_trivia::{has_leading_content, is_python_whitespace, PythonWhitespace};
|
||||
|
||||
use ruff_python_trivia::{
|
||||
has_leading_content, is_python_whitespace, PythonWhitespace, SimpleTokenKind, SimpleTokenizer,
|
||||
};
|
||||
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
@@ -89,78 +89,49 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
argument: &T,
|
||||
arguments: &Arguments,
|
||||
parentheses: Parentheses,
|
||||
locator: &Locator,
|
||||
source_type: PySourceType,
|
||||
source: &str,
|
||||
) -> Result<Edit> {
|
||||
// TODO(sbrugman): Preserve trailing comments.
|
||||
if arguments.keywords.len() + arguments.args.len() > 1 {
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
// Partition into arguments before and after the argument to remove.
|
||||
let (before, after): (Vec<_>, Vec<_>) = arguments
|
||||
.args
|
||||
.iter()
|
||||
.map(Expr::range)
|
||||
.chain(arguments.keywords.iter().map(Keyword::range))
|
||||
.filter(|range| argument.range() != *range)
|
||||
.partition(|range| range.start() < argument.start());
|
||||
|
||||
if arguments
|
||||
.args
|
||||
.iter()
|
||||
.map(Expr::start)
|
||||
.chain(arguments.keywords.iter().map(Keyword::start))
|
||||
.any(|location| location > argument.start())
|
||||
{
|
||||
// Case 1: argument or keyword is _not_ the last node, so delete from the start of the
|
||||
// argument to the end of the subsequent comma.
|
||||
let mut seen_comma = false;
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(arguments.range()),
|
||||
source_type.as_mode(),
|
||||
arguments.start(),
|
||||
)
|
||||
.flatten()
|
||||
{
|
||||
if seen_comma {
|
||||
if tok.is_non_logical_newline() {
|
||||
// Also delete any non-logical newlines after the comma.
|
||||
continue;
|
||||
}
|
||||
fix_end = Some(if tok.is_newline() {
|
||||
range.end()
|
||||
} else {
|
||||
range.start()
|
||||
});
|
||||
break;
|
||||
}
|
||||
if range.start() == argument.start() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
if fix_start.is_some() && tok.is_comma() {
|
||||
seen_comma = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case 2: argument or keyword is the last node, so delete from the start of the
|
||||
// previous comma to the end of the argument.
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(arguments.range()),
|
||||
source_type.as_mode(),
|
||||
arguments.start(),
|
||||
)
|
||||
.flatten()
|
||||
{
|
||||
if range.start() == argument.start() {
|
||||
fix_end = Some(argument.end());
|
||||
break;
|
||||
}
|
||||
if tok.is_comma() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
}
|
||||
}
|
||||
if !after.is_empty() {
|
||||
// Case 1: argument or keyword is _not_ the last node, so delete from the start of the
|
||||
// argument to the end of the subsequent comma.
|
||||
let mut tokenizer = SimpleTokenizer::starts_at(argument.end(), source);
|
||||
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
|
||||
_ => {
|
||||
bail!("No fix could be constructed")
|
||||
}
|
||||
}
|
||||
// Find the trailing comma.
|
||||
tokenizer
|
||||
.find(|token| token.kind == SimpleTokenKind::Comma)
|
||||
.context("Unable to find trailing comma")?;
|
||||
|
||||
// Find the next non-whitespace token.
|
||||
let next = tokenizer
|
||||
.find(|token| {
|
||||
token.kind != SimpleTokenKind::Whitespace && token.kind != SimpleTokenKind::Newline
|
||||
})
|
||||
.context("Unable to find next token")?;
|
||||
|
||||
Ok(Edit::deletion(argument.start(), next.start()))
|
||||
} else if let Some(previous) = before.iter().map(Ranged::end).max() {
|
||||
// Case 2: argument or keyword is the last node, so delete from the start of the
|
||||
// previous comma to the end of the argument.
|
||||
let mut tokenizer = SimpleTokenizer::starts_at(previous, source);
|
||||
|
||||
// Find the trailing comma.
|
||||
let comma = tokenizer
|
||||
.find(|token| token.kind == SimpleTokenKind::Comma)
|
||||
.context("Unable to find trailing comma")?;
|
||||
|
||||
Ok(Edit::deletion(comma.start(), argument.end()))
|
||||
} else {
|
||||
// Only one argument; remove it (but preserve parentheses, if needed).
|
||||
// Case 3: argument or keyword is the only node, so delete the arguments (but preserve
|
||||
// parentheses, if needed).
|
||||
Ok(match parentheses {
|
||||
Parentheses::Remove => Edit::deletion(arguments.start(), arguments.end()),
|
||||
Parentheses::Preserve => {
|
||||
@@ -209,14 +180,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)
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::registry::{AsRule, Rule};
|
||||
|
||||
pub(crate) mod codemods;
|
||||
pub(crate) mod edits;
|
||||
pub(crate) mod snippet;
|
||||
pub(crate) mod source_map;
|
||||
|
||||
pub(crate) struct FixResult {
|
||||
|
||||
36
crates/ruff/src/autofix/snippet.rs
Normal file
36
crates/ruff/src/autofix/snippet.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
/// A snippet of source code for user-facing display, as in a diagnostic.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct SourceCodeSnippet(String);
|
||||
|
||||
impl SourceCodeSnippet {
|
||||
pub(crate) fn new(source_code: String) -> Self {
|
||||
Self(source_code)
|
||||
}
|
||||
|
||||
/// Return the full snippet for user-facing display, or `None` if the snippet should be
|
||||
/// truncated.
|
||||
pub(crate) fn full_display(&self) -> Option<&str> {
|
||||
if Self::should_truncate(&self.0) {
|
||||
None
|
||||
} else {
|
||||
Some(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a truncated snippet for user-facing display.
|
||||
pub(crate) fn truncated_display(&self) -> &str {
|
||||
if Self::should_truncate(&self.0) {
|
||||
"..."
|
||||
} else {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the source code should be truncated when included in a user-facing
|
||||
/// diagnostic.
|
||||
fn should_truncate(source_code: &str) -> bool {
|
||||
source_code.width() > 50 || source_code.contains(['\r', '\n'])
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
use ruff_python_ast::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
@@ -16,14 +17,20 @@ 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(),
|
||||
},
|
||||
binding.range,
|
||||
binding.range(),
|
||||
);
|
||||
if checker.patch(Rule::UnusedVariable) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
|
||||
@@ -7,10 +7,6 @@ use crate::rules::flake8_simplify;
|
||||
/// Run lint rules over a [`Comprehension`] syntax nodes.
|
||||
pub(crate) fn comprehension(comprehension: &Comprehension, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::InDictKeys) {
|
||||
flake8_simplify::rules::key_in_dict_for(
|
||||
checker,
|
||||
&comprehension.target,
|
||||
&comprehension.iter,
|
||||
);
|
||||
flake8_simplify::rules::key_in_dict_comprehension(checker, comprehension);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use ruff_python_ast::{self as ast, Stmt};
|
||||
use ruff_python_ast::Stmt;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_bugbear, perflint};
|
||||
use crate::rules::{flake8_bugbear, perflint, pyupgrade};
|
||||
|
||||
/// Run lint rules over all deferred for-loops in the [`SemanticModel`].
|
||||
pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||
@@ -11,18 +11,18 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||
for snapshot in for_loops {
|
||||
checker.semantic.restore(snapshot);
|
||||
|
||||
let Stmt::For(ast::StmtFor {
|
||||
target, iter, body, ..
|
||||
}) = checker.semantic.current_statement()
|
||||
else {
|
||||
let Stmt::For(stmt_for) = checker.semantic.current_statement() else {
|
||||
unreachable!("Expected Stmt::For");
|
||||
};
|
||||
|
||||
if checker.enabled(Rule::UnusedLoopControlVariable) {
|
||||
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
|
||||
flake8_bugbear::rules::unused_loop_control_variable(checker, stmt_for);
|
||||
}
|
||||
if checker.enabled(Rule::IncorrectDictIterator) {
|
||||
perflint::rules::incorrect_dict_iterator(checker, target, iter);
|
||||
perflint::rules::incorrect_dict_iterator(checker, stmt_for);
|
||||
}
|
||||
if checker.enabled(Rule::YieldInForLoop) {
|
||||
pyupgrade::rules::yield_in_for_loop(checker, stmt_for);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_semantic::analyze::{branch_detection, visibility};
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -29,6 +30,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
Rule::UnusedPrivateTypedDict,
|
||||
Rule::UnusedStaticMethodArgument,
|
||||
Rule::UnusedVariable,
|
||||
Rule::NoSelfUse,
|
||||
]) {
|
||||
return;
|
||||
}
|
||||
@@ -81,7 +83,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
pylint::rules::GlobalVariableNotAssigned {
|
||||
name: (*name).to_string(),
|
||||
},
|
||||
binding.range,
|
||||
binding.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -111,25 +113,21 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
branch_detection::different_forks(
|
||||
left,
|
||||
right,
|
||||
checker.semantic.statements(),
|
||||
)
|
||||
checker.semantic.different_branches(left, right)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let line = checker.locator.compute_line_index(shadowed.range.start());
|
||||
let line = checker.locator.compute_line_index(shadowed.start());
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::ImportShadowedByLoopVar {
|
||||
name: name.to_string(),
|
||||
line,
|
||||
},
|
||||
binding.range,
|
||||
binding.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -171,7 +169,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(statement_id) = shadowed.source else {
|
||||
let Some(node_id) = shadowed.source else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -179,7 +177,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
if shadowed.kind.is_function_definition() {
|
||||
if checker
|
||||
.semantic
|
||||
.statement(statement_id)
|
||||
.statement(node_id)
|
||||
.as_function_def_stmt()
|
||||
.is_some_and(|function| {
|
||||
visibility::is_overload(
|
||||
@@ -207,24 +205,20 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
branch_detection::different_forks(
|
||||
left,
|
||||
right,
|
||||
checker.semantic.statements(),
|
||||
)
|
||||
checker.semantic.different_branches(left, right)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let line = checker.locator.compute_line_index(shadowed.range.start());
|
||||
let line = checker.locator.compute_line_index(shadowed.start());
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
pyflakes::rules::RedefinedWhileUnused {
|
||||
name: (*name).to_string(),
|
||||
line,
|
||||
},
|
||||
binding.range,
|
||||
binding.range(),
|
||||
);
|
||||
if let Some(range) = binding.parent_range(&checker.semantic) {
|
||||
diagnostic.set_parent(range.start());
|
||||
@@ -309,6 +303,12 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
pyflakes::rules::unused_import(checker, scope, &mut diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
if scope.kind.is_function() {
|
||||
if checker.enabled(Rule::NoSelfUse) {
|
||||
pylint::rules::no_self_use(checker, scope, &mut diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.diagnostics.extend(diagnostics);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -434,7 +432,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pyupgrade::rules::deprecated_unittest_alias(checker, func);
|
||||
}
|
||||
if checker.enabled(Rule::SuperCallWithParameters) {
|
||||
pyupgrade::rules::super_call_with_parameters(checker, expr, func, args);
|
||||
pyupgrade::rules::super_call_with_parameters(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryEncodeUTF8) {
|
||||
pyupgrade::rules::unnecessary_encode_utf8(checker, call);
|
||||
@@ -533,6 +531,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryDictKwargs) {
|
||||
flake8_pie::rules::unnecessary_dict_kwargs(checker, expr, keywords);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryRangeStart) {
|
||||
flake8_pie::rules::unnecessary_range_start(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::ExecBuiltin) {
|
||||
flake8_bandit::rules::exec_used(checker, func);
|
||||
}
|
||||
@@ -675,10 +676,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 +759,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 +876,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 +927,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 +954,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 +1107,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 +1140,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);
|
||||
@@ -1183,7 +1178,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::magic_value_comparison(checker, left, comparators);
|
||||
}
|
||||
if checker.enabled(Rule::InDictKeys) {
|
||||
flake8_simplify::rules::key_in_dict_compare(checker, expr, left, ops, comparators);
|
||||
flake8_simplify::rules::key_in_dict_compare(checker, compare);
|
||||
}
|
||||
if checker.enabled(Rule::YodaConditions) {
|
||||
flake8_simplify::rules::yoda_conditions(checker, expr, left, ops, comparators);
|
||||
|
||||
@@ -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,
|
||||
@@ -332,9 +338,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::FStringDocstring) {
|
||||
flake8_bugbear::rules::f_string_docstring(checker, body);
|
||||
}
|
||||
if checker.enabled(Rule::YieldInForLoop) {
|
||||
pyupgrade::rules::yield_in_for_loop(checker, stmt);
|
||||
}
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
|
||||
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
||||
flake8_builtins::rules::builtin_method_shadowing(
|
||||
@@ -461,17 +464,17 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_pyi::rules::pass_statement_stub_body(checker, body);
|
||||
}
|
||||
if checker.enabled(Rule::PassInClassBody) {
|
||||
flake8_pyi::rules::pass_in_class_body(checker, stmt, body);
|
||||
flake8_pyi::rules::pass_in_class_body(checker, class_def);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::EllipsisInNonEmptyClassBody) {
|
||||
flake8_pyi::rules::ellipsis_in_non_empty_class_body(checker, stmt, body);
|
||||
flake8_pyi::rules::ellipsis_in_non_empty_class_body(checker, body);
|
||||
}
|
||||
if checker.enabled(Rule::PytestIncorrectMarkParenthesesStyle) {
|
||||
flake8_pytest_style::rules::marks(checker, decorator_list);
|
||||
}
|
||||
if checker.enabled(Rule::DuplicateClassFieldDefinition) {
|
||||
flake8_pie::rules::duplicate_class_field_definition(checker, stmt, body);
|
||||
flake8_pie::rules::duplicate_class_field_definition(checker, body);
|
||||
}
|
||||
if checker.enabled(Rule::NonUniqueEnums) {
|
||||
flake8_pie::rules::non_unique_enums(checker, stmt, body);
|
||||
@@ -506,17 +509,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 +554,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 +691,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 +727,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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1084,7 +1139,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pygrep_hooks::rules::non_existent_mock_method(checker, test);
|
||||
}
|
||||
}
|
||||
Stmt::With(with_ @ ast::StmtWith { items, body, .. }) => {
|
||||
Stmt::With(with_stmt @ ast::StmtWith { items, body, .. }) => {
|
||||
if checker.enabled(Rule::AssertRaisesException) {
|
||||
flake8_bugbear::rules::assert_raises_exception(checker, items);
|
||||
}
|
||||
@@ -1094,7 +1149,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::MultipleWithStatements) {
|
||||
flake8_simplify::rules::multiple_with_statements(
|
||||
checker,
|
||||
with_,
|
||||
with_stmt,
|
||||
checker.semantic.current_statement_parent(),
|
||||
);
|
||||
}
|
||||
@@ -1113,15 +1168,21 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
perflint::rules::try_except_in_loop(checker, body);
|
||||
}
|
||||
}
|
||||
Stmt::For(ast::StmtFor {
|
||||
target,
|
||||
body,
|
||||
iter,
|
||||
orelse,
|
||||
..
|
||||
}) => {
|
||||
if checker.any_enabled(&[Rule::UnusedLoopControlVariable, Rule::IncorrectDictIterator])
|
||||
{
|
||||
Stmt::For(
|
||||
for_stmt @ ast::StmtFor {
|
||||
target,
|
||||
body,
|
||||
iter,
|
||||
orelse,
|
||||
is_async,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnusedLoopControlVariable,
|
||||
Rule::IncorrectDictIterator,
|
||||
Rule::YieldInForLoop,
|
||||
]) {
|
||||
checker.deferred.for_loops.push(checker.semantic.snapshot());
|
||||
}
|
||||
if checker.enabled(Rule::LoopVariableOverridesIterator) {
|
||||
@@ -1142,17 +1203,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::IterationOverSet) {
|
||||
pylint::rules::iteration_over_set(checker, iter);
|
||||
}
|
||||
if stmt.is_for_stmt() {
|
||||
if checker.enabled(Rule::ReimplementedBuiltin) {
|
||||
flake8_simplify::rules::convert_for_loop_to_any_all(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::InDictKeys) {
|
||||
flake8_simplify::rules::key_in_dict_for(checker, target, iter);
|
||||
}
|
||||
if checker.enabled(Rule::TryExceptInLoop) {
|
||||
perflint::rules::try_except_in_loop(checker, body);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::ManualListComprehension) {
|
||||
perflint::rules::manual_list_comprehension(checker, target, body);
|
||||
}
|
||||
@@ -1162,20 +1212,24 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryListCast) {
|
||||
perflint::rules::unnecessary_list_cast(checker, iter);
|
||||
}
|
||||
if !is_async {
|
||||
if checker.enabled(Rule::ReimplementedBuiltin) {
|
||||
flake8_simplify::rules::convert_for_loop_to_any_all(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::InDictKeys) {
|
||||
flake8_simplify::rules::key_in_dict_for(checker, for_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::TryExceptInLoop) {
|
||||
perflint::rules::try_except_in_loop(checker, body);
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Try(ast::StmtTry {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
})
|
||||
| Stmt::TryStar(ast::StmtTryStar {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
..
|
||||
}) => {
|
||||
if checker.enabled(Rule::JumpStatementInFinally) {
|
||||
flake8_bugbear::rules::jump_statement_in_finally(checker, finalbody);
|
||||
|
||||
@@ -32,8 +32,8 @@ use itertools::Itertools;
|
||||
use log::error;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arguments, Comprehension, Constant, ElifElseClause, ExceptHandler, Expr,
|
||||
ExprContext, Keyword, Parameter, ParameterWithDefault, Parameters, Pattern, Ranged, Stmt,
|
||||
Suite, UnaryOp,
|
||||
ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Ranged,
|
||||
Stmt, Suite, UnaryOp,
|
||||
};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
@@ -193,18 +193,22 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`IsolationLevel`] for fixes in the current context.
|
||||
/// Returns the [`IsolationLevel`] to isolate fixes for the current statement.
|
||||
///
|
||||
/// The primary use-case for fix isolation is to ensure that we don't delete all statements
|
||||
/// in a given indented block, which would cause a syntax error. We therefore need to ensure
|
||||
/// that we delete at most one statement per indented block per fixer pass. Fix isolation should
|
||||
/// thus be applied whenever we delete a statement, but can otherwise be omitted.
|
||||
pub(crate) fn isolation(&self, parent: Option<&Stmt>) -> IsolationLevel {
|
||||
parent
|
||||
.and_then(|stmt| self.semantic.statement_id(stmt))
|
||||
.map_or(IsolationLevel::default(), |node_id| {
|
||||
IsolationLevel::Group(node_id.into())
|
||||
})
|
||||
pub(crate) fn statement_isolation(&self) -> IsolationLevel {
|
||||
IsolationLevel::Group(self.semantic.current_statement_id().into())
|
||||
}
|
||||
|
||||
/// Returns the [`IsolationLevel`] to isolate fixes in the current statement's parent.
|
||||
pub(crate) fn parent_isolation(&self) -> IsolationLevel {
|
||||
self.semantic
|
||||
.current_statement_parent_id()
|
||||
.map(|node_id| IsolationLevel::Group(node_id.into()))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The [`Locator`] for the current file, which enables extraction of source code from byte
|
||||
@@ -263,7 +267,7 @@ where
|
||||
{
|
||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||
// Step 0: Pre-processing
|
||||
self.semantic.push_statement(stmt);
|
||||
self.semantic.push_node(stmt);
|
||||
|
||||
// Track whether we've seen docstrings, non-imports, etc.
|
||||
match stmt {
|
||||
@@ -599,14 +603,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) {
|
||||
@@ -626,16 +623,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over the `body`, then the `handlers`, then the `orelse`, then the
|
||||
// `finalbody`, but treat the body and the `orelse` as a single branch for
|
||||
// flow analysis purposes.
|
||||
let branch = self.semantic.push_branch();
|
||||
self.semantic.handled_exceptions.push(handled_exceptions);
|
||||
self.visit_body(body);
|
||||
self.semantic.handled_exceptions.pop();
|
||||
self.semantic.pop_branch();
|
||||
|
||||
for except_handler in handlers {
|
||||
self.semantic.push_branch();
|
||||
self.visit_except_handler(except_handler);
|
||||
self.semantic.pop_branch();
|
||||
}
|
||||
|
||||
self.semantic.set_branch(branch);
|
||||
self.visit_body(orelse);
|
||||
self.semantic.pop_branch();
|
||||
|
||||
self.semantic.push_branch();
|
||||
self.visit_body(finalbody);
|
||||
self.semantic.pop_branch();
|
||||
}
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
target,
|
||||
@@ -715,6 +724,7 @@ where
|
||||
) => {
|
||||
self.visit_boolean_test(test);
|
||||
|
||||
self.semantic.push_branch();
|
||||
if typing::is_type_checking_block(stmt_if, &self.semantic) {
|
||||
if self.semantic.at_top_level() {
|
||||
self.importer.visit_type_checking_block(stmt);
|
||||
@@ -723,9 +733,12 @@ where
|
||||
} else {
|
||||
self.visit_body(body);
|
||||
}
|
||||
self.semantic.pop_branch();
|
||||
|
||||
for clause in elif_else_clauses {
|
||||
self.semantic.push_branch();
|
||||
self.visit_elif_else_clause(clause);
|
||||
self.semantic.pop_branch();
|
||||
}
|
||||
}
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
@@ -766,7 +779,7 @@ where
|
||||
analyze::statement(stmt, self);
|
||||
|
||||
self.semantic.flags = flags_snapshot;
|
||||
self.semantic.pop_statement();
|
||||
self.semantic.pop_node();
|
||||
}
|
||||
|
||||
fn visit_annotation(&mut self, expr: &'b Expr) {
|
||||
@@ -802,7 +815,7 @@ where
|
||||
return;
|
||||
}
|
||||
|
||||
self.semantic.push_expression(expr);
|
||||
self.semantic.push_node(expr);
|
||||
|
||||
// Store the flags prior to any further descent, so that we can restore them after visiting
|
||||
// the node.
|
||||
@@ -881,18 +894,20 @@ where
|
||||
},
|
||||
) => {
|
||||
// Visit the default arguments, but avoid the body, which will be deferred.
|
||||
for ParameterWithDefault {
|
||||
default,
|
||||
parameter: _,
|
||||
range: _,
|
||||
} in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
if let Some(expr) = &default {
|
||||
self.visit_expr(expr);
|
||||
if let Some(parameters) = parameters {
|
||||
for ParameterWithDefault {
|
||||
default,
|
||||
parameter: _,
|
||||
range: _,
|
||||
} in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
if let Some(expr) = &default {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1220,7 +1235,7 @@ where
|
||||
analyze::expression(expr, self);
|
||||
|
||||
self.semantic.flags = flags_snapshot;
|
||||
self.semantic.pop_expression();
|
||||
self.semantic.pop_node();
|
||||
}
|
||||
|
||||
fn visit_except_handler(&mut self, except_handler: &'b ExceptHandler) {
|
||||
@@ -1275,7 +1290,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);
|
||||
}
|
||||
@@ -1358,6 +1373,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_match_case(&mut self, match_case: &'b MatchCase) {
|
||||
self.visit_pattern(&match_case.pattern);
|
||||
if let Some(expr) = &match_case.guard {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
|
||||
self.semantic.push_branch();
|
||||
self.visit_body(&match_case.body);
|
||||
self.semantic.pop_branch();
|
||||
}
|
||||
|
||||
fn visit_type_param(&mut self, type_param: &'b ast::TypeParam) {
|
||||
// Step 1: Binding
|
||||
match type_param {
|
||||
@@ -1841,7 +1867,9 @@ impl<'a> Checker<'a> {
|
||||
range: _,
|
||||
}) = expr
|
||||
{
|
||||
self.visit_parameters(parameters);
|
||||
if let Some(parameters) = parameters {
|
||||
self.visit_parameters(parameters);
|
||||
}
|
||||
self.visit_expr(body);
|
||||
} else {
|
||||
unreachable!("Expected Expr::Lambda");
|
||||
@@ -1862,7 +1890,7 @@ impl<'a> Checker<'a> {
|
||||
.map(|binding_id| &self.semantic.bindings[binding_id])
|
||||
.filter_map(|binding| match &binding.kind {
|
||||
BindingKind::Export(Export { names }) => {
|
||||
Some(names.iter().map(|name| (*name, binding.range)))
|
||||
Some(names.iter().map(|name| (*name, binding.range())))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::TokenKind;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pycodestyle::rules::logical_lines::{
|
||||
|
||||
@@ -216,6 +216,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R1722") => (RuleGroup::Unspecified, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R2004") => (RuleGroup::Unspecified, rules::pylint::rules::MagicValueComparison),
|
||||
(Pylint, "R5501") => (RuleGroup::Unspecified, rules::pylint::rules::CollapsibleElseIf),
|
||||
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
|
||||
(Pylint, "W0120") => (RuleGroup::Unspecified, rules::pylint::rules::UselessElseOnLoop),
|
||||
(Pylint, "W0127") => (RuleGroup::Unspecified, rules::pylint::rules::SelfAssigningVariable),
|
||||
(Pylint, "W0129") => (RuleGroup::Unspecified, rules::pylint::rules::AssertOnStringLiteral),
|
||||
@@ -226,8 +227,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 +312,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 +569,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 +686,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 +699,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),
|
||||
@@ -702,6 +708,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Pie, "800") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessarySpread),
|
||||
(Flake8Pie, "804") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessaryDictKwargs),
|
||||
(Flake8Pie, "807") => (RuleGroup::Unspecified, rules::flake8_pie::rules::ReimplementedListBuiltin),
|
||||
(Flake8Pie, "808") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessaryRangeStart),
|
||||
(Flake8Pie, "810") => (RuleGroup::Unspecified, rules::flake8_pie::rules::MultipleStartsEndsWith),
|
||||
|
||||
// flake8-commas
|
||||
@@ -811,6 +818,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),
|
||||
|
||||
|
||||
@@ -405,7 +405,7 @@ y = 2
|
||||
z = x + 1";
|
||||
assert_eq!(
|
||||
noqa_mappings(contents),
|
||||
NoqaMapping::from_iter([TextRange::new(TextSize::from(0), TextSize::from(22)),])
|
||||
NoqaMapping::from_iter([TextRange::new(TextSize::from(0), TextSize::from(22))])
|
||||
);
|
||||
|
||||
let contents = "x = 1
|
||||
|
||||
@@ -2,9 +2,8 @@ use std::fmt::{Debug, Formatter};
|
||||
use std::ops::Deref;
|
||||
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_python_semantic::Definition;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
pub(crate) mod extraction;
|
||||
pub(crate) mod google;
|
||||
@@ -28,43 +27,34 @@ impl<'a> Docstring<'a> {
|
||||
DocstringBody { docstring: self }
|
||||
}
|
||||
|
||||
pub(crate) fn start(&self) -> TextSize {
|
||||
self.expr.start()
|
||||
}
|
||||
|
||||
pub(crate) fn end(&self) -> TextSize {
|
||||
self.expr.end()
|
||||
}
|
||||
|
||||
pub(crate) fn range(&self) -> TextRange {
|
||||
self.expr.range()
|
||||
}
|
||||
|
||||
pub(crate) fn leading_quote(&self) -> &'a str {
|
||||
&self.contents[TextRange::up_to(self.body_range.start())]
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for Docstring<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
self.expr.range()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct DocstringBody<'a> {
|
||||
docstring: &'a Docstring<'a>,
|
||||
}
|
||||
|
||||
impl<'a> DocstringBody<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn start(self) -> TextSize {
|
||||
self.range().start()
|
||||
}
|
||||
|
||||
pub(crate) fn range(self) -> TextRange {
|
||||
self.docstring.body_range + self.docstring.start()
|
||||
}
|
||||
|
||||
pub(crate) fn as_str(self) -> &'a str {
|
||||
&self.docstring.contents[self.docstring.body_range]
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for DocstringBody<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
self.docstring.body_range + self.docstring.start()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DocstringBody<'_> {
|
||||
type Target = str;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::fmt::{Debug, Formatter};
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use ruff_python_ast::docstrings::{leading_space, leading_words};
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
@@ -366,6 +367,12 @@ impl<'a> SectionContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for SectionContext<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SectionContext<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SectionContext")
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use globset::GlobMatcher;
|
||||
use log::debug;
|
||||
use path_absolutize::{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;
|
||||
|
||||
@@ -194,7 +194,7 @@ impl<'a> Importer<'a> {
|
||||
// import and the current location, and thus the symbol would not be available). It's also
|
||||
// unclear whether should add an import statement at the start of the file, since it could
|
||||
// be shadowed between the import and the current location.
|
||||
if imported_name.range().start() > at {
|
||||
if imported_name.start() > at {
|
||||
return Some(Err(ResolutionError::ImportAfterUsage));
|
||||
}
|
||||
|
||||
@@ -301,12 +301,14 @@ impl<'a> Importer<'a> {
|
||||
}
|
||||
if let Stmt::ImportFrom(ast::StmtImportFrom {
|
||||
module: name,
|
||||
names,
|
||||
level,
|
||||
..
|
||||
range: _,
|
||||
}) = stmt
|
||||
{
|
||||
if level.map_or(true, |level| level.to_u32() == 0)
|
||||
&& name.as_ref().is_some_and(|name| name == module)
|
||||
&& names.iter().all(|alias| alias.name.as_str() != "*")
|
||||
{
|
||||
import_from = Some(*stmt);
|
||||
}
|
||||
|
||||
@@ -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,14 +26,14 @@ 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(),
|
||||
err
|
||||
)
|
||||
})?;
|
||||
let code = notebook.content().to_string();
|
||||
let code = notebook.source_code().to_string();
|
||||
notebook.update_cell_content(&code);
|
||||
let mut writer = Vec::new();
|
||||
notebook.write_inner(&mut writer)?;
|
||||
@@ -103,7 +103,7 @@ pub struct Notebook {
|
||||
/// separated by a newline and a trailing newline. The trailing newline
|
||||
/// is added to make sure that each cell ends with a newline which will
|
||||
/// be removed when updating the cell content.
|
||||
content: String,
|
||||
source_code: String,
|
||||
/// The index of the notebook. This is used to map between the concatenated
|
||||
/// source code and the original notebook.
|
||||
index: OnceCell<JupyterIndex>,
|
||||
@@ -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_source_code(source_code: &str) -> Result<Self, Box<Diagnostic>> {
|
||||
Self::from_reader(Cursor::new(source_code))
|
||||
}
|
||||
|
||||
/// 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}"
|
||||
),
|
||||
@@ -251,7 +268,7 @@ impl Notebook {
|
||||
// The additional newline at the end is to maintain consistency for
|
||||
// all cells. These newlines will be removed before updating the
|
||||
// source code with the transformed content. Refer `update_cell_content`.
|
||||
content: contents.join("\n") + "\n",
|
||||
source_code: contents.join("\n") + "\n",
|
||||
cell_offsets,
|
||||
valid_code_cells,
|
||||
trailing_newline,
|
||||
@@ -387,8 +404,8 @@ impl Notebook {
|
||||
/// Return the notebook content.
|
||||
///
|
||||
/// This is the concatenation of all Python code cells.
|
||||
pub(crate) fn content(&self) -> &str {
|
||||
&self.content
|
||||
pub fn source_code(&self) -> &str {
|
||||
&self.source_code
|
||||
}
|
||||
|
||||
/// Return the Jupyter notebook index.
|
||||
@@ -407,12 +424,13 @@ impl Notebook {
|
||||
}
|
||||
|
||||
/// Update the notebook with the given sourcemap and transformed content.
|
||||
pub(crate) fn update(&mut self, source_map: &SourceMap, transformed: &str) {
|
||||
pub(crate) fn update(&mut self, source_map: &SourceMap, transformed: String) {
|
||||
// Cell offsets must be updated before updating the cell content as
|
||||
// it depends on the offsets to extract the cell content.
|
||||
self.index.take();
|
||||
self.update_cell_offsets(source_map);
|
||||
self.update_cell_content(transformed);
|
||||
self.content = transformed.to_string();
|
||||
self.update_cell_content(&transformed);
|
||||
self.source_code = transformed;
|
||||
}
|
||||
|
||||
/// Return a slice of [`Cell`] in the Jupyter notebook.
|
||||
@@ -459,14 +477,16 @@ mod tests {
|
||||
use crate::jupyter::schema::Cell;
|
||||
use crate::jupyter::Notebook;
|
||||
use crate::registry::Rule;
|
||||
use crate::test::{read_jupyter_notebook, test_notebook_path, test_resource_path};
|
||||
use crate::test::{
|
||||
read_jupyter_notebook, test_notebook_path, test_resource_path, TestedNotebook,
|
||||
};
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
/// Read a Jupyter cell from the `resources/test/fixtures/jupyter/cell` directory.
|
||||
fn read_jupyter_cell(path: impl AsRef<Path>) -> Result<Cell> {
|
||||
let path = test_resource_path("fixtures/jupyter/cell").join(path);
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
Ok(serde_json::from_str(&contents)?)
|
||||
let source_code = std::fs::read_to_string(path)?;
|
||||
Ok(serde_json::from_str(&source_code)?)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -484,22 +504,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"
|
||||
);
|
||||
@@ -519,7 +539,7 @@ mod tests {
|
||||
fn test_concat_notebook() -> Result<()> {
|
||||
let notebook = read_jupyter_notebook(Path::new("valid.ipynb"))?;
|
||||
assert_eq!(
|
||||
notebook.content,
|
||||
notebook.source_code,
|
||||
r#"def unused_variable():
|
||||
x = 1
|
||||
y = 2
|
||||
@@ -561,49 +581,64 @@ print("after empty cells")
|
||||
#[test]
|
||||
fn test_import_sorting() -> Result<()> {
|
||||
let path = "isort.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&path,
|
||||
Path::new("isort_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnsortedImports),
|
||||
)?;
|
||||
assert_messages!(diagnostics, path, source_kind);
|
||||
assert_messages!(messages, path, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ipy_escape_command() -> Result<()> {
|
||||
let path = "ipy_escape_command.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&path,
|
||||
Path::new("ipy_escape_command_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
assert_messages!(diagnostics, path, source_kind);
|
||||
assert_messages!(messages, path, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unused_variable() -> Result<()> {
|
||||
let path = "unused_variable.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&path,
|
||||
Path::new("unused_variable_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedVariable),
|
||||
)?;
|
||||
assert_messages!(diagnostics, path, source_kind);
|
||||
assert_messages!(messages, path, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_consistency() -> Result<()> {
|
||||
let path = "before_fix.ipynb".to_string();
|
||||
let (_, _, source_kind) = test_notebook_path(
|
||||
let TestedNotebook {
|
||||
linted_notebook: fixed_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
path,
|
||||
Path::new("after_fix.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
let mut writer = Vec::new();
|
||||
source_kind.expect_jupyter().write_inner(&mut writer)?;
|
||||
fixed_notebook.write_inner(&mut writer)?;
|
||||
let actual = String::from_utf8(writer)?;
|
||||
let expected =
|
||||
std::fs::read_to_string(test_resource_path("fixtures/jupyter/after_fix.ipynb"))?;
|
||||
|
||||
@@ -59,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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,10 +145,10 @@ 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 NonZeroU8);
|
||||
pub struct TabSize(NonZeroU8);
|
||||
|
||||
impl TabSize {
|
||||
fn as_usize(self) -> usize {
|
||||
pub(crate) fn as_usize(self) -> usize {
|
||||
self.0.get() as usize
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ pub struct FixerResult<'a> {
|
||||
/// The result returned by the linter, after applying any fixes.
|
||||
pub result: LinterResult<(Vec<Message>, Option<ImportMap>)>,
|
||||
/// The resulting source code, after applying any fixes.
|
||||
pub transformed: Cow<'a, str>,
|
||||
pub transformed: Cow<'a, SourceKind>,
|
||||
/// The number of fixes applied for each [`Rule`].
|
||||
pub fixed: FixTable,
|
||||
}
|
||||
@@ -335,19 +335,19 @@ pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings
|
||||
/// Generate a [`Message`] for each [`Diagnostic`] triggered by the given source
|
||||
/// code.
|
||||
pub fn lint_only(
|
||||
contents: &str,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
) -> LinterResult<(Vec<Message>, Option<ImportMap>)> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, source_type.as_mode());
|
||||
let tokens: Vec<LexResult> =
|
||||
ruff_python_parser::tokenize(source_kind.source_code(), source_type.as_mode());
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(contents);
|
||||
let locator = Locator::new(source_kind.source_code());
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
@@ -374,7 +374,7 @@ pub fn lint_only(
|
||||
&directives,
|
||||
settings,
|
||||
noqa,
|
||||
source_kind,
|
||||
Some(source_kind),
|
||||
source_type,
|
||||
);
|
||||
|
||||
@@ -416,15 +416,14 @@ fn diagnostics_to_messages(
|
||||
/// Generate `Diagnostic`s from source code content, iteratively autofixing
|
||||
/// until stable.
|
||||
pub fn lint_fix<'a>(
|
||||
contents: &'a str,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
noqa: flags::Noqa,
|
||||
settings: &Settings,
|
||||
source_kind: &mut SourceKind,
|
||||
source_kind: &'a SourceKind,
|
||||
source_type: PySourceType,
|
||||
) -> Result<FixerResult<'a>> {
|
||||
let mut transformed = Cow::Borrowed(contents);
|
||||
let mut transformed = Cow::Borrowed(source_kind);
|
||||
|
||||
// Track the number of fixed errors across iterations.
|
||||
let mut fixed = FxHashMap::default();
|
||||
@@ -439,10 +438,10 @@ pub fn lint_fix<'a>(
|
||||
loop {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> =
|
||||
ruff_python_parser::tokenize(&transformed, source_type.as_mode());
|
||||
ruff_python_parser::tokenize(transformed.source_code(), source_type.as_mode());
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(&transformed);
|
||||
let locator = Locator::new(transformed.source_code());
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
@@ -482,7 +481,7 @@ pub fn lint_fix<'a>(
|
||||
if parseable && result.error.is_some() {
|
||||
report_autofix_syntax_error(
|
||||
path,
|
||||
&transformed,
|
||||
transformed.source_code(),
|
||||
&result.error.unwrap(),
|
||||
fixed.keys().copied(),
|
||||
);
|
||||
@@ -503,12 +502,7 @@ pub fn lint_fix<'a>(
|
||||
*fixed.entry(rule).or_default() += count;
|
||||
}
|
||||
|
||||
if let SourceKind::Jupyter(notebook) = source_kind {
|
||||
notebook.update(&source_map, &fixed_contents);
|
||||
}
|
||||
|
||||
// Store the fixed contents.
|
||||
transformed = Cow::Owned(fixed_contents);
|
||||
transformed = Cow::Owned(transformed.updated(fixed_contents, &source_map));
|
||||
|
||||
// Increment the iteration count.
|
||||
iterations += 1;
|
||||
@@ -517,7 +511,7 @@ pub fn lint_fix<'a>(
|
||||
continue;
|
||||
}
|
||||
|
||||
report_failed_to_converge_error(path, &transformed, &result.data.0);
|
||||
report_failed_to_converge_error(path, transformed.source_code(), &result.data.0);
|
||||
}
|
||||
|
||||
return Ok(FixerResult {
|
||||
|
||||
@@ -18,7 +18,7 @@ impl Emitter for AzureEmitter {
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
for message in messages {
|
||||
let location = if context.is_jupyter_notebook(message.filename()) {
|
||||
let location = if context.is_notebook(message.filename()) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
SourceLocation::default()
|
||||
|
||||
@@ -20,7 +20,7 @@ impl Emitter for GithubEmitter {
|
||||
) -> anyhow::Result<()> {
|
||||
for message in messages {
|
||||
let source_location = message.compute_start_location();
|
||||
let location = if context.is_jupyter_notebook(message.filename()) {
|
||||
let location = if context.is_notebook(message.filename()) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
SourceLocation::default()
|
||||
|
||||
@@ -63,7 +63,7 @@ impl Serialize for SerializedMessages<'_> {
|
||||
let start_location = message.compute_start_location();
|
||||
let end_location = message.compute_end_location();
|
||||
|
||||
let lines = if self.context.is_jupyter_notebook(message.filename()) {
|
||||
let lines = if self.context.is_notebook(message.filename()) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
json!({
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::message::text::{MessageCodeFrame, RuleCodeAndBody};
|
||||
use crate::message::{
|
||||
group_messages_by_filename, Emitter, EmitterContext, Message, MessageWithLocation,
|
||||
};
|
||||
use crate::source_kind::SourceKind;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GroupedEmitter {
|
||||
@@ -66,10 +65,7 @@ impl Emitter for GroupedEmitter {
|
||||
writer,
|
||||
"{}",
|
||||
DisplayGroupedMessage {
|
||||
jupyter_index: context
|
||||
.source_kind(message.filename())
|
||||
.and_then(SourceKind::notebook)
|
||||
.map(Notebook::index),
|
||||
jupyter_index: context.notebook(message.filename()).map(Notebook::index),
|
||||
message,
|
||||
show_fix_status: self.show_fix_status,
|
||||
show_source: self.show_source,
|
||||
|
||||
@@ -45,7 +45,7 @@ impl Emitter for JunitEmitter {
|
||||
} = message;
|
||||
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
|
||||
status.set_message(message.kind.body.clone());
|
||||
let location = if context.is_jupyter_notebook(message.filename()) {
|
||||
let location = if context.is_notebook(message.filename()) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
SourceLocation::default()
|
||||
|
||||
@@ -3,10 +3,8 @@ use std::collections::BTreeMap;
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::source_kind::SourceKind;
|
||||
pub use azure::AzureEmitter;
|
||||
pub use github::GithubEmitter;
|
||||
pub use gitlab::GitlabEmitter;
|
||||
@@ -17,8 +15,11 @@ pub use junit::JunitEmitter;
|
||||
pub use pylint::PylintEmitter;
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
|
||||
use ruff_source_file::{SourceFile, SourceLocation};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
pub use text::TextEmitter;
|
||||
|
||||
use crate::jupyter::Notebook;
|
||||
|
||||
mod azure;
|
||||
mod diff;
|
||||
mod github;
|
||||
@@ -129,33 +130,31 @@ pub trait Emitter {
|
||||
|
||||
/// Context passed to [`Emitter`].
|
||||
pub struct EmitterContext<'a> {
|
||||
source_kind: &'a FxHashMap<String, SourceKind>,
|
||||
notebooks: &'a FxHashMap<String, Notebook>,
|
||||
}
|
||||
|
||||
impl<'a> EmitterContext<'a> {
|
||||
pub fn new(source_kind: &'a FxHashMap<String, SourceKind>) -> Self {
|
||||
Self { source_kind }
|
||||
pub fn new(notebooks: &'a FxHashMap<String, Notebook>) -> Self {
|
||||
Self { notebooks }
|
||||
}
|
||||
|
||||
/// Tests if the file with `name` is a jupyter notebook.
|
||||
pub fn is_jupyter_notebook(&self, name: &str) -> bool {
|
||||
self.source_kind
|
||||
.get(name)
|
||||
.is_some_and(SourceKind::is_jupyter)
|
||||
pub fn is_notebook(&self, name: &str) -> bool {
|
||||
self.notebooks.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn source_kind(&self, name: &str) -> Option<&SourceKind> {
|
||||
self.source_kind.get(name)
|
||||
pub fn notebook(&self, name: &str) -> Option<&Notebook> {
|
||||
self.notebooks.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Edit, Fix};
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ impl Emitter for PylintEmitter {
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
for message in messages {
|
||||
let row = if context.is_jupyter_notebook(message.filename()) {
|
||||
let row = if context.is_notebook(message.filename()) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
OneIndexed::from_zero_indexed(0)
|
||||
|
||||
@@ -6,9 +6,9 @@ use annotate_snippets::display_list::{DisplayList, FormatOptions};
|
||||
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_source_file::{OneIndexed, SourceLocation};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::jupyter::{JupyterIndex, Notebook};
|
||||
@@ -16,7 +16,6 @@ use crate::line_width::{LineWidth, TabSize};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
use crate::source_kind::SourceKind;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
@@ -72,10 +71,7 @@ impl Emitter for TextEmitter {
|
||||
)?;
|
||||
|
||||
let start_location = message.compute_start_location();
|
||||
let jupyter_index = context
|
||||
.source_kind(message.filename())
|
||||
.and_then(SourceKind::notebook)
|
||||
.map(Notebook::index);
|
||||
let jupyter_index = context.notebook(message.filename()).map(Notebook::index);
|
||||
|
||||
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
|
||||
let diagnostic_location = if let Some(jupyter_index) = jupyter_index {
|
||||
|
||||
@@ -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
|
||||
/// ])));
|
||||
/// ```
|
||||
|
||||
@@ -4,6 +4,7 @@ use anyhow::{anyhow, Result};
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_python_semantic::{Binding, BindingKind, Scope, ScopeId, SemanticModel};
|
||||
|
||||
pub(crate) struct Renamer;
|
||||
@@ -220,12 +221,12 @@ impl Renamer {
|
||||
BindingKind::Import(_) | BindingKind::FromImport(_) => {
|
||||
if binding.is_alias() {
|
||||
// Ex) Rename `import pandas as alias` to `import pandas as pd`.
|
||||
Some(Edit::range_replacement(target.to_string(), binding.range))
|
||||
Some(Edit::range_replacement(target.to_string(), binding.range()))
|
||||
} else {
|
||||
// Ex) Rename `import pandas` to `import pandas as pd`.
|
||||
Some(Edit::range_replacement(
|
||||
format!("{name} as {target}"),
|
||||
binding.range,
|
||||
binding.range(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -234,7 +235,7 @@ impl Renamer {
|
||||
let module_name = import.call_path.first().unwrap();
|
||||
Some(Edit::range_replacement(
|
||||
format!("{module_name} as {target}"),
|
||||
binding.range,
|
||||
binding.range(),
|
||||
))
|
||||
}
|
||||
// Avoid renaming builtins and other "special" bindings.
|
||||
@@ -254,7 +255,7 @@ impl Renamer {
|
||||
| BindingKind::FunctionDefinition(_)
|
||||
| BindingKind::Deletion
|
||||
| BindingKind::UnboundException(_) => {
|
||||
Some(Edit::range_replacement(target.to_string(), binding.range))
|
||||
Some(Edit::range_replacement(target.to_string(), binding.range()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ pub(crate) fn variable_name_task_id(
|
||||
// If the keyword argument is not a string, we can't do anything.
|
||||
let task_id = match &keyword.value {
|
||||
Expr::Constant(constant) => match &constant.value {
|
||||
Constant::Str(value) => value,
|
||||
Constant::Str(ast::StringConstant { value, .. }) => value,
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
|
||||
@@ -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,14 +1,11 @@
|
||||
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 {
|
||||
matches!(
|
||||
name,
|
||||
"append"
|
||||
"__setattr__"
|
||||
| "append"
|
||||
| "assertEqual"
|
||||
| "assertEquals"
|
||||
| "assertNotEqual"
|
||||
@@ -30,13 +27,13 @@ pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
||||
| "int"
|
||||
| "is_"
|
||||
| "is_not"
|
||||
| "next"
|
||||
| "param"
|
||||
| "pop"
|
||||
| "remove"
|
||||
| "set_blocking"
|
||||
| "set_enabled"
|
||||
| "setattr"
|
||||
| "__setattr__"
|
||||
| "setdefault"
|
||||
| "str"
|
||||
)
|
||||
@@ -62,18 +59,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,92 +1,92 @@
|
||||
---
|
||||
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:87: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
|
||||
87 | pass
|
||||
86 | # FBT001: Boolean positional arg in function definition
|
||||
87 | def foo(self, value: bool) -> None:
|
||||
| ^^^^^ FBT001
|
||||
88 | 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,
|
||||
|
|
||||
|
||||
@@ -90,8 +90,7 @@ impl AlwaysAutofixableViolation for DuplicateHandlerException {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let DuplicateHandlerException { names } = self;
|
||||
if names.len() == 1 {
|
||||
let name = &names[0];
|
||||
if let [name] = names.as_slice() {
|
||||
format!("Exception handler with duplicate exception: `{name}`")
|
||||
} else {
|
||||
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
|
||||
|
||||
@@ -184,7 +184,10 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
return false;
|
||||
}
|
||||
|
||||
if parameters.includes(&loaded.id) {
|
||||
if parameters
|
||||
.as_ref()
|
||||
.is_some_and(|parameters| parameters.includes(&loaded.id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -76,17 +76,20 @@ where
|
||||
range: _,
|
||||
}) => {
|
||||
visitor::walk_expr(self, body);
|
||||
for ParameterWithDefault {
|
||||
parameter,
|
||||
default: _,
|
||||
range: _,
|
||||
} in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
self.names.remove(parameter.name.as_str());
|
||||
|
||||
if let Some(parameters) = parameters {
|
||||
for ParameterWithDefault {
|
||||
parameter,
|
||||
default: _,
|
||||
range: _,
|
||||
} in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
self.names.remove(parameter.name.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user