Compare commits
1 Commits
charlie/ou
...
nextest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56fbde05b6 |
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -4,10 +4,8 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels: ["internal"]
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels: ["internal"]
|
||||
day: "monday"
|
||||
time: "12:00"
|
||||
timezone: "America/New_York"
|
||||
commit-message:
|
||||
prefix: "ci(deps)"
|
||||
|
||||
10
.github/workflows/ci.yaml
vendored
10
.github/workflows/ci.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: tj-actions/changed-files@v38
|
||||
- uses: tj-actions/changed-files@v37
|
||||
id: changed
|
||||
with:
|
||||
files_yaml: |
|
||||
@@ -96,11 +96,17 @@ jobs:
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- run: pip install black[d]==23.1.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run tests (Ubuntu)"
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: cargo insta test --all --all-features --unreferenced reject
|
||||
run: >
|
||||
cargo insta test --all --all-features --unreferenced reject --test-runner nextest
|
||||
-- --status-level skip --failure-output immediate-final --no-fail-fast --all-targets
|
||||
- name: "Run tests (Windows)"
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
shell: bash
|
||||
|
||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.1.1
|
||||
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@v3.1.1
|
||||
uses: cloudflare/wrangler-action@v3.1.0
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
@@ -129,7 +129,6 @@ At time of writing, the repository includes the following crates:
|
||||
intermediate representation. The backend for `ruff_python_formatter`.
|
||||
- `crates/ruff_index`: library crate inspired by `rustc_index`.
|
||||
- `crates/ruff_macros`: proc macro crate containing macros used by Ruff.
|
||||
- `crates/ruff_notebook`: library crate for parsing and manipulating Jupyter notebooks.
|
||||
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
|
||||
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
|
||||
- `crates/ruff_python_formatter`: library crate implementing the Python formatter. Emits an
|
||||
|
||||
591
Cargo.lock
generated
591
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,7 @@ uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
# v1.0.1
|
||||
libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "9c263aa8977962a870ce2770d2aa18ee0dacb344", default-features = false }
|
||||
libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "3cacca1a1029f05707e50703b49fe3dd860aa839", default-features = false }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
@@ -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.287
|
||||
rev: v0.0.286
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -398,7 +398,6 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
- [Pylint](https://github.com/PyCQA/pylint)
|
||||
- [Reflex](https://github.com/reflex-dev/reflex)
|
||||
- [Rippling](https://rippling.com)
|
||||
- [Robyn](https://github.com/sansyrox/robyn)
|
||||
- Scale AI ([Launch SDK](https://github.com/scaleapi/launch-python-client))
|
||||
- Snowflake ([SnowCLI](https://github.com/Snowflake-Labs/snowcli))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.287"
|
||||
version = "0.0.286"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.287"
|
||||
version = "0.0.286"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -18,7 +18,6 @@ name = "ruff"
|
||||
ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
|
||||
ruff_index = { path = "../ruff_index" }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
|
||||
ruff_python_codegen = { path = "../ruff_python_codegen" }
|
||||
@@ -65,15 +64,17 @@ schemars = { workspace = true, optional = true }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_with = { version = "3.0.0" }
|
||||
similar = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { version = "1.0.43" }
|
||||
toml = { workspace = true }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unicode-width = { workspace = true }
|
||||
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
|
||||
uuid = { workspace = true, features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -308,7 +308,3 @@ def single_line_func_wrong(value: dict[str, str] = {
|
||||
def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
: \
|
||||
"""Docstring"""
|
||||
|
||||
|
||||
def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
"""Docstring without newline"""
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Should emit:
|
||||
B009 - Lines 19-31
|
||||
B010 - Lines 40-45
|
||||
B009 - Line 19, 20, 21, 22, 23, 24
|
||||
B010 - Line 40, 41, 42, 43, 44, 45
|
||||
"""
|
||||
|
||||
# Valid getattr usage
|
||||
@@ -24,16 +24,6 @@ getattr(foo, r"abc123")
|
||||
_ = lambda x: getattr(x, "bar")
|
||||
if getattr(x, "bar"):
|
||||
pass
|
||||
getattr(1, "real")
|
||||
getattr(1., "real")
|
||||
getattr(1.0, "real")
|
||||
getattr(1j, "real")
|
||||
getattr(True, "real")
|
||||
getattr(x := 1, "real")
|
||||
getattr(x + y, "real")
|
||||
getattr("foo"
|
||||
"bar", "real")
|
||||
|
||||
|
||||
# Valid setattr usage
|
||||
setattr(foo, bar, None)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
retriable_exceptions = (FileExistsError, FileNotFoundError)
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError,):
|
||||
@@ -8,5 +6,3 @@ except AttributeError:
|
||||
pass
|
||||
except (ImportError, TypeError):
|
||||
pass
|
||||
except (*retriable_exceptions,):
|
||||
pass
|
||||
|
||||
@@ -5,7 +5,6 @@ map(lambda x: str(x), nums)
|
||||
list(map(lambda x: x * 2, nums))
|
||||
set(map(lambda x: x % 2 == 0, nums))
|
||||
dict(map(lambda v: (v, v**2), nums))
|
||||
dict(map(lambda v: [v, v**2], nums))
|
||||
map(lambda: "const", nums)
|
||||
map(lambda _: 3.0, nums)
|
||||
_ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
@@ -38,6 +37,3 @@ map(lambda x: lambda x: x, range(4))
|
||||
map(lambda x=1: x, nums)
|
||||
map(lambda *args: len(args), range(4))
|
||||
map(lambda **kwargs: len(kwargs), range(4))
|
||||
|
||||
# Ok because multiple arguments are allowed.
|
||||
dict(map(lambda k, v: (k, v), keys, values))
|
||||
|
||||
@@ -3,20 +3,16 @@ def bar():
|
||||
|
||||
|
||||
def foo():
|
||||
"""foo""" # OK, docstrings are handled by another rule
|
||||
"""foo""" # OK
|
||||
|
||||
|
||||
def buzz():
|
||||
print("buzz") # ERROR PYI010
|
||||
print("buzz") # OK, not in stub file
|
||||
|
||||
|
||||
def foo2():
|
||||
123 # ERROR PYI010
|
||||
123 # OK, not in a stub file
|
||||
|
||||
|
||||
def bizz():
|
||||
x = 123 # ERROR PYI010
|
||||
|
||||
|
||||
def foo3():
|
||||
pass # OK, pass is handled by another rule
|
||||
x = 123 # OK, not in a stub file
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
def bar(): ... # OK
|
||||
def foo():
|
||||
"""foo""" # OK, docstrings are handled by another rule
|
||||
"""foo""" # OK, strings are handled by another rule
|
||||
|
||||
def buzz():
|
||||
print("buzz") # ERROR PYI010
|
||||
@@ -10,6 +10,3 @@ def foo2():
|
||||
|
||||
def bizz():
|
||||
x = 123 # ERROR PYI010
|
||||
|
||||
def foo3():
|
||||
pass # OK, pass is handled by another rule
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
def bar():
|
||||
... # OK
|
||||
def bar(): # OK
|
||||
...
|
||||
|
||||
|
||||
def bar():
|
||||
pass # OK
|
||||
|
||||
|
||||
def bar():
|
||||
"""oof""" # OK
|
||||
|
||||
|
||||
def oof(): # ERROR PYI048
|
||||
def oof(): # OK, docstrings are handled by another rule
|
||||
"""oof"""
|
||||
print("foo")
|
||||
|
||||
|
||||
def foo(): # ERROR PYI048
|
||||
def foo(): # Ok not in Stub file
|
||||
"""foo"""
|
||||
print("foo")
|
||||
print("foo")
|
||||
|
||||
|
||||
def buzz(): # ERROR PYI048
|
||||
def buzz(): # Ok not in Stub file
|
||||
print("fizz")
|
||||
print("buzz")
|
||||
print("test")
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
def bar(): ... # OK
|
||||
def bar():
|
||||
pass # OK
|
||||
... # OK
|
||||
|
||||
def bar():
|
||||
"""oof""" # OK
|
||||
|
||||
def oof(): # ERROR PYI048
|
||||
"""oof"""
|
||||
print("foo")
|
||||
def oof(): # OK, docstrings are handled by another rule
|
||||
"""oof"""
|
||||
print("foo")
|
||||
|
||||
def foo(): # ERROR PYI048
|
||||
|
||||
|
||||
def foo(): # ERROR PYI048
|
||||
"""foo"""
|
||||
print("foo")
|
||||
print("foo")
|
||||
|
||||
def buzz(): # ERROR PYI048
|
||||
|
||||
def buzz(): # ERROR PYI048
|
||||
print("fizz")
|
||||
print("buzz")
|
||||
print("test")
|
||||
|
||||
@@ -357,9 +357,3 @@ def foo():
|
||||
def foo():
|
||||
a = 1 # Comment
|
||||
return a
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7098
|
||||
def mavko_debari(P_kbar):
|
||||
D=0.4853881 + 3.6006116*P - 0.0117368*(P-1.3822)**2
|
||||
return D
|
||||
|
||||
@@ -13,8 +13,3 @@ def f():
|
||||
return False
|
||||
|
||||
a = True if b else False
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7076
|
||||
samesld = True if (psl.privatesuffix(urlparse(response.url).netloc) ==
|
||||
psl.privatesuffix(src.netloc)) else False
|
||||
|
||||
@@ -152,11 +152,3 @@ if (a or [1] or True or [2]) == (a or [1]): # SIM222
|
||||
|
||||
if f(a or [1] or True or [2]): # SIM222
|
||||
pass
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7099
|
||||
def secondToTime(s0: int) -> (int, int, int) or str:
|
||||
m, s = divmod(s0, 60)
|
||||
|
||||
|
||||
def secondToTime(s0: int) -> ((int, int, int) or str):
|
||||
m, s = divmod(s0, 60)
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
def func():
|
||||
import numpy as np
|
||||
import numpy as np
|
||||
|
||||
np.round_(np.random.rand(5, 5), 2)
|
||||
np.product(np.random.rand(5, 5))
|
||||
np.cumproduct(np.random.rand(5, 5))
|
||||
np.sometrue(np.random.rand(5, 5))
|
||||
np.alltrue(np.random.rand(5, 5))
|
||||
np.round_(np.random.rand(5, 5), 2)
|
||||
np.product(np.random.rand(5, 5))
|
||||
np.cumproduct(np.random.rand(5, 5))
|
||||
np.sometrue(np.random.rand(5, 5))
|
||||
np.alltrue(np.random.rand(5, 5))
|
||||
|
||||
from numpy import round_, product, cumproduct, sometrue, alltrue
|
||||
|
||||
def func():
|
||||
from numpy import round_, product, cumproduct, sometrue, alltrue
|
||||
|
||||
round_(np.random.rand(5, 5), 2)
|
||||
product(np.random.rand(5, 5))
|
||||
cumproduct(np.random.rand(5, 5))
|
||||
sometrue(np.random.rand(5, 5))
|
||||
alltrue(np.random.rand(5, 5))
|
||||
round_(np.random.rand(5, 5), 2)
|
||||
product(np.random.rand(5, 5))
|
||||
cumproduct(np.random.rand(5, 5))
|
||||
sometrue(np.random.rand(5, 5))
|
||||
alltrue(np.random.rand(5, 5))
|
||||
|
||||
@@ -29,5 +29,3 @@ x.apply(lambda x: x.sort_values("a", inplace=True))
|
||||
import torch
|
||||
|
||||
torch.m.ReLU(inplace=True) # safe because this isn't a pandas call
|
||||
|
||||
(x.drop(["a"], axis=1, inplace=True))
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import collections
|
||||
from collections import namedtuple
|
||||
from typing import TypeAlias, TypeVar, NewType, NamedTuple, TypedDict
|
||||
from typing import TypeVar
|
||||
from typing import NewType
|
||||
from typing import NamedTuple, TypedDict
|
||||
|
||||
GLOBAL: str = "foo"
|
||||
|
||||
@@ -19,11 +21,9 @@ def assign():
|
||||
T = TypeVar("T")
|
||||
UserId = NewType("UserId", int)
|
||||
|
||||
Employee = NamedTuple("Employee", [("name", str), ("id", int)])
|
||||
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
|
||||
|
||||
Point2D = TypedDict("Point2D", {"in": int, "x-y": int})
|
||||
|
||||
IntOrStr: TypeAlias = int | str
|
||||
Point2D = TypedDict('Point2D', {'in': int, 'x-y': int})
|
||||
|
||||
|
||||
def aug_assign(rank, world_size):
|
||||
|
||||
@@ -18,11 +18,11 @@ def f():
|
||||
result = []
|
||||
for i in items:
|
||||
if i % 2:
|
||||
result.append(i) # Ok
|
||||
result.append(i) # PERF401
|
||||
elif i % 2:
|
||||
result.append(i)
|
||||
result.append(i) # PERF401
|
||||
else:
|
||||
result.append(i)
|
||||
result.append(i) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
@@ -60,15 +60,3 @@ def f():
|
||||
for i in range(20):
|
||||
foo.fibonacci.append(sum(foo.fibonacci[-2:])) # OK
|
||||
print(foo.fibonacci)
|
||||
|
||||
|
||||
class Foo:
|
||||
def append(self, x):
|
||||
pass
|
||||
|
||||
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = Foo()
|
||||
for i in items:
|
||||
result.append(i) # Ok
|
||||
|
||||
@@ -24,22 +24,3 @@ def f():
|
||||
result = {}
|
||||
for i in items:
|
||||
result[i].append(i * i) # OK
|
||||
|
||||
|
||||
class Foo:
|
||||
def append(self, x):
|
||||
pass
|
||||
|
||||
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = Foo()
|
||||
for i in items:
|
||||
result.append(i) # OK
|
||||
|
||||
|
||||
def f():
|
||||
import sys
|
||||
|
||||
for path in ("foo", "bar"):
|
||||
sys.path.append(path) # OK
|
||||
|
||||
@@ -9,8 +9,3 @@ hidden = {"a": "!"}
|
||||
"%(a)s" % {'a': 1, u"b": "!"} # F504 ("b" not used)
|
||||
|
||||
'' % {'a''b' : ''} # F504 ("ab" not used)
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/4899
|
||||
"" % {
|
||||
'test1': '',
|
||||
'test2': '',
|
||||
|
||||
@@ -165,9 +165,3 @@ def f():
|
||||
x = 1
|
||||
|
||||
y = 2
|
||||
|
||||
|
||||
def f():
|
||||
(x) = foo()
|
||||
((x)) = foo()
|
||||
(x) = (y.z) = foo()
|
||||
|
||||
@@ -91,7 +91,3 @@ def f(x: Optional[int : float]) -> None:
|
||||
|
||||
def f(x: Optional[str, int : float]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: Optional[int, float]) -> None:
|
||||
...
|
||||
|
||||
@@ -22,4 +22,3 @@ MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
|
||||
# unfixable
|
||||
MyType = typing.NamedTuple("MyType", [("a", int)], [("b", str)])
|
||||
MyType = typing.NamedTuple("MyType", [("a", int)], b=str)
|
||||
MyType = typing.NamedTuple(typename="MyType", a=int, b=str)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import subprocess
|
||||
import subprocess as somename
|
||||
from subprocess import run
|
||||
from subprocess import run as anothername
|
||||
|
||||
# Errors
|
||||
subprocess.run(["foo"], universal_newlines=True, check=True)
|
||||
subprocess.run(["foo"], universal_newlines=True, text=True)
|
||||
run(["foo"], universal_newlines=True, check=False)
|
||||
somename.run(["foo"], universal_newlines=True)
|
||||
|
||||
run(["foo"], universal_newlines=True, check=False)
|
||||
anothername(["foo"], universal_newlines=True)
|
||||
|
||||
# OK
|
||||
subprocess.run(["foo"], check=True)
|
||||
|
||||
@@ -35,13 +35,6 @@ if output:
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
output = subprocess.run(
|
||||
["foo"], stdout=subprocess.PIPE, capture_output=True, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
output = subprocess.run(
|
||||
["foo"], stdout=subprocess.PIPE, capture_output=False, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
# Examples that should NOT trigger the rule
|
||||
from foo import PIPE
|
||||
|
||||
@@ -72,12 +72,3 @@ def f():
|
||||
for x, y in z():
|
||||
yield x, y
|
||||
x = 1
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7103
|
||||
def _serve_method(fn):
|
||||
for h in (
|
||||
TaggedText.from_file(args.input)
|
||||
.markup(highlight=args.region)
|
||||
):
|
||||
yield h
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# OK
|
||||
# These should NOT change
|
||||
def f():
|
||||
for x in z:
|
||||
yield
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
def func():
|
||||
pass
|
||||
|
||||
|
||||
nums = []
|
||||
nums2 = []
|
||||
nums3: list[int] = func()
|
||||
nums4: List[int]
|
||||
|
||||
|
||||
class C:
|
||||
def append(self, x):
|
||||
pass
|
||||
|
||||
|
||||
# Errors.
|
||||
|
||||
|
||||
# FURB113
|
||||
nums.append(1)
|
||||
nums.append(2)
|
||||
pass
|
||||
|
||||
|
||||
# FURB113
|
||||
nums3.append(1)
|
||||
nums3.append(2)
|
||||
pass
|
||||
|
||||
|
||||
# FURB113
|
||||
nums4.append(1)
|
||||
nums4.append(2)
|
||||
pass
|
||||
|
||||
|
||||
# FURB113
|
||||
nums.append(1)
|
||||
nums2.append(1)
|
||||
nums.append(2)
|
||||
nums.append(3)
|
||||
pass
|
||||
|
||||
|
||||
# FURB113
|
||||
nums.append(1)
|
||||
nums2.append(1)
|
||||
nums.append(2)
|
||||
# FURB113
|
||||
nums3.append(1)
|
||||
nums.append(3)
|
||||
# FURB113
|
||||
nums4.append(1)
|
||||
nums4.append(2)
|
||||
nums3.append(2)
|
||||
pass
|
||||
|
||||
# FURB113
|
||||
nums.append(1)
|
||||
nums.append(2)
|
||||
nums.append(3)
|
||||
|
||||
|
||||
if True:
|
||||
# FURB113
|
||||
nums.append(1)
|
||||
nums.append(2)
|
||||
|
||||
|
||||
if True:
|
||||
# FURB113
|
||||
nums.append(1)
|
||||
nums.append(2)
|
||||
pass
|
||||
|
||||
|
||||
if True:
|
||||
# FURB113
|
||||
nums.append(1)
|
||||
nums2.append(1)
|
||||
nums.append(2)
|
||||
nums.append(3)
|
||||
|
||||
|
||||
def yes_one(x: list[int]):
|
||||
# FURB113
|
||||
x.append(1)
|
||||
x.append(2)
|
||||
|
||||
|
||||
def yes_two(x: List[int]):
|
||||
# FURB113
|
||||
x.append(1)
|
||||
x.append(2)
|
||||
|
||||
|
||||
def yes_three(*, x: list[int]):
|
||||
# FURB113
|
||||
x.append(1)
|
||||
x.append(2)
|
||||
|
||||
|
||||
def yes_four(x: list[int], /):
|
||||
# FURB113
|
||||
x.append(1)
|
||||
x.append(2)
|
||||
|
||||
|
||||
def yes_five(x: list[int], y: list[int]):
|
||||
# FURB113
|
||||
x.append(1)
|
||||
x.append(2)
|
||||
y.append(1)
|
||||
x.append(3)
|
||||
|
||||
|
||||
def yes_six(x: list):
|
||||
# FURB113
|
||||
x.append(1)
|
||||
x.append(2)
|
||||
|
||||
|
||||
# Non-errors.
|
||||
|
||||
nums.append(1)
|
||||
pass
|
||||
nums.append(2)
|
||||
|
||||
|
||||
if True:
|
||||
nums.append(1)
|
||||
pass
|
||||
nums.append(2)
|
||||
|
||||
|
||||
nums.append(1)
|
||||
pass
|
||||
|
||||
|
||||
nums.append(1)
|
||||
nums2.append(2)
|
||||
|
||||
|
||||
nums.copy()
|
||||
nums.copy()
|
||||
|
||||
|
||||
c = C()
|
||||
c.append(1)
|
||||
c.append(2)
|
||||
|
||||
|
||||
def not_one(x):
|
||||
x.append(1)
|
||||
x.append(2)
|
||||
|
||||
|
||||
def not_two(x: C):
|
||||
x.append(1)
|
||||
x.append(2)
|
||||
|
||||
|
||||
# redefining a list variable with a new type shouldn't confuse ruff.
|
||||
nums2 = C()
|
||||
nums2.append(1)
|
||||
nums2.append(2)
|
||||
@@ -1,64 +0,0 @@
|
||||
from typing import Dict, List
|
||||
|
||||
names = {"key": "value"}
|
||||
nums = [1, 2, 3]
|
||||
x = 42
|
||||
y = "hello"
|
||||
|
||||
# these should match
|
||||
|
||||
# FURB131
|
||||
del nums[:]
|
||||
|
||||
|
||||
# FURB131
|
||||
del names[:]
|
||||
|
||||
|
||||
# FURB131
|
||||
del x, nums[:]
|
||||
|
||||
|
||||
# FURB131
|
||||
del y, names[:], x
|
||||
|
||||
|
||||
def yes_one(x: list[int]):
|
||||
# FURB131
|
||||
del x[:]
|
||||
|
||||
|
||||
def yes_two(x: dict[int, str]):
|
||||
# FURB131
|
||||
del x[:]
|
||||
|
||||
|
||||
def yes_three(x: List[int]):
|
||||
# FURB131
|
||||
del x[:]
|
||||
|
||||
|
||||
def yes_four(x: Dict[int, str]):
|
||||
# FURB131
|
||||
del x[:]
|
||||
|
||||
|
||||
# these should not
|
||||
|
||||
del names["key"]
|
||||
del nums[0]
|
||||
|
||||
x = 1
|
||||
del x
|
||||
|
||||
|
||||
del nums[1:2]
|
||||
|
||||
|
||||
del nums[:2]
|
||||
del nums[1:]
|
||||
del nums[::2]
|
||||
|
||||
|
||||
def no_one(param):
|
||||
del param[:]
|
||||
@@ -1,80 +0,0 @@
|
||||
from typing import Set
|
||||
from some.package.name import foo, bar
|
||||
|
||||
|
||||
s = set()
|
||||
s2 = {}
|
||||
s3: set[int] = foo()
|
||||
|
||||
# these should match
|
||||
|
||||
# FURB132
|
||||
if "x" in s:
|
||||
s.remove("x")
|
||||
|
||||
|
||||
# FURB132
|
||||
if "x" in s2:
|
||||
s2.remove("x")
|
||||
|
||||
|
||||
# FURB132
|
||||
if "x" in s3:
|
||||
s3.remove("x")
|
||||
|
||||
|
||||
var = "y"
|
||||
# FURB132
|
||||
if var in s:
|
||||
s.remove(var)
|
||||
|
||||
|
||||
if f"{var}:{var}" in s:
|
||||
s.remove(f"{var}:{var}")
|
||||
|
||||
|
||||
def identity(x):
|
||||
return x
|
||||
|
||||
|
||||
# TODO: FURB132
|
||||
if identity("x") in s2:
|
||||
s2.remove(identity("x"))
|
||||
|
||||
|
||||
# these should not
|
||||
|
||||
if "x" in s:
|
||||
s.remove("y")
|
||||
|
||||
s.discard("x")
|
||||
|
||||
s2 = set()
|
||||
|
||||
if "x" in s:
|
||||
s2.remove("x")
|
||||
|
||||
if "x" in s:
|
||||
s.remove("x")
|
||||
print("removed item")
|
||||
|
||||
if bar() in s:
|
||||
# bar() might have a side effect
|
||||
s.remove(bar())
|
||||
|
||||
if "x" in s:
|
||||
s.remove("x")
|
||||
else:
|
||||
print("not found")
|
||||
|
||||
class Container:
|
||||
def remove(self, item) -> None:
|
||||
return
|
||||
|
||||
def __contains__(self, other) -> bool:
|
||||
return True
|
||||
|
||||
c = Container()
|
||||
|
||||
if "x" in c:
|
||||
c.remove("x")
|
||||
@@ -12,10 +12,3 @@ sum([[1, 2, 3], [4, 5, 6]],
|
||||
# OK
|
||||
sum([x, y])
|
||||
sum([[1, 2, 3], [4, 5, 6]])
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7059
|
||||
def func():
|
||||
import functools, operator
|
||||
|
||||
sum([x, y], [])
|
||||
|
||||
@@ -8,6 +8,7 @@ use libcst_native::{
|
||||
use ruff_python_ast::Stmt;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_statement;
|
||||
@@ -38,7 +39,7 @@ pub(crate) fn remove_imports<'a>(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Option<String>> {
|
||||
let module_text = locator.slice(stmt);
|
||||
let module_text = locator.slice(stmt.range());
|
||||
let mut tree = match_statement(module_text)?;
|
||||
|
||||
let Statement::Simple(body) = &mut tree else {
|
||||
@@ -117,7 +118,7 @@ pub(crate) fn retain_imports(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
) -> Result<String> {
|
||||
let module_text = locator.slice(stmt);
|
||||
let module_text = locator.slice(stmt.range());
|
||||
let mut tree = match_statement(module_text)?;
|
||||
|
||||
let Statement::Simple(body) = &mut tree else {
|
||||
|
||||
@@ -4,15 +4,17 @@ use std::collections::BTreeSet;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel, SourceMap};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel};
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::autofix::source_map::SourceMap;
|
||||
use crate::linter::FixTable;
|
||||
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 {
|
||||
/// The resulting source code, after applying all fixes.
|
||||
@@ -138,9 +140,10 @@ fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Orderi
|
||||
mod tests {
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, SourceMarker};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::autofix::source_map::SourceMarker;
|
||||
use crate::autofix::{apply_fixes, FixResult};
|
||||
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
|
||||
|
||||
@@ -204,8 +207,14 @@ print("hello world")
|
||||
assert_eq!(
|
||||
source_map.markers(),
|
||||
&[
|
||||
SourceMarker::new(10.into(), 10.into(),),
|
||||
SourceMarker::new(10.into(), 21.into(),),
|
||||
SourceMarker {
|
||||
source: 10.into(),
|
||||
dest: 10.into(),
|
||||
},
|
||||
SourceMarker {
|
||||
source: 10.into(),
|
||||
dest: 21.into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -241,8 +250,14 @@ class A(Bar):
|
||||
assert_eq!(
|
||||
source_map.markers(),
|
||||
&[
|
||||
SourceMarker::new(8.into(), 8.into(),),
|
||||
SourceMarker::new(14.into(), 11.into(),),
|
||||
SourceMarker {
|
||||
source: 8.into(),
|
||||
dest: 8.into(),
|
||||
},
|
||||
SourceMarker {
|
||||
source: 14.into(),
|
||||
dest: 11.into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -274,8 +289,14 @@ class A:
|
||||
assert_eq!(
|
||||
source_map.markers(),
|
||||
&[
|
||||
SourceMarker::new(7.into(), 7.into()),
|
||||
SourceMarker::new(15.into(), 7.into()),
|
||||
SourceMarker {
|
||||
source: 7.into(),
|
||||
dest: 7.into()
|
||||
},
|
||||
SourceMarker {
|
||||
source: 15.into(),
|
||||
dest: 7.into()
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -311,10 +332,22 @@ class A(object):
|
||||
assert_eq!(
|
||||
source_map.markers(),
|
||||
&[
|
||||
SourceMarker::new(8.into(), 8.into()),
|
||||
SourceMarker::new(16.into(), 8.into()),
|
||||
SourceMarker::new(22.into(), 14.into(),),
|
||||
SourceMarker::new(30.into(), 14.into(),),
|
||||
SourceMarker {
|
||||
source: 8.into(),
|
||||
dest: 8.into()
|
||||
},
|
||||
SourceMarker {
|
||||
source: 16.into(),
|
||||
dest: 8.into()
|
||||
},
|
||||
SourceMarker {
|
||||
source: 22.into(),
|
||||
dest: 14.into(),
|
||||
},
|
||||
SourceMarker {
|
||||
source: 30.into(),
|
||||
dest: 14.into(),
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -349,8 +382,14 @@ class A:
|
||||
assert_eq!(
|
||||
source_map.markers(),
|
||||
&[
|
||||
SourceMarker::new(7.into(), 7.into(),),
|
||||
SourceMarker::new(15.into(), 7.into(),),
|
||||
SourceMarker {
|
||||
source: 7.into(),
|
||||
dest: 7.into(),
|
||||
},
|
||||
SourceMarker {
|
||||
source: 15.into(),
|
||||
dest: 7.into(),
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,10 +9,6 @@ impl SourceCodeSnippet {
|
||||
Self(source_code)
|
||||
}
|
||||
|
||||
pub(crate) fn from_str(source_code: &str) -> Self {
|
||||
Self(source_code.to_string())
|
||||
}
|
||||
|
||||
/// Return the full snippet for user-facing display, or `None` if the snippet should be
|
||||
/// truncated.
|
||||
pub(crate) fn full_display(&self) -> Option<&str> {
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::Edit;
|
||||
use ruff_diagnostics::Edit;
|
||||
|
||||
/// Lightweight sourcemap marker representing the source and destination
|
||||
/// position for an [`Edit`].
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SourceMarker {
|
||||
pub(crate) struct SourceMarker {
|
||||
/// Position of the marker in the original source.
|
||||
source: TextSize,
|
||||
pub(crate) source: TextSize,
|
||||
/// Position of the marker in the transformed code.
|
||||
dest: TextSize,
|
||||
}
|
||||
|
||||
impl SourceMarker {
|
||||
pub fn new(source: TextSize, dest: TextSize) -> Self {
|
||||
Self { source, dest }
|
||||
}
|
||||
|
||||
pub const fn source(&self) -> TextSize {
|
||||
self.source
|
||||
}
|
||||
|
||||
pub const fn dest(&self) -> TextSize {
|
||||
self.dest
|
||||
}
|
||||
pub(crate) dest: TextSize,
|
||||
}
|
||||
|
||||
/// A collection of [`SourceMarker`].
|
||||
@@ -32,12 +18,12 @@ impl SourceMarker {
|
||||
/// the transformed code. Here, only the boundaries of edits are tracked instead
|
||||
/// of every single character.
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
pub struct SourceMap(Vec<SourceMarker>);
|
||||
pub(crate) struct SourceMap(Vec<SourceMarker>);
|
||||
|
||||
impl SourceMap {
|
||||
/// Returns a slice of all the markers in the sourcemap in the order they
|
||||
/// were added.
|
||||
pub fn markers(&self) -> &[SourceMarker] {
|
||||
pub(crate) fn markers(&self) -> &[SourceMarker] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
@@ -45,7 +31,7 @@ impl SourceMap {
|
||||
///
|
||||
/// The `output_length` is the length of the transformed string before the
|
||||
/// edit is applied.
|
||||
pub fn push_start_marker(&mut self, edit: &Edit, output_length: TextSize) {
|
||||
pub(crate) fn push_start_marker(&mut self, edit: &Edit, output_length: TextSize) {
|
||||
self.0.push(SourceMarker {
|
||||
source: edit.start(),
|
||||
dest: output_length,
|
||||
@@ -56,7 +42,7 @@ impl SourceMap {
|
||||
///
|
||||
/// The `output_length` is the length of the transformed string after the
|
||||
/// edit has been applied.
|
||||
pub fn push_end_marker(&mut self, edit: &Edit, output_length: TextSize) {
|
||||
pub(crate) fn push_end_marker(&mut self, edit: &Edit, output_length: TextSize) {
|
||||
if edit.is_insertion() {
|
||||
self.0.push(SourceMarker {
|
||||
source: edit.start(),
|
||||
@@ -163,9 +163,9 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
continue;
|
||||
};
|
||||
|
||||
let contents = checker.locator().slice(expr);
|
||||
let contents = checker.locator.slice(expr.range());
|
||||
|
||||
let indentation = checker.locator().slice(TextRange::new(
|
||||
let indentation = checker.locator.slice(TextRange::new(
|
||||
checker.locator.line_start(expr.start()),
|
||||
expr.start(),
|
||||
));
|
||||
|
||||
@@ -1039,7 +1039,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::PrintfStringFormatting) {
|
||||
pyupgrade::rules::printf_string_formatting(checker, expr, right);
|
||||
pyupgrade::rules::printf_string_formatting(
|
||||
checker,
|
||||
expr,
|
||||
right,
|
||||
checker.locator,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::BadStringFormatCharacter) {
|
||||
pylint::rules::bad_string_format_character::percent(checker, expr);
|
||||
@@ -1253,10 +1258,14 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::IfExprWithTrueFalse) {
|
||||
flake8_simplify::rules::if_expr_with_true_false(checker, expr, test, body, orelse);
|
||||
flake8_simplify::rules::explicit_true_false_in_ifexpr(
|
||||
checker, expr, test, body, orelse,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::IfExprWithFalseTrue) {
|
||||
flake8_simplify::rules::if_expr_with_false_true(checker, expr, test, body, orelse);
|
||||
flake8_simplify::rules::explicit_false_true_in_ifexpr(
|
||||
checker, expr, test, body, orelse,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::IfExprWithTwistedArms) {
|
||||
flake8_simplify::rules::twisted_arms_in_ifexpr(checker, expr, test, body, orelse);
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::rules::{
|
||||
flake8_django, flake8_errmsg, flake8_import_conventions, flake8_pie, flake8_pyi,
|
||||
flake8_pytest_style, flake8_raise, flake8_return, flake8_simplify, flake8_slots,
|
||||
flake8_tidy_imports, flake8_type_checking, mccabe, pandas_vet, pep8_naming, perflint,
|
||||
pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops,
|
||||
pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
@@ -1056,9 +1056,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::CheckAndRemoveFromSet) {
|
||||
refurb::rules::check_and_remove_from_set(checker, if_);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnrecognizedVersionInfoCheck,
|
||||
@@ -1462,7 +1459,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Delete(delete @ ast::StmtDelete { targets, range: _ }) => {
|
||||
Stmt::Delete(ast::StmtDelete { targets, range: _ }) => {
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
for target in targets {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target {
|
||||
@@ -1470,9 +1467,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::DeleteFullSlice) {
|
||||
refurb::rules::delete_full_slice(checker, delete);
|
||||
}
|
||||
}
|
||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
|
||||
if checker.enabled(Rule::UselessComparison) {
|
||||
@@ -1490,9 +1484,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::AsyncioDanglingTask) {
|
||||
ruff::rules::asyncio_dangling_task(checker, value);
|
||||
}
|
||||
if checker.enabled(Rule::RepeatedAppend) {
|
||||
refurb::rules::repeated_append(checker, stmt);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ pub(crate) fn check_imports(
|
||||
stylist: &Stylist,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
source_kind: &SourceKind,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_type: PySourceType,
|
||||
) -> (Vec<Diagnostic>, Option<ImportMap>) {
|
||||
// Extract all import blocks from the AST.
|
||||
|
||||
@@ -110,7 +110,7 @@ pub(crate) fn check_noqa(
|
||||
Diagnostic::new(UnusedNOQA { codes: None }, directive.range());
|
||||
if settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
diagnostic
|
||||
.set_fix(Fix::suggested(delete_noqa(directive.range(), locator)));
|
||||
.set_fix(Fix::automatic(delete_noqa(directive.range(), locator)));
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -174,12 +174,12 @@ pub(crate) fn check_noqa(
|
||||
);
|
||||
if settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
if valid_codes.is_empty() {
|
||||
diagnostic.set_fix(Fix::suggested(delete_noqa(
|
||||
diagnostic.set_fix(Fix::automatic(delete_noqa(
|
||||
directive.range(),
|
||||
locator,
|
||||
)));
|
||||
} else {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
format!("# noqa: {}", valid_codes.join(", ")),
|
||||
directive.range(),
|
||||
)));
|
||||
|
||||
@@ -865,11 +865,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Slots, "001") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInTupleSubclass),
|
||||
(Flake8Slots, "002") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInNamedtupleSubclass),
|
||||
|
||||
// refurb
|
||||
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
|
||||
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
|
||||
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ impl StatementVisitor<'_> for StringLinesVisitor<'_> {
|
||||
}) = value.as_ref()
|
||||
{
|
||||
for line in UniversalNewlineIterator::with_offset(
|
||||
self.locator.slice(value.as_ref()),
|
||||
self.locator.slice(value.range()),
|
||||
value.start(),
|
||||
) {
|
||||
self.string_lines.push(line.start());
|
||||
|
||||
@@ -171,8 +171,10 @@ impl<'a> Importer<'a> {
|
||||
at: TextSize,
|
||||
semantic: &SemanticModel,
|
||||
) -> Result<(Edit, String), ResolutionError> {
|
||||
self.get_symbol(symbol, at, semantic)?
|
||||
.map_or_else(|| self.import_symbol(symbol, at, semantic), Ok)
|
||||
match self.get_symbol(symbol, at, semantic) {
|
||||
Some(result) => result,
|
||||
None => self.import_symbol(symbol, at, semantic),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an [`Edit`] to reference an existing symbol, if it's present in the given [`SemanticModel`].
|
||||
@@ -181,13 +183,9 @@ impl<'a> Importer<'a> {
|
||||
symbol: &ImportRequest,
|
||||
at: TextSize,
|
||||
semantic: &SemanticModel,
|
||||
) -> Result<Option<(Edit, String)>, ResolutionError> {
|
||||
) -> Option<Result<(Edit, String), ResolutionError>> {
|
||||
// If the symbol is already available in the current scope, use it.
|
||||
let Some(imported_name) =
|
||||
semantic.resolve_qualified_import_name(symbol.module, symbol.member)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let imported_name = semantic.resolve_qualified_import_name(symbol.module, symbol.member)?;
|
||||
|
||||
// If the symbol source (i.e., the import statement) comes after the current location,
|
||||
// abort. For example, we could be generating an edit within a function, and the import
|
||||
@@ -197,13 +195,13 @@ impl<'a> Importer<'a> {
|
||||
// unclear whether should add an import statement at the start of the file, since it could
|
||||
// be shadowed between the import and the current location.
|
||||
if imported_name.start() > at {
|
||||
return Err(ResolutionError::ImportAfterUsage);
|
||||
return Some(Err(ResolutionError::ImportAfterUsage));
|
||||
}
|
||||
|
||||
// If the symbol source (i.e., the import statement) is in a typing-only context, but we're
|
||||
// in a runtime context, abort.
|
||||
if imported_name.context().is_typing() && semantic.execution_context().is_runtime() {
|
||||
return Err(ResolutionError::IncompatibleContext);
|
||||
return Some(Err(ResolutionError::IncompatibleContext));
|
||||
}
|
||||
|
||||
// We also add a no-op edit to force conflicts with any other fixes that might try to
|
||||
@@ -226,7 +224,7 @@ impl<'a> Importer<'a> {
|
||||
self.locator.slice(imported_name.range()).to_string(),
|
||||
imported_name.range(),
|
||||
);
|
||||
Ok(Some((import_edit, imported_name.into_name())))
|
||||
Some(Ok((import_edit, imported_name.into_name())))
|
||||
}
|
||||
|
||||
/// Generate an [`Edit`] to reference the given symbol. Returns the [`Edit`] necessary to make
|
||||
@@ -321,7 +319,7 @@ impl<'a> Importer<'a> {
|
||||
|
||||
/// Add the given member to an existing `Stmt::ImportFrom` statement.
|
||||
fn add_member(&self, stmt: &Stmt, member: &str) -> Result<Edit> {
|
||||
let mut statement = match_statement(self.locator.slice(stmt))?;
|
||||
let mut statement = match_statement(self.locator.slice(stmt.range()))?;
|
||||
let import_from = match_import_from(&mut statement)?;
|
||||
let aliases = match_aliases(import_from)?;
|
||||
aliases.push(ImportAlias {
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::io::{BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::iter;
|
||||
use std::path::Path;
|
||||
use std::{io, iter};
|
||||
|
||||
use itertools::Itertools;
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::Serialize;
|
||||
use serde_json::error::Category;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use ruff_diagnostics::{SourceMap, SourceMarker};
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_parser::lexer::lex;
|
||||
use ruff_python_parser::Mode;
|
||||
use ruff_source_file::{NewlineWithTrailingNewline, UniversalNewlineIterator};
|
||||
use ruff_text_size::TextSize;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::index::NotebookIndex;
|
||||
use crate::schema::{Cell, RawNotebook, SortAlphabetically, SourceValue};
|
||||
use crate::autofix::source_map::{SourceMap, SourceMarker};
|
||||
use crate::jupyter::index::NotebookIndex;
|
||||
use crate::jupyter::schema::{Cell, RawNotebook, SortAlphabetically, SourceValue};
|
||||
use crate::rules::pycodestyle::rules::SyntaxError;
|
||||
use crate::IOError;
|
||||
|
||||
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> {
|
||||
@@ -31,7 +37,7 @@ pub fn round_trip(path: &Path) -> anyhow::Result<String> {
|
||||
let code = notebook.source_code().to_string();
|
||||
notebook.update_cell_content(&code);
|
||||
let mut writer = Vec::new();
|
||||
notebook.write(&mut writer)?;
|
||||
notebook.write_inner(&mut writer)?;
|
||||
Ok(String::from_utf8(writer)?)
|
||||
}
|
||||
|
||||
@@ -90,21 +96,6 @@ impl Cell {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that can occur while deserializing a Jupyter Notebook.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum NotebookError {
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
#[error(transparent)]
|
||||
Json(serde_json::Error),
|
||||
#[error("Expected a Jupyter Notebook, which must be internally stored as JSON, but this file isn't valid JSON: {0}")]
|
||||
InvalidJson(serde_json::Error),
|
||||
#[error("This file does not match the schema expected of Jupyter Notebooks: {0}")]
|
||||
InvalidSchema(serde_json::Error),
|
||||
#[error("Expected Jupyter Notebook format 4, found: {0}")]
|
||||
InvalidFormat(i64),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Notebook {
|
||||
/// Python source code of the notebook.
|
||||
@@ -130,12 +121,19 @@ pub struct Notebook {
|
||||
|
||||
impl Notebook {
|
||||
/// Read the Jupyter Notebook from the given [`Path`].
|
||||
pub fn from_path(path: &Path) -> Result<Self, NotebookError> {
|
||||
Self::from_reader(BufReader::new(File::open(path)?))
|
||||
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, NotebookError> {
|
||||
pub fn from_source_code(source_code: &str) -> Result<Self, Box<Diagnostic>> {
|
||||
Self::from_reader(Cursor::new(source_code))
|
||||
}
|
||||
|
||||
@@ -143,7 +141,7 @@ impl Notebook {
|
||||
///
|
||||
/// 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, NotebookError>
|
||||
fn from_reader<R>(mut reader: R) -> Result<Self, Box<Diagnostic>>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
@@ -151,27 +149,95 @@ impl Notebook {
|
||||
let mut buf = [0; 1];
|
||||
reader.read_exact(&mut buf).is_ok_and(|_| buf[0] == b'\n')
|
||||
});
|
||||
reader.rewind()?;
|
||||
reader.rewind().map_err(|err| {
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: format!("{err}"),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
})?;
|
||||
let mut raw_notebook: RawNotebook = match serde_json::from_reader(reader.by_ref()) {
|
||||
Ok(notebook) => notebook,
|
||||
Err(err) => {
|
||||
// Translate the error into a diagnostic
|
||||
return Err(match err.classify() {
|
||||
Category::Io => NotebookError::Json(err),
|
||||
Category::Syntax | Category::Eof => NotebookError::InvalidJson(err),
|
||||
Category::Data => {
|
||||
// We could try to read the schema version here but if this fails it's
|
||||
// a bug anyway.
|
||||
NotebookError::InvalidSchema(err)
|
||||
return Err(Box::new({
|
||||
match err.classify() {
|
||||
Category::Io => Diagnostic::new(
|
||||
IOError {
|
||||
message: format!("{err}"),
|
||||
},
|
||||
TextRange::default(),
|
||||
),
|
||||
Category::Syntax | Category::Eof => {
|
||||
// Maybe someone saved the python sources (those with the `# %%` separator)
|
||||
// as jupyter notebook instead. Let's help them.
|
||||
let mut contents = String::new();
|
||||
reader
|
||||
.rewind()
|
||||
.and_then(|_| reader.read_to_string(&mut contents))
|
||||
.map_err(|err| {
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: format!("{err}"),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Check if tokenizing was successful and the file is non-empty
|
||||
if lex(&contents, Mode::Module).any(|result| result.is_err()) {
|
||||
Diagnostic::new(
|
||||
SyntaxError {
|
||||
message: format!(
|
||||
"A Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT}) must internally be JSON, \
|
||||
but this file isn't valid JSON: {err}"
|
||||
),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
} else {
|
||||
Diagnostic::new(
|
||||
SyntaxError {
|
||||
message: format!(
|
||||
"Expected a Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT}), \
|
||||
which must be internally stored as JSON, \
|
||||
but found a Python source file: {err}"
|
||||
),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Category::Data => {
|
||||
// We could try to read the schema version here but if this fails it's
|
||||
// a bug anyway
|
||||
Diagnostic::new(
|
||||
SyntaxError {
|
||||
message: format!(
|
||||
"This file does not match the schema expected of Jupyter Notebooks: {err}"
|
||||
),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// v4 is what everybody uses
|
||||
if raw_notebook.nbformat != 4 {
|
||||
// bail because we should have already failed at the json schema stage
|
||||
return Err(NotebookError::InvalidFormat(raw_notebook.nbformat));
|
||||
return Err(Box::new(Diagnostic::new(
|
||||
SyntaxError {
|
||||
message: format!(
|
||||
"Expected Jupyter Notebook format 4, found {}",
|
||||
raw_notebook.nbformat
|
||||
),
|
||||
},
|
||||
TextRange::default(),
|
||||
)));
|
||||
}
|
||||
|
||||
let valid_code_cells = raw_notebook
|
||||
@@ -238,13 +304,13 @@ impl Notebook {
|
||||
// The first offset is always going to be at 0, so skip it.
|
||||
for offset in self.cell_offsets.iter_mut().skip(1).rev() {
|
||||
let closest_marker = match last_marker {
|
||||
Some(marker) if marker.source() <= *offset => marker,
|
||||
Some(marker) if marker.source <= *offset => marker,
|
||||
_ => {
|
||||
let Some(marker) = source_map
|
||||
.markers()
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|marker| marker.source() <= *offset)
|
||||
.find(|m| m.source <= *offset)
|
||||
else {
|
||||
// There are no markers above the current offset, so we can
|
||||
// stop here.
|
||||
@@ -255,9 +321,9 @@ impl Notebook {
|
||||
}
|
||||
};
|
||||
|
||||
match closest_marker.source().cmp(&closest_marker.dest()) {
|
||||
Ordering::Less => *offset += closest_marker.dest() - closest_marker.source(),
|
||||
Ordering::Greater => *offset -= closest_marker.source() - closest_marker.dest(),
|
||||
match closest_marker.source.cmp(&closest_marker.dest) {
|
||||
Ordering::Less => *offset += closest_marker.dest - closest_marker.source,
|
||||
Ordering::Greater => *offset -= closest_marker.source - closest_marker.dest,
|
||||
Ordering::Equal => (),
|
||||
}
|
||||
}
|
||||
@@ -365,23 +431,18 @@ impl Notebook {
|
||||
/// The index is built only once when required. This is only used to
|
||||
/// report diagnostics, so by that time all of the autofixes must have
|
||||
/// been applied if `--fix` was passed.
|
||||
pub fn index(&self) -> &NotebookIndex {
|
||||
pub(crate) fn index(&self) -> &NotebookIndex {
|
||||
self.index.get_or_init(|| self.build_index())
|
||||
}
|
||||
|
||||
/// Return the cell offsets for the concatenated source code corresponding
|
||||
/// the Jupyter notebook.
|
||||
pub fn cell_offsets(&self) -> &[TextSize] {
|
||||
pub(crate) fn cell_offsets(&self) -> &[TextSize] {
|
||||
&self.cell_offsets
|
||||
}
|
||||
|
||||
/// Return `true` if the notebook has a trailing newline, `false` otherwise.
|
||||
pub fn trailing_newline(&self) -> bool {
|
||||
self.trailing_newline
|
||||
}
|
||||
|
||||
/// Update the notebook with the given sourcemap and transformed content.
|
||||
pub fn update(&mut self, source_map: &SourceMap, transformed: String) {
|
||||
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();
|
||||
@@ -404,8 +465,7 @@ impl Notebook {
|
||||
.map_or(true, |language| language.name == "python")
|
||||
}
|
||||
|
||||
/// Write the notebook back to the given [`Write`] implementor.
|
||||
pub fn write(&self, writer: &mut dyn Write) -> anyhow::Result<()> {
|
||||
fn write_inner(&self, writer: &mut impl Write) -> anyhow::Result<()> {
|
||||
// https://github.com/psf/black/blob/69ca0a4c7a365c5f5eea519a90980bab72cab764/src/black/__init__.py#LL1041
|
||||
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
|
||||
let mut serializer = serde_json::Serializer::with_formatter(writer, formatter);
|
||||
@@ -415,6 +475,13 @@ impl Notebook {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write back with an indent of 1, just like black
|
||||
pub fn write(&self, path: &Path) -> anyhow::Result<()> {
|
||||
let mut writer = BufWriter::new(File::create(path)?);
|
||||
self.write_inner(&mut writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -424,41 +491,58 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::{Cell, Notebook, NotebookError, NotebookIndex};
|
||||
use crate::jupyter::index::NotebookIndex;
|
||||
use crate::jupyter::schema::Cell;
|
||||
use crate::jupyter::Notebook;
|
||||
use crate::registry::Rule;
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::test::{
|
||||
read_jupyter_notebook, test_contents, test_notebook_path, test_resource_path,
|
||||
TestedNotebook,
|
||||
};
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
/// Construct a path to a Jupyter notebook in the `resources/test/fixtures/jupyter` directory.
|
||||
fn notebook_path(path: impl AsRef<Path>) -> std::path::PathBuf {
|
||||
Path::new("./resources/test/fixtures/jupyter").join(path)
|
||||
/// 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 source_code = std::fs::read_to_string(path)?;
|
||||
Ok(serde_json::from_str(&source_code)?)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_python() -> Result<(), NotebookError> {
|
||||
let notebook = Notebook::from_path(¬ebook_path("valid.ipynb"))?;
|
||||
assert!(notebook.is_python_notebook());
|
||||
Ok(())
|
||||
fn test_valid() {
|
||||
assert!(read_jupyter_notebook(Path::new("valid.ipynb")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_r() -> Result<(), NotebookError> {
|
||||
let notebook = Notebook::from_path(¬ebook_path("R.ipynb"))?;
|
||||
assert!(!notebook.is_python_notebook());
|
||||
Ok(())
|
||||
fn test_r() {
|
||||
// We can load this, it will be filtered out later
|
||||
assert!(read_jupyter_notebook(Path::new("R.ipynb")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid() {
|
||||
assert!(matches!(
|
||||
Notebook::from_path(¬ebook_path("invalid_extension.ipynb")),
|
||||
Err(NotebookError::InvalidJson(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
Notebook::from_path(¬ebook_path("not_json.ipynb")),
|
||||
Err(NotebookError::InvalidJson(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
Notebook::from_path(¬ebook_path("wrong_schema.ipynb")),
|
||||
Err(NotebookError::InvalidSchema(_))
|
||||
));
|
||||
let path = Path::new("resources/test/fixtures/jupyter/invalid_extension.ipynb");
|
||||
assert_eq!(
|
||||
Notebook::from_path(path).unwrap_err().kind.body,
|
||||
"SyntaxError: Expected a Jupyter Notebook (.ipynb), \
|
||||
which must be internally stored as JSON, \
|
||||
but found a Python source file: \
|
||||
expected value at line 1 column 1"
|
||||
);
|
||||
let path = Path::new("resources/test/fixtures/jupyter/not_json.ipynb");
|
||||
assert_eq!(
|
||||
Notebook::from_path(path).unwrap_err().kind.body,
|
||||
"SyntaxError: A Jupyter Notebook (.ipynb) must internally be JSON, \
|
||||
but this file isn't valid JSON: \
|
||||
expected value at line 1 column 1"
|
||||
);
|
||||
let path = Path::new("resources/test/fixtures/jupyter/wrong_schema.ipynb");
|
||||
assert_eq!(
|
||||
Notebook::from_path(path).unwrap_err().kind.body,
|
||||
"SyntaxError: This file does not match the schema expected of Jupyter Notebooks: \
|
||||
missing field `cells` at line 1 column 2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(Path::new("markdown.json"), false; "markdown")]
|
||||
@@ -467,20 +551,13 @@ mod tests {
|
||||
#[test_case(Path::new("only_code.json"), true; "only_code")]
|
||||
#[test_case(Path::new("cell_magic.json"), false; "cell_magic")]
|
||||
fn test_is_valid_code_cell(path: &Path, expected: bool) -> Result<()> {
|
||||
/// Read a Jupyter cell from the `resources/test/fixtures/jupyter/cell` directory.
|
||||
fn read_jupyter_cell(path: impl AsRef<Path>) -> Result<Cell> {
|
||||
let path = notebook_path("cell").join(path);
|
||||
let source_code = std::fs::read_to_string(path)?;
|
||||
Ok(serde_json::from_str(&source_code)?)
|
||||
}
|
||||
|
||||
assert_eq!(read_jupyter_cell(path)?.is_valid_code_cell(), expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concat_notebook() -> Result<(), NotebookError> {
|
||||
let notebook = Notebook::from_path(¬ebook_path("valid.ipynb"))?;
|
||||
fn test_concat_notebook() -> Result<()> {
|
||||
let notebook = read_jupyter_notebook(Path::new("valid.ipynb"))?;
|
||||
assert_eq!(
|
||||
notebook.source_code,
|
||||
r#"def unused_variable():
|
||||
@@ -520,4 +597,110 @@ print("after empty cells")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_sorting() -> Result<()> {
|
||||
let path = "isort.ipynb".to_string();
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&path,
|
||||
Path::new("isort_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnsortedImports),
|
||||
)?;
|
||||
assert_messages!(messages, path, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ipy_escape_command() -> Result<()> {
|
||||
let path = "ipy_escape_command.ipynb".to_string();
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&path,
|
||||
Path::new("ipy_escape_command_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
assert_messages!(messages, path, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unused_variable() -> Result<()> {
|
||||
let path = "unused_variable.ipynb".to_string();
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&path,
|
||||
Path::new("unused_variable_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedVariable),
|
||||
)?;
|
||||
assert_messages!(messages, path, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_consistency() -> Result<()> {
|
||||
let path = "before_fix.ipynb".to_string();
|
||||
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();
|
||||
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"))?;
|
||||
assert_eq!(actual, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("before_fix.ipynb"), true; "trailing_newline")]
|
||||
#[test_case(Path::new("no_trailing_newline.ipynb"), false; "no_trailing_newline")]
|
||||
fn test_trailing_newline(path: &Path, trailing_newline: bool) -> Result<()> {
|
||||
let notebook = read_jupyter_notebook(path)?;
|
||||
assert_eq!(notebook.trailing_newline, trailing_newline);
|
||||
|
||||
let mut writer = Vec::new();
|
||||
notebook.write_inner(&mut writer)?;
|
||||
let string = String::from_utf8(writer)?;
|
||||
assert_eq!(string.ends_with('\n'), trailing_newline);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Version <4.5, don't emit cell ids
|
||||
#[test_case(Path::new("no_cell_id.ipynb"), false; "no_cell_id")]
|
||||
// Version 4.5, cell ids are missing and need to be added
|
||||
#[test_case(Path::new("add_missing_cell_id.ipynb"), true; "add_missing_cell_id")]
|
||||
fn test_cell_id(path: &Path, has_id: bool) -> Result<()> {
|
||||
let source_notebook = read_jupyter_notebook(path)?;
|
||||
let source_kind = SourceKind::IpyNotebook(source_notebook);
|
||||
let (_, transformed) = test_contents(
|
||||
&source_kind,
|
||||
path,
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
);
|
||||
let linted_notebook = transformed.into_owned().expect_ipy_notebook();
|
||||
let mut writer = Vec::new();
|
||||
linted_notebook.write_inner(&mut writer)?;
|
||||
let actual = String::from_utf8(writer)?;
|
||||
if has_id {
|
||||
assert!(actual.contains(r#""id": ""#));
|
||||
} else {
|
||||
assert!(!actual.contains(r#""id":"#));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ fn sort_alphabetically<T: Serialize, S: serde::Serializer>(
|
||||
///
|
||||
/// use serde::Serialize;
|
||||
///
|
||||
/// use ruff_notebook::SortAlphabetically;
|
||||
/// use ruff::jupyter::SortAlphabetically;
|
||||
///
|
||||
/// #[derive(Serialize)]
|
||||
/// struct MyStruct {
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/linter.rs
|
||||
source: crates/ruff/src/jupyter/notebook.rs
|
||||
---
|
||||
isort.ipynb:cell 1:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/linter.rs
|
||||
source: crates/ruff/src/jupyter/notebook.rs
|
||||
---
|
||||
ipy_escape_command.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/linter.rs
|
||||
source: crates/ruff/src/jupyter/notebook.rs
|
||||
---
|
||||
unused_variable.ipynb:cell 1:2:5: F841 [*] Local variable `foo1` is assigned to but never used
|
||||
|
|
||||
@@ -6,7 +6,7 @@
|
||||
//! [Ruff]: https://github.com/astral-sh/ruff
|
||||
|
||||
pub use rule_selector::RuleSelector;
|
||||
pub use rules::pycodestyle::rules::{IOError, SyntaxError};
|
||||
pub use rules::pycodestyle::rules::IOError;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -20,6 +20,7 @@ mod doc_lines;
|
||||
mod docstrings;
|
||||
pub mod fs;
|
||||
mod importer;
|
||||
pub mod jupyter;
|
||||
mod lex;
|
||||
pub mod line_width;
|
||||
pub mod linter;
|
||||
|
||||
@@ -6,6 +6,8 @@ use anyhow::{anyhow, Result};
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{AsMode, ParseError};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
@@ -13,8 +15,7 @@ use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{AsMode, ParseError};
|
||||
|
||||
use ruff_source_file::{Locator, SourceFileBuilder};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -81,7 +82,7 @@ pub fn check_path(
|
||||
directives: &Directives,
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
source_kind: &SourceKind,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_type: PySourceType,
|
||||
) -> LinterResult<(Vec<Diagnostic>, Option<ImportMap>)> {
|
||||
// Aggregate all diagnostics.
|
||||
@@ -267,20 +268,17 @@ pub fn check_path(
|
||||
const MAX_ITERATIONS: usize = 100;
|
||||
|
||||
/// Add any missing `# noqa` pragmas to the source code at the given `Path`.
|
||||
pub fn add_noqa_to_path(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
settings: &Settings,
|
||||
) -> Result<usize> {
|
||||
let contents = source_kind.source_code();
|
||||
pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings) -> Result<usize> {
|
||||
let source_type = PySourceType::from(path);
|
||||
|
||||
// Read the file from disk.
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, source_type.as_mode());
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(&contents, source_type.as_mode());
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(contents);
|
||||
let locator = Locator::new(&contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
@@ -310,20 +308,21 @@ pub fn add_noqa_to_path(
|
||||
&directives,
|
||||
settings,
|
||||
flags::Noqa::Disabled,
|
||||
source_kind,
|
||||
None,
|
||||
source_type,
|
||||
);
|
||||
|
||||
// Log any parse errors.
|
||||
if let Some(err) = error {
|
||||
// TODO(dhruvmanila): This should use `SourceKind`, update when
|
||||
// `--add-noqa` is supported for Jupyter notebooks.
|
||||
error!(
|
||||
"{}",
|
||||
DisplayParseError::new(err, locator.to_source_code(), source_kind)
|
||||
DisplayParseError::new(err, locator.to_source_code(), None)
|
||||
);
|
||||
}
|
||||
|
||||
// Add any missing `# noqa` pragmas.
|
||||
// TODO(dhruvmanila): Add support for Jupyter Notebooks
|
||||
add_noqa(
|
||||
path,
|
||||
&diagnostics.0,
|
||||
@@ -376,7 +375,7 @@ pub fn lint_only(
|
||||
&directives,
|
||||
settings,
|
||||
noqa,
|
||||
source_kind,
|
||||
Some(source_kind),
|
||||
source_type,
|
||||
);
|
||||
|
||||
@@ -470,7 +469,7 @@ pub fn lint_fix<'a>(
|
||||
&directives,
|
||||
settings,
|
||||
noqa,
|
||||
source_kind,
|
||||
Some(source_kind),
|
||||
source_type,
|
||||
);
|
||||
|
||||
@@ -607,133 +606,3 @@ This indicates a bug in `{}`. If you could open an issue at:
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::test::{test_contents, test_notebook_path, TestedNotebook};
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
/// Construct a path to a Jupyter notebook in the `resources/test/fixtures/jupyter` directory.
|
||||
fn notebook_path(path: impl AsRef<Path>) -> std::path::PathBuf {
|
||||
Path::new("../ruff_notebook/resources/test/fixtures/jupyter").join(path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_sorting() -> Result<(), NotebookError> {
|
||||
let actual = notebook_path("isort.ipynb");
|
||||
let expected = notebook_path("isort_expected.ipynb");
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::Settings::for_rule(Rule::UnsortedImports),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ipy_escape_command() -> Result<(), NotebookError> {
|
||||
let actual = notebook_path("ipy_escape_command.ipynb");
|
||||
let expected = notebook_path("ipy_escape_command_expected.ipynb");
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unused_variable() -> Result<(), NotebookError> {
|
||||
let actual = notebook_path("unused_variable.ipynb");
|
||||
let expected = notebook_path("unused_variable_expected.ipynb");
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::Settings::for_rule(Rule::UnusedVariable),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_consistency() -> Result<()> {
|
||||
let actual_path = notebook_path("before_fix.ipynb");
|
||||
let expected_path = notebook_path("after_fix.ipynb");
|
||||
|
||||
let TestedNotebook {
|
||||
linted_notebook: fixed_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
actual_path,
|
||||
&expected_path,
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
let mut writer = Vec::new();
|
||||
fixed_notebook.write(&mut writer)?;
|
||||
let actual = String::from_utf8(writer)?;
|
||||
let expected = std::fs::read_to_string(expected_path)?;
|
||||
assert_eq!(actual, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("before_fix.ipynb"), true; "trailing_newline")]
|
||||
#[test_case(Path::new("no_trailing_newline.ipynb"), false; "no_trailing_newline")]
|
||||
fn test_trailing_newline(path: &Path, trailing_newline: bool) -> Result<()> {
|
||||
let notebook = Notebook::from_path(¬ebook_path(path))?;
|
||||
assert_eq!(notebook.trailing_newline(), trailing_newline);
|
||||
|
||||
let mut writer = Vec::new();
|
||||
notebook.write(&mut writer)?;
|
||||
let string = String::from_utf8(writer)?;
|
||||
assert_eq!(string.ends_with('\n'), trailing_newline);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Version <4.5, don't emit cell ids
|
||||
#[test_case(Path::new("no_cell_id.ipynb"), false; "no_cell_id")]
|
||||
// Version 4.5, cell ids are missing and need to be added
|
||||
#[test_case(Path::new("add_missing_cell_id.ipynb"), true; "add_missing_cell_id")]
|
||||
fn test_cell_id(path: &Path, has_id: bool) -> Result<()> {
|
||||
let source_notebook = Notebook::from_path(¬ebook_path(path))?;
|
||||
let source_kind = SourceKind::IpyNotebook(source_notebook);
|
||||
let (_, transformed) = test_contents(
|
||||
&source_kind,
|
||||
path,
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
);
|
||||
let linted_notebook = transformed.into_owned().expect_ipy_notebook();
|
||||
let mut writer = Vec::new();
|
||||
linted_notebook.write(&mut writer)?;
|
||||
let actual = String::from_utf8(writer)?;
|
||||
if has_id {
|
||||
assert!(actual.contains(r#""id": ""#));
|
||||
} else {
|
||||
assert!(!actual.contains(r#""id":"#));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ use ruff_python_parser::{ParseError, ParseErrorType};
|
||||
use ruff_source_file::{OneIndexed, SourceCode, SourceLocation};
|
||||
|
||||
use crate::fs;
|
||||
use crate::jupyter::Notebook;
|
||||
use crate::source_kind::SourceKind;
|
||||
use ruff_notebook::Notebook;
|
||||
|
||||
pub static WARNINGS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::default);
|
||||
|
||||
@@ -139,14 +139,14 @@ pub fn set_up_logging(level: &LogLevel) -> Result<()> {
|
||||
pub struct DisplayParseError<'a> {
|
||||
error: ParseError,
|
||||
source_code: SourceCode<'a, 'a>,
|
||||
source_kind: &'a SourceKind,
|
||||
source_kind: Option<&'a SourceKind>,
|
||||
}
|
||||
|
||||
impl<'a> DisplayParseError<'a> {
|
||||
pub fn new(
|
||||
error: ParseError,
|
||||
source_code: SourceCode<'a, 'a>,
|
||||
source_kind: &'a SourceKind,
|
||||
source_kind: Option<&'a SourceKind>,
|
||||
) -> Self {
|
||||
Self {
|
||||
error,
|
||||
@@ -171,29 +171,32 @@ impl Display for DisplayParseError<'_> {
|
||||
// If we're working on a Jupyter notebook, translate the positions
|
||||
// with respect to the cell and row in the cell. This is the same
|
||||
// format as the `TextEmitter`.
|
||||
let error_location =
|
||||
if let Some(jupyter_index) = self.source_kind.as_ipy_notebook().map(Notebook::index) {
|
||||
write!(
|
||||
f,
|
||||
"cell {cell}{colon}",
|
||||
cell = jupyter_index
|
||||
.cell(source_location.row.get())
|
||||
.unwrap_or_default(),
|
||||
colon = ":".cyan(),
|
||||
)?;
|
||||
let error_location = if let Some(jupyter_index) = self
|
||||
.source_kind
|
||||
.and_then(SourceKind::notebook)
|
||||
.map(Notebook::index)
|
||||
{
|
||||
write!(
|
||||
f,
|
||||
"cell {cell}{colon}",
|
||||
cell = jupyter_index
|
||||
.cell(source_location.row.get())
|
||||
.unwrap_or_default(),
|
||||
colon = ":".cyan(),
|
||||
)?;
|
||||
|
||||
SourceLocation {
|
||||
row: OneIndexed::new(
|
||||
jupyter_index
|
||||
.cell_row(source_location.row.get())
|
||||
.unwrap_or(1) as usize,
|
||||
)
|
||||
.unwrap(),
|
||||
column: source_location.column,
|
||||
}
|
||||
} else {
|
||||
source_location
|
||||
};
|
||||
SourceLocation {
|
||||
row: OneIndexed::new(
|
||||
jupyter_index
|
||||
.cell_row(source_location.row.get())
|
||||
.unwrap_or(1) as usize,
|
||||
)
|
||||
.unwrap(),
|
||||
column: source_location.column,
|
||||
}
|
||||
} else {
|
||||
source_location
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
|
||||
@@ -4,10 +4,10 @@ use std::num::NonZeroUsize;
|
||||
|
||||
use colored::Colorize;
|
||||
|
||||
use ruff_notebook::{Notebook, NotebookIndex};
|
||||
use ruff_source_file::OneIndexed;
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::jupyter::{Notebook, NotebookIndex};
|
||||
use crate::message::diff::calculate_print_width;
|
||||
use crate::message::text::{MessageCodeFrame, RuleCodeAndBody};
|
||||
use crate::message::{
|
||||
|
||||
@@ -14,11 +14,12 @@ pub use json_lines::JsonLinesEmitter;
|
||||
pub use junit::JunitEmitter;
|
||||
pub use pylint::PylintEmitter;
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
|
||||
use ruff_notebook::Notebook;
|
||||
use ruff_source_file::{SourceFile, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
pub use text::TextEmitter;
|
||||
|
||||
use crate::jupyter::Notebook;
|
||||
|
||||
mod azure;
|
||||
mod diff;
|
||||
mod github;
|
||||
|
||||
@@ -7,11 +7,11 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
|
||||
use ruff_notebook::{Notebook, NotebookIndex};
|
||||
use ruff_source_file::{OneIndexed, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::jupyter::{Notebook, NotebookIndex};
|
||||
use crate::line_width::{LineWidthBuilder, TabSize};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
|
||||
@@ -196,9 +196,6 @@ pub enum Linter {
|
||||
/// [Perflint](https://pypi.org/project/perflint/)
|
||||
#[prefix = "PERF"]
|
||||
Perflint,
|
||||
/// [refurb](https://pypi.org/project/refurb/)
|
||||
#[prefix = "FURB"]
|
||||
Refurb,
|
||||
/// Ruff-specific rules
|
||||
#[prefix = "RUF"]
|
||||
Ruff,
|
||||
|
||||
@@ -23,10 +23,6 @@ use ruff_text_size::Ranged;
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// if not x > 0:
|
||||
/// raise ValueError("Expected positive value.")
|
||||
///
|
||||
/// # or even better:
|
||||
/// if x <= 0:
|
||||
/// raise ValueError("Expected positive value.")
|
||||
/// ```
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -80,17 +81,7 @@ pub(crate) fn getattr_with_constant(
|
||||
let mut diagnostic = Diagnostic::new(GetAttrWithConstant, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
if matches!(
|
||||
obj,
|
||||
Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_)
|
||||
) {
|
||||
format!("{}.{}", checker.locator().slice(obj), value)
|
||||
} else {
|
||||
// Defensively parenthesize any other expressions. For example, attribute accesses
|
||||
// on `int` literals must be parenthesized, e.g., `getattr(1, "real")` becomes
|
||||
// `(1).real`. The same is true for named expressions and others.
|
||||
format!("({}).{}", checker.locator().slice(obj), value)
|
||||
},
|
||||
format!("{}.{}", checker.locator().slice(obj.range()), value),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -175,15 +175,8 @@ fn move_initialization(
|
||||
return None;
|
||||
}
|
||||
Edit::insertion(content, locator.line_start(statement.start()))
|
||||
} else if statement.end() == locator.text_len() {
|
||||
// If the statement is at the end of the file, without a trailing newline, insert
|
||||
// _after_ it with an extra newline.
|
||||
Edit::insertion(
|
||||
format!("{}{}", stylist.line_ending().as_str(), content),
|
||||
locator.full_line_end(statement.end()),
|
||||
)
|
||||
} else {
|
||||
// If the docstring is the only statement, insert _after_ it.
|
||||
// If the docstring is the only statement, insert _before_ it.
|
||||
Edit::insertion(content, locator.full_line_end(statement.end()))
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,6 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::map_starred;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -42,7 +41,11 @@ pub struct RedundantTupleInExceptionHandler {
|
||||
impl AlwaysAutofixableViolation for RedundantTupleInExceptionHandler {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("A length-one tuple literal is redundant in exception handlers")
|
||||
let RedundantTupleInExceptionHandler { name } = self;
|
||||
format!(
|
||||
"A length-one tuple literal is redundant. Write `except {name}` instead of `except \
|
||||
({name},)`."
|
||||
)
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
@@ -67,10 +70,9 @@ pub(crate) fn redundant_tuple_in_exception_handler(
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = type_.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
let [elt] = elts.as_slice() else {
|
||||
let [elt] = &elts[..] else {
|
||||
continue;
|
||||
};
|
||||
let elt = map_starred(elt);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
RedundantTupleInExceptionHandler {
|
||||
name: checker.generator().expr(elt),
|
||||
|
||||
@@ -84,7 +84,7 @@ pub(crate) fn unreliable_callable_check(
|
||||
if id == "hasattr" {
|
||||
if checker.semantic().is_builtin("callable") {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
format!("callable({})", checker.locator().slice(obj)),
|
||||
format!("callable({})", checker.locator().slice(obj.range())),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -476,23 +476,4 @@ B006_B008.py:308:52: B006 Do not use mutable data structures for argument defaul
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
B006_B008.py:313:52: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
313 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^ B006
|
||||
314 | """Docstring without newline"""
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
310 310 | """Docstring"""
|
||||
311 311 |
|
||||
312 312 |
|
||||
313 |-def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
314 |- """Docstring without newline"""
|
||||
313 |+def single_line_func_wrong(value: dict[str, str] = None):
|
||||
314 |+ """Docstring without newline"""
|
||||
315 |+ if value is None:
|
||||
316 |+ value = {}
|
||||
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ B009_B010.py:24:15: B009 [*] Do not call `getattr` with a constant attribute val
|
||||
24 |+_ = lambda x: x.bar
|
||||
25 25 | if getattr(x, "bar"):
|
||||
26 26 | pass
|
||||
27 27 | getattr(1, "real")
|
||||
27 27 |
|
||||
|
||||
B009_B010.py:25:4: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
@@ -133,7 +133,6 @@ B009_B010.py:25:4: B009 [*] Do not call `getattr` with a constant attribute valu
|
||||
25 | if getattr(x, "bar"):
|
||||
| ^^^^^^^^^^^^^^^^^ B009
|
||||
26 | pass
|
||||
27 | getattr(1, "real")
|
||||
|
|
||||
= help: Replace `getattr` with attribute access
|
||||
|
||||
@@ -144,176 +143,7 @@ B009_B010.py:25:4: B009 [*] Do not call `getattr` with a constant attribute valu
|
||||
25 |-if getattr(x, "bar"):
|
||||
25 |+if x.bar:
|
||||
26 26 | pass
|
||||
27 27 | getattr(1, "real")
|
||||
28 28 | getattr(1., "real")
|
||||
|
||||
B009_B010.py:27:1: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
25 | if getattr(x, "bar"):
|
||||
26 | pass
|
||||
27 | getattr(1, "real")
|
||||
| ^^^^^^^^^^^^^^^^^^ B009
|
||||
28 | getattr(1., "real")
|
||||
29 | getattr(1.0, "real")
|
||||
|
|
||||
= help: Replace `getattr` with attribute access
|
||||
|
||||
ℹ Suggested fix
|
||||
24 24 | _ = lambda x: getattr(x, "bar")
|
||||
25 25 | if getattr(x, "bar"):
|
||||
26 26 | pass
|
||||
27 |-getattr(1, "real")
|
||||
27 |+(1).real
|
||||
28 28 | getattr(1., "real")
|
||||
29 29 | getattr(1.0, "real")
|
||||
30 30 | getattr(1j, "real")
|
||||
|
||||
B009_B010.py:28:1: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
26 | pass
|
||||
27 | getattr(1, "real")
|
||||
28 | getattr(1., "real")
|
||||
| ^^^^^^^^^^^^^^^^^^^ B009
|
||||
29 | getattr(1.0, "real")
|
||||
30 | getattr(1j, "real")
|
||||
|
|
||||
= help: Replace `getattr` with attribute access
|
||||
|
||||
ℹ Suggested fix
|
||||
25 25 | if getattr(x, "bar"):
|
||||
26 26 | pass
|
||||
27 27 | getattr(1, "real")
|
||||
28 |-getattr(1., "real")
|
||||
28 |+(1.).real
|
||||
29 29 | getattr(1.0, "real")
|
||||
30 30 | getattr(1j, "real")
|
||||
31 31 | getattr(True, "real")
|
||||
|
||||
B009_B010.py:29:1: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
27 | getattr(1, "real")
|
||||
28 | getattr(1., "real")
|
||||
29 | getattr(1.0, "real")
|
||||
| ^^^^^^^^^^^^^^^^^^^^ B009
|
||||
30 | getattr(1j, "real")
|
||||
31 | getattr(True, "real")
|
||||
|
|
||||
= help: Replace `getattr` with attribute access
|
||||
|
||||
ℹ Suggested fix
|
||||
26 26 | pass
|
||||
27 27 | getattr(1, "real")
|
||||
28 28 | getattr(1., "real")
|
||||
29 |-getattr(1.0, "real")
|
||||
29 |+(1.0).real
|
||||
30 30 | getattr(1j, "real")
|
||||
31 31 | getattr(True, "real")
|
||||
32 32 | getattr(x := 1, "real")
|
||||
|
||||
B009_B010.py:30:1: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
28 | getattr(1., "real")
|
||||
29 | getattr(1.0, "real")
|
||||
30 | getattr(1j, "real")
|
||||
| ^^^^^^^^^^^^^^^^^^^ B009
|
||||
31 | getattr(True, "real")
|
||||
32 | getattr(x := 1, "real")
|
||||
|
|
||||
= help: Replace `getattr` with attribute access
|
||||
|
||||
ℹ Suggested fix
|
||||
27 27 | getattr(1, "real")
|
||||
28 28 | getattr(1., "real")
|
||||
29 29 | getattr(1.0, "real")
|
||||
30 |-getattr(1j, "real")
|
||||
30 |+(1j).real
|
||||
31 31 | getattr(True, "real")
|
||||
32 32 | getattr(x := 1, "real")
|
||||
33 33 | getattr(x + y, "real")
|
||||
|
||||
B009_B010.py:31:1: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
29 | getattr(1.0, "real")
|
||||
30 | getattr(1j, "real")
|
||||
31 | getattr(True, "real")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ B009
|
||||
32 | getattr(x := 1, "real")
|
||||
33 | getattr(x + y, "real")
|
||||
|
|
||||
= help: Replace `getattr` with attribute access
|
||||
|
||||
ℹ Suggested fix
|
||||
28 28 | getattr(1., "real")
|
||||
29 29 | getattr(1.0, "real")
|
||||
30 30 | getattr(1j, "real")
|
||||
31 |-getattr(True, "real")
|
||||
31 |+(True).real
|
||||
32 32 | getattr(x := 1, "real")
|
||||
33 33 | getattr(x + y, "real")
|
||||
34 34 | getattr("foo"
|
||||
|
||||
B009_B010.py:32:1: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
30 | getattr(1j, "real")
|
||||
31 | getattr(True, "real")
|
||||
32 | getattr(x := 1, "real")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ B009
|
||||
33 | getattr(x + y, "real")
|
||||
34 | getattr("foo"
|
||||
|
|
||||
= help: Replace `getattr` with attribute access
|
||||
|
||||
ℹ Suggested fix
|
||||
29 29 | getattr(1.0, "real")
|
||||
30 30 | getattr(1j, "real")
|
||||
31 31 | getattr(True, "real")
|
||||
32 |-getattr(x := 1, "real")
|
||||
32 |+(x := 1).real
|
||||
33 33 | getattr(x + y, "real")
|
||||
34 34 | getattr("foo"
|
||||
35 35 | "bar", "real")
|
||||
|
||||
B009_B010.py:33:1: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
31 | getattr(True, "real")
|
||||
32 | getattr(x := 1, "real")
|
||||
33 | getattr(x + y, "real")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ B009
|
||||
34 | getattr("foo"
|
||||
35 | "bar", "real")
|
||||
|
|
||||
= help: Replace `getattr` with attribute access
|
||||
|
||||
ℹ Suggested fix
|
||||
30 30 | getattr(1j, "real")
|
||||
31 31 | getattr(True, "real")
|
||||
32 32 | getattr(x := 1, "real")
|
||||
33 |-getattr(x + y, "real")
|
||||
33 |+(x + y).real
|
||||
34 34 | getattr("foo"
|
||||
35 35 | "bar", "real")
|
||||
36 36 |
|
||||
|
||||
B009_B010.py:34:1: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
32 | getattr(x := 1, "real")
|
||||
33 | getattr(x + y, "real")
|
||||
34 | / getattr("foo"
|
||||
35 | | "bar", "real")
|
||||
| |______________________^ B009
|
||||
|
|
||||
= help: Replace `getattr` with attribute access
|
||||
|
||||
ℹ Suggested fix
|
||||
31 31 | getattr(True, "real")
|
||||
32 32 | getattr(x := 1, "real")
|
||||
33 33 | getattr(x + y, "real")
|
||||
34 |-getattr("foo"
|
||||
35 |- "bar", "real")
|
||||
34 |+("foo"
|
||||
35 |+ "bar").real
|
||||
36 36 |
|
||||
37 37 |
|
||||
38 38 | # Valid setattr usage
|
||||
27 27 |
|
||||
28 28 | # Valid setattr usage
|
||||
|
||||
|
||||
|
||||
@@ -1,120 +1,120 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B009_B010.py:50:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
B009_B010.py:40:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
49 | # Invalid usage
|
||||
50 | setattr(foo, "bar", None)
|
||||
39 | # Invalid usage
|
||||
40 | setattr(foo, "bar", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B010
|
||||
51 | setattr(foo, "_123abc", None)
|
||||
52 | setattr(foo, "__123abc__", None)
|
||||
41 | setattr(foo, "_123abc", None)
|
||||
42 | setattr(foo, "__123abc__", None)
|
||||
|
|
||||
= help: Replace `setattr` with assignment
|
||||
|
||||
ℹ Suggested fix
|
||||
47 47 | pass
|
||||
48 48 |
|
||||
49 49 | # Invalid usage
|
||||
50 |-setattr(foo, "bar", None)
|
||||
50 |+foo.bar = None
|
||||
51 51 | setattr(foo, "_123abc", None)
|
||||
52 52 | setattr(foo, "__123abc__", None)
|
||||
53 53 | setattr(foo, "abc123", None)
|
||||
37 37 | pass
|
||||
38 38 |
|
||||
39 39 | # Invalid usage
|
||||
40 |-setattr(foo, "bar", None)
|
||||
40 |+foo.bar = None
|
||||
41 41 | setattr(foo, "_123abc", None)
|
||||
42 42 | setattr(foo, "__123abc__", None)
|
||||
43 43 | setattr(foo, "abc123", None)
|
||||
|
||||
B009_B010.py:51:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
B009_B010.py:41:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
49 | # Invalid usage
|
||||
50 | setattr(foo, "bar", None)
|
||||
51 | setattr(foo, "_123abc", None)
|
||||
39 | # Invalid usage
|
||||
40 | setattr(foo, "bar", None)
|
||||
41 | setattr(foo, "_123abc", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B010
|
||||
52 | setattr(foo, "__123abc__", None)
|
||||
53 | setattr(foo, "abc123", None)
|
||||
42 | setattr(foo, "__123abc__", None)
|
||||
43 | setattr(foo, "abc123", None)
|
||||
|
|
||||
= help: Replace `setattr` with assignment
|
||||
|
||||
ℹ Suggested fix
|
||||
48 48 |
|
||||
49 49 | # Invalid usage
|
||||
50 50 | setattr(foo, "bar", None)
|
||||
51 |-setattr(foo, "_123abc", None)
|
||||
51 |+foo._123abc = None
|
||||
52 52 | setattr(foo, "__123abc__", None)
|
||||
53 53 | setattr(foo, "abc123", None)
|
||||
54 54 | setattr(foo, r"abc123", None)
|
||||
38 38 |
|
||||
39 39 | # Invalid usage
|
||||
40 40 | setattr(foo, "bar", None)
|
||||
41 |-setattr(foo, "_123abc", None)
|
||||
41 |+foo._123abc = None
|
||||
42 42 | setattr(foo, "__123abc__", None)
|
||||
43 43 | setattr(foo, "abc123", None)
|
||||
44 44 | setattr(foo, r"abc123", None)
|
||||
|
||||
B009_B010.py:52:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
B009_B010.py:42:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
50 | setattr(foo, "bar", None)
|
||||
51 | setattr(foo, "_123abc", None)
|
||||
52 | setattr(foo, "__123abc__", None)
|
||||
40 | setattr(foo, "bar", None)
|
||||
41 | setattr(foo, "_123abc", None)
|
||||
42 | setattr(foo, "__123abc__", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B010
|
||||
53 | setattr(foo, "abc123", None)
|
||||
54 | setattr(foo, r"abc123", None)
|
||||
43 | setattr(foo, "abc123", None)
|
||||
44 | setattr(foo, r"abc123", None)
|
||||
|
|
||||
= help: Replace `setattr` with assignment
|
||||
|
||||
ℹ Suggested fix
|
||||
49 49 | # Invalid usage
|
||||
50 50 | setattr(foo, "bar", None)
|
||||
51 51 | setattr(foo, "_123abc", None)
|
||||
52 |-setattr(foo, "__123abc__", None)
|
||||
52 |+foo.__123abc__ = None
|
||||
53 53 | setattr(foo, "abc123", None)
|
||||
54 54 | setattr(foo, r"abc123", None)
|
||||
55 55 | setattr(foo.bar, r"baz", None)
|
||||
39 39 | # Invalid usage
|
||||
40 40 | setattr(foo, "bar", None)
|
||||
41 41 | setattr(foo, "_123abc", None)
|
||||
42 |-setattr(foo, "__123abc__", None)
|
||||
42 |+foo.__123abc__ = None
|
||||
43 43 | setattr(foo, "abc123", None)
|
||||
44 44 | setattr(foo, r"abc123", None)
|
||||
45 45 | setattr(foo.bar, r"baz", None)
|
||||
|
||||
B009_B010.py:53:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
B009_B010.py:43:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
51 | setattr(foo, "_123abc", None)
|
||||
52 | setattr(foo, "__123abc__", None)
|
||||
53 | setattr(foo, "abc123", None)
|
||||
41 | setattr(foo, "_123abc", None)
|
||||
42 | setattr(foo, "__123abc__", None)
|
||||
43 | setattr(foo, "abc123", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B010
|
||||
54 | setattr(foo, r"abc123", None)
|
||||
55 | setattr(foo.bar, r"baz", None)
|
||||
44 | setattr(foo, r"abc123", None)
|
||||
45 | setattr(foo.bar, r"baz", None)
|
||||
|
|
||||
= help: Replace `setattr` with assignment
|
||||
|
||||
ℹ Suggested fix
|
||||
50 50 | setattr(foo, "bar", None)
|
||||
51 51 | setattr(foo, "_123abc", None)
|
||||
52 52 | setattr(foo, "__123abc__", None)
|
||||
53 |-setattr(foo, "abc123", None)
|
||||
53 |+foo.abc123 = None
|
||||
54 54 | setattr(foo, r"abc123", None)
|
||||
55 55 | setattr(foo.bar, r"baz", None)
|
||||
40 40 | setattr(foo, "bar", None)
|
||||
41 41 | setattr(foo, "_123abc", None)
|
||||
42 42 | setattr(foo, "__123abc__", None)
|
||||
43 |-setattr(foo, "abc123", None)
|
||||
43 |+foo.abc123 = None
|
||||
44 44 | setattr(foo, r"abc123", None)
|
||||
45 45 | setattr(foo.bar, r"baz", None)
|
||||
|
||||
B009_B010.py:54:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
B009_B010.py:44:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
52 | setattr(foo, "__123abc__", None)
|
||||
53 | setattr(foo, "abc123", None)
|
||||
54 | setattr(foo, r"abc123", None)
|
||||
42 | setattr(foo, "__123abc__", None)
|
||||
43 | setattr(foo, "abc123", None)
|
||||
44 | setattr(foo, r"abc123", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B010
|
||||
55 | setattr(foo.bar, r"baz", None)
|
||||
45 | setattr(foo.bar, r"baz", None)
|
||||
|
|
||||
= help: Replace `setattr` with assignment
|
||||
|
||||
ℹ Suggested fix
|
||||
51 51 | setattr(foo, "_123abc", None)
|
||||
52 52 | setattr(foo, "__123abc__", None)
|
||||
53 53 | setattr(foo, "abc123", None)
|
||||
54 |-setattr(foo, r"abc123", None)
|
||||
54 |+foo.abc123 = None
|
||||
55 55 | setattr(foo.bar, r"baz", None)
|
||||
41 41 | setattr(foo, "_123abc", None)
|
||||
42 42 | setattr(foo, "__123abc__", None)
|
||||
43 43 | setattr(foo, "abc123", None)
|
||||
44 |-setattr(foo, r"abc123", None)
|
||||
44 |+foo.abc123 = None
|
||||
45 45 | setattr(foo.bar, r"baz", None)
|
||||
|
||||
B009_B010.py:55:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
B009_B010.py:45:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
53 | setattr(foo, "abc123", None)
|
||||
54 | setattr(foo, r"abc123", None)
|
||||
55 | setattr(foo.bar, r"baz", None)
|
||||
43 | setattr(foo, "abc123", None)
|
||||
44 | setattr(foo, r"abc123", None)
|
||||
45 | setattr(foo.bar, r"baz", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B010
|
||||
|
|
||||
= help: Replace `setattr` with assignment
|
||||
|
||||
ℹ Suggested fix
|
||||
52 52 | setattr(foo, "__123abc__", None)
|
||||
53 53 | setattr(foo, "abc123", None)
|
||||
54 54 | setattr(foo, r"abc123", None)
|
||||
55 |-setattr(foo.bar, r"baz", None)
|
||||
55 |+foo.bar.baz = None
|
||||
42 42 | setattr(foo, "__123abc__", None)
|
||||
43 43 | setattr(foo, "abc123", None)
|
||||
44 44 | setattr(foo, r"abc123", None)
|
||||
45 |-setattr(foo.bar, r"baz", None)
|
||||
45 |+foo.bar.baz = None
|
||||
|
||||
|
||||
|
||||
@@ -1,43 +1,24 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B013.py:5:8: B013 [*] A length-one tuple literal is redundant in exception handlers
|
||||
B013.py:3:8: B013 [*] A length-one tuple literal is redundant. Write `except ValueError` instead of `except (ValueError,)`.
|
||||
|
|
||||
3 | try:
|
||||
4 | pass
|
||||
5 | except (ValueError,):
|
||||
1 | try:
|
||||
2 | pass
|
||||
3 | except (ValueError,):
|
||||
| ^^^^^^^^^^^^^ B013
|
||||
6 | pass
|
||||
7 | except AttributeError:
|
||||
4 | pass
|
||||
5 | except AttributeError:
|
||||
|
|
||||
= help: Replace with `except ValueError`
|
||||
|
||||
ℹ Fix
|
||||
2 2 |
|
||||
3 3 | try:
|
||||
1 1 | try:
|
||||
2 2 | pass
|
||||
3 |-except (ValueError,):
|
||||
3 |+except ValueError:
|
||||
4 4 | pass
|
||||
5 |-except (ValueError,):
|
||||
5 |+except ValueError:
|
||||
5 5 | except AttributeError:
|
||||
6 6 | pass
|
||||
7 7 | except AttributeError:
|
||||
8 8 | pass
|
||||
|
||||
B013.py:11:8: B013 [*] A length-one tuple literal is redundant in exception handlers
|
||||
|
|
||||
9 | except (ImportError, TypeError):
|
||||
10 | pass
|
||||
11 | except (*retriable_exceptions,):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B013
|
||||
12 | pass
|
||||
|
|
||||
= help: Replace with `except retriable_exceptions`
|
||||
|
||||
ℹ Fix
|
||||
8 8 | pass
|
||||
9 9 | except (ImportError, TypeError):
|
||||
10 10 | pass
|
||||
11 |-except (*retriable_exceptions,):
|
||||
11 |+except retriable_exceptions:
|
||||
12 12 | pass
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ pub(crate) fn fix_unnecessary_generator_list(
|
||||
expr: &Expr,
|
||||
) -> Result<Edit> {
|
||||
// Expr(Call(GeneratorExp)))) -> Expr(ListComp)))
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -63,7 +63,7 @@ pub(crate) fn fix_unnecessary_generator_set(checker: &Checker, expr: &Expr) -> R
|
||||
let stylist = checker.stylist();
|
||||
|
||||
// Expr(Call(GeneratorExp)))) -> Expr(SetComp)))
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -97,7 +97,7 @@ pub(crate) fn fix_unnecessary_generator_dict(checker: &Checker, expr: &Expr) ->
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -141,7 +141,7 @@ pub(crate) fn fix_unnecessary_list_comprehension_set(
|
||||
let stylist = checker.stylist();
|
||||
// Expr(Call(ListComp)))) ->
|
||||
// Expr(SetComp)))
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -176,7 +176,7 @@ pub(crate) fn fix_unnecessary_list_comprehension_dict(
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -261,7 +261,7 @@ pub(crate) fn fix_unnecessary_literal_set(checker: &Checker, expr: &Expr) -> Res
|
||||
let stylist = checker.stylist();
|
||||
|
||||
// Expr(Call(List|Tuple)))) -> Expr(Set)))
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -302,7 +302,7 @@ pub(crate) fn fix_unnecessary_literal_dict(checker: &Checker, expr: &Expr) -> Re
|
||||
let stylist = checker.stylist();
|
||||
|
||||
// Expr(Call(List|Tuple)))) -> Expr(Dict)))
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -371,7 +371,7 @@ pub(crate) fn fix_unnecessary_collection_call(checker: &Checker, expr: &Expr) ->
|
||||
let stylist = checker.stylist();
|
||||
|
||||
// Expr(Call("list" | "tuple" | "dict")))) -> Expr(List|Tuple|Dict)
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call(&tree)?;
|
||||
let name = match_name(&call.func)?;
|
||||
@@ -522,7 +522,7 @@ pub(crate) fn fix_unnecessary_literal_within_tuple_call(
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -572,7 +572,7 @@ pub(crate) fn fix_unnecessary_literal_within_list_call(
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -625,7 +625,7 @@ pub(crate) fn fix_unnecessary_list_call(
|
||||
expr: &Expr,
|
||||
) -> Result<Edit> {
|
||||
// Expr(Call(List|Tuple)))) -> Expr(List|Tuple)))
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -646,7 +646,7 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let outer_call = match_call_mut(&mut tree)?;
|
||||
let inner_call = match &outer_call.args[..] {
|
||||
@@ -758,7 +758,7 @@ pub(crate) fn fix_unnecessary_double_cast_or_process(
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let outer_call = match_call_mut(&mut tree)?;
|
||||
|
||||
@@ -789,7 +789,7 @@ pub(crate) fn fix_unnecessary_comprehension(
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
|
||||
match &tree {
|
||||
@@ -878,7 +878,7 @@ pub(crate) fn fix_unnecessary_map(
|
||||
parent: Option<&Expr>,
|
||||
object_type: ObjectType,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -966,21 +966,21 @@ pub(crate) fn fix_unnecessary_map(
|
||||
}));
|
||||
}
|
||||
ObjectType::Dict => {
|
||||
let elements = match func_body.body.as_ref() {
|
||||
Expression::Tuple(tuple) => &tuple.elements,
|
||||
Expression::List(list) => &list.elements,
|
||||
_ => {
|
||||
bail!("Expected tuple or list for dictionary comprehension")
|
||||
let (key, value) = if let Expression::Tuple(tuple) = func_body.body.as_ref() {
|
||||
if tuple.elements.len() != 2 {
|
||||
bail!("Expected two elements")
|
||||
}
|
||||
};
|
||||
let [key, value] = elements.as_slice() else {
|
||||
bail!("Expected container to include two elements");
|
||||
};
|
||||
let Element::Simple { value: key, .. } = key else {
|
||||
bail!("Expected container to use a key as the first element");
|
||||
};
|
||||
let Element::Simple { value, .. } = value else {
|
||||
bail!("Expected container to use a value as the second element");
|
||||
|
||||
let Some(Element::Simple { value: key, .. }) = &tuple.elements.get(0) else {
|
||||
bail!("Expected tuple to contain a key as the first element");
|
||||
};
|
||||
let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) else {
|
||||
bail!("Expected tuple to contain a key as the second element");
|
||||
};
|
||||
|
||||
(key, value)
|
||||
} else {
|
||||
bail!("Expected tuple for dict comprehension")
|
||||
};
|
||||
|
||||
tree = Expression::DictComp(Box::new(DictComp {
|
||||
@@ -1021,7 +1021,7 @@ pub(crate) fn fix_unnecessary_literal_within_dict_call(
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -1041,7 +1041,7 @@ pub(crate) fn fix_unnecessary_comprehension_any_all(
|
||||
expr: &Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(ListComp) -> Expr(GeneratorExp)
|
||||
let module_text = locator.slice(expr);
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ pub(crate) fn unnecessary_map(
|
||||
ObjectType::Generator => {
|
||||
// Exclude the parent if already matched by other arms.
|
||||
if parent
|
||||
.and_then(Expr::as_call_expr)
|
||||
.and_then(ruff_python_ast::Expr::as_call_expr)
|
||||
.and_then(|call| call.func.as_name_expr())
|
||||
.is_some_and(|name| matches!(name.id.as_str(), "list" | "set" | "dict"))
|
||||
{
|
||||
@@ -122,7 +122,7 @@ pub(crate) fn unnecessary_map(
|
||||
// Only flag, e.g., `list(map(lambda x: x + 1, iterable))`.
|
||||
let [Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
arguments: Arguments { args, .. },
|
||||
..
|
||||
})] = args
|
||||
else {
|
||||
@@ -133,10 +133,6 @@ pub(crate) fn unnecessary_map(
|
||||
return;
|
||||
}
|
||||
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(argument) = helpers::first_argument_with_matching_function("map", func, args)
|
||||
else {
|
||||
return;
|
||||
@@ -167,21 +163,13 @@ pub(crate) fn unnecessary_map(
|
||||
// Only flag, e.g., `dict(map(lambda v: (v, v ** 2), values))`.
|
||||
let [Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
arguments: Arguments { args, .. },
|
||||
..
|
||||
})] = args
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if args.len() != 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(argument) = helpers::first_argument_with_matching_function("map", func, args)
|
||||
else {
|
||||
return;
|
||||
|
||||
@@ -61,7 +61,7 @@ C417.py:5:1: C417 [*] Unnecessary `map` usage (rewrite using a `list` comprehens
|
||||
5 |+[x * 2 for x in nums]
|
||||
6 6 | set(map(lambda x: x % 2 == 0, nums))
|
||||
7 7 | dict(map(lambda v: (v, v**2), nums))
|
||||
8 8 | dict(map(lambda v: [v, v**2], nums))
|
||||
8 8 | map(lambda: "const", nums)
|
||||
|
||||
C417.py:6:1: C417 [*] Unnecessary `map` usage (rewrite using a `set` comprehension)
|
||||
|
|
||||
@@ -70,7 +70,7 @@ C417.py:6:1: C417 [*] Unnecessary `map` usage (rewrite using a `set` comprehensi
|
||||
6 | set(map(lambda x: x % 2 == 0, nums))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
7 | dict(map(lambda v: (v, v**2), nums))
|
||||
8 | dict(map(lambda v: [v, v**2], nums))
|
||||
8 | map(lambda: "const", nums)
|
||||
|
|
||||
= help: Replace `map` with a `set` comprehension
|
||||
|
||||
@@ -81,8 +81,8 @@ C417.py:6:1: C417 [*] Unnecessary `map` usage (rewrite using a `set` comprehensi
|
||||
6 |-set(map(lambda x: x % 2 == 0, nums))
|
||||
6 |+{x % 2 == 0 for x in nums}
|
||||
7 7 | dict(map(lambda v: (v, v**2), nums))
|
||||
8 8 | dict(map(lambda v: [v, v**2], nums))
|
||||
9 9 | map(lambda: "const", nums)
|
||||
8 8 | map(lambda: "const", nums)
|
||||
9 9 | map(lambda _: 3.0, nums)
|
||||
|
||||
C417.py:7:1: C417 [*] Unnecessary `map` usage (rewrite using a `dict` comprehension)
|
||||
|
|
||||
@@ -90,8 +90,8 @@ C417.py:7:1: C417 [*] Unnecessary `map` usage (rewrite using a `dict` comprehens
|
||||
6 | set(map(lambda x: x % 2 == 0, nums))
|
||||
7 | dict(map(lambda v: (v, v**2), nums))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
8 | dict(map(lambda v: [v, v**2], nums))
|
||||
9 | map(lambda: "const", nums)
|
||||
8 | map(lambda: "const", nums)
|
||||
9 | map(lambda _: 3.0, nums)
|
||||
|
|
||||
= help: Replace `map` with a `dict` comprehension
|
||||
|
||||
@@ -101,193 +101,172 @@ C417.py:7:1: C417 [*] Unnecessary `map` usage (rewrite using a `dict` comprehens
|
||||
6 6 | set(map(lambda x: x % 2 == 0, nums))
|
||||
7 |-dict(map(lambda v: (v, v**2), nums))
|
||||
7 |+{v: v**2 for v in nums}
|
||||
8 8 | dict(map(lambda v: [v, v**2], nums))
|
||||
9 9 | map(lambda: "const", nums)
|
||||
10 10 | map(lambda _: 3.0, nums)
|
||||
8 8 | map(lambda: "const", nums)
|
||||
9 9 | map(lambda _: 3.0, nums)
|
||||
10 10 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
|
||||
C417.py:8:1: C417 [*] Unnecessary `map` usage (rewrite using a `dict` comprehension)
|
||||
C417.py:8:1: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
|
|
||||
6 | set(map(lambda x: x % 2 == 0, nums))
|
||||
7 | dict(map(lambda v: (v, v**2), nums))
|
||||
8 | dict(map(lambda v: [v, v**2], nums))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
9 | map(lambda: "const", nums)
|
||||
10 | map(lambda _: 3.0, nums)
|
||||
8 | map(lambda: "const", nums)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
9 | map(lambda _: 3.0, nums)
|
||||
10 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
|
|
||||
= help: Replace `map` with a `dict` comprehension
|
||||
= help: Replace `map` with a generator expression
|
||||
|
||||
ℹ Suggested fix
|
||||
5 5 | list(map(lambda x: x * 2, nums))
|
||||
6 6 | set(map(lambda x: x % 2 == 0, nums))
|
||||
7 7 | dict(map(lambda v: (v, v**2), nums))
|
||||
8 |-dict(map(lambda v: [v, v**2], nums))
|
||||
8 |+{v: v**2 for v in nums}
|
||||
9 9 | map(lambda: "const", nums)
|
||||
10 10 | map(lambda _: 3.0, nums)
|
||||
11 11 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
8 |-map(lambda: "const", nums)
|
||||
8 |+("const" for _ in nums)
|
||||
9 9 | map(lambda _: 3.0, nums)
|
||||
10 10 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
11 11 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
|
||||
C417.py:9:1: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
|
|
||||
7 | dict(map(lambda v: (v, v**2), nums))
|
||||
8 | dict(map(lambda v: [v, v**2], nums))
|
||||
9 | map(lambda: "const", nums)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
10 | map(lambda _: 3.0, nums)
|
||||
11 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
8 | map(lambda: "const", nums)
|
||||
9 | map(lambda _: 3.0, nums)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
10 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
11 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
|
|
||||
= help: Replace `map` with a generator expression
|
||||
|
||||
ℹ Suggested fix
|
||||
6 6 | set(map(lambda x: x % 2 == 0, nums))
|
||||
7 7 | dict(map(lambda v: (v, v**2), nums))
|
||||
8 8 | dict(map(lambda v: [v, v**2], nums))
|
||||
9 |-map(lambda: "const", nums)
|
||||
9 |+("const" for _ in nums)
|
||||
10 10 | map(lambda _: 3.0, nums)
|
||||
11 11 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
12 12 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
8 8 | map(lambda: "const", nums)
|
||||
9 |-map(lambda _: 3.0, nums)
|
||||
9 |+(3.0 for _ in nums)
|
||||
10 10 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
11 11 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
12 12 | filter(func, map(lambda v: v, nums))
|
||||
|
||||
C417.py:10:1: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
C417.py:10:13: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
|
|
||||
8 | dict(map(lambda v: [v, v**2], nums))
|
||||
9 | map(lambda: "const", nums)
|
||||
10 | map(lambda _: 3.0, nums)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
11 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
12 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
8 | map(lambda: "const", nums)
|
||||
9 | map(lambda _: 3.0, nums)
|
||||
10 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
11 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
12 | filter(func, map(lambda v: v, nums))
|
||||
|
|
||||
= help: Replace `map` with a generator expression
|
||||
|
||||
ℹ Suggested fix
|
||||
7 7 | dict(map(lambda v: (v, v**2), nums))
|
||||
8 8 | dict(map(lambda v: [v, v**2], nums))
|
||||
9 9 | map(lambda: "const", nums)
|
||||
10 |-map(lambda _: 3.0, nums)
|
||||
10 |+(3.0 for _ in nums)
|
||||
11 11 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
12 12 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
13 13 | filter(func, map(lambda v: v, nums))
|
||||
8 8 | map(lambda: "const", nums)
|
||||
9 9 | map(lambda _: 3.0, nums)
|
||||
10 |-_ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
10 |+_ = "".join((x in nums and "1" or "0" for x in range(123)))
|
||||
11 11 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
12 12 | filter(func, map(lambda v: v, nums))
|
||||
13 13 |
|
||||
|
||||
C417.py:11:13: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
C417.py:11:5: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
|
|
||||
9 | map(lambda: "const", nums)
|
||||
10 | map(lambda _: 3.0, nums)
|
||||
11 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
12 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
13 | filter(func, map(lambda v: v, nums))
|
||||
|
|
||||
= help: Replace `map` with a generator expression
|
||||
|
||||
ℹ Suggested fix
|
||||
8 8 | dict(map(lambda v: [v, v**2], nums))
|
||||
9 9 | map(lambda: "const", nums)
|
||||
10 10 | map(lambda _: 3.0, nums)
|
||||
11 |-_ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
11 |+_ = "".join((x in nums and "1" or "0" for x in range(123)))
|
||||
12 12 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
13 13 | filter(func, map(lambda v: v, nums))
|
||||
14 14 |
|
||||
|
||||
C417.py:12:5: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
|
|
||||
10 | map(lambda _: 3.0, nums)
|
||||
11 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
12 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
9 | map(lambda _: 3.0, nums)
|
||||
10 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
11 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
13 | filter(func, map(lambda v: v, nums))
|
||||
12 | filter(func, map(lambda v: v, nums))
|
||||
|
|
||||
= help: Replace `map` with a generator expression
|
||||
|
||||
ℹ Suggested fix
|
||||
9 9 | map(lambda: "const", nums)
|
||||
10 10 | map(lambda _: 3.0, nums)
|
||||
11 11 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
12 |-all(map(lambda v: isinstance(v, dict), nums))
|
||||
12 |+all((isinstance(v, dict) for v in nums))
|
||||
13 13 | filter(func, map(lambda v: v, nums))
|
||||
14 14 |
|
||||
15 15 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
8 8 | map(lambda: "const", nums)
|
||||
9 9 | map(lambda _: 3.0, nums)
|
||||
10 10 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
11 |-all(map(lambda v: isinstance(v, dict), nums))
|
||||
11 |+all((isinstance(v, dict) for v in nums))
|
||||
12 12 | filter(func, map(lambda v: v, nums))
|
||||
13 13 |
|
||||
14 14 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
|
||||
C417.py:13:14: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
C417.py:12:14: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
|
|
||||
11 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
12 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
13 | filter(func, map(lambda v: v, nums))
|
||||
10 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
11 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
12 | filter(func, map(lambda v: v, nums))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
14 |
|
||||
15 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
13 |
|
||||
14 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
|
|
||||
= help: Replace `map` with a generator expression
|
||||
|
||||
ℹ Suggested fix
|
||||
10 10 | map(lambda _: 3.0, nums)
|
||||
11 11 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
12 12 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
13 |-filter(func, map(lambda v: v, nums))
|
||||
13 |+filter(func, (v for v in nums))
|
||||
14 14 |
|
||||
15 15 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
16 16 | _ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
9 9 | map(lambda _: 3.0, nums)
|
||||
10 10 | _ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
11 11 | all(map(lambda v: isinstance(v, dict), nums))
|
||||
12 |-filter(func, map(lambda v: v, nums))
|
||||
12 |+filter(func, (v for v in nums))
|
||||
13 13 |
|
||||
14 14 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
15 15 | _ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
|
||||
C417.py:16:8: C417 [*] Unnecessary `map` usage (rewrite using a `set` comprehension)
|
||||
C417.py:15:8: C417 [*] Unnecessary `map` usage (rewrite using a `set` comprehension)
|
||||
|
|
||||
15 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
16 | _ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
14 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
15 | _ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
17 | _ = f"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
16 | _ = f"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
|
|
||||
= help: Replace `map` with a `set` comprehension
|
||||
|
||||
ℹ Suggested fix
|
||||
13 13 | filter(func, map(lambda v: v, nums))
|
||||
14 14 |
|
||||
15 15 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
16 |-_ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
16 |+_ = f"{ {x % 2 == 0 for x in nums} }"
|
||||
17 17 | _ = f"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
18 18 |
|
||||
19 19 | # False negatives.
|
||||
12 12 | filter(func, map(lambda v: v, nums))
|
||||
13 13 |
|
||||
14 14 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
15 |-_ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
15 |+_ = f"{ {x % 2 == 0 for x in nums} }"
|
||||
16 16 | _ = f"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
17 17 |
|
||||
18 18 | # False negatives.
|
||||
|
||||
C417.py:17:8: C417 [*] Unnecessary `map` usage (rewrite using a `dict` comprehension)
|
||||
C417.py:16:8: C417 [*] Unnecessary `map` usage (rewrite using a `dict` comprehension)
|
||||
|
|
||||
15 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
16 | _ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
17 | _ = f"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
14 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
15 | _ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
16 | _ = f"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
18 |
|
||||
19 | # False negatives.
|
||||
17 |
|
||||
18 | # False negatives.
|
||||
|
|
||||
= help: Replace `map` with a `dict` comprehension
|
||||
|
||||
ℹ Suggested fix
|
||||
14 14 |
|
||||
15 15 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
16 16 | _ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
17 |-_ = f"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
17 |+_ = f"{ {v: v**2 for v in nums} }"
|
||||
18 18 |
|
||||
19 19 | # False negatives.
|
||||
20 20 | map(lambda x=2, y=1: x + y, nums, nums)
|
||||
13 13 |
|
||||
14 14 | # When inside f-string, then the fix should be surrounded by whitespace
|
||||
15 15 | _ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
16 |-_ = f"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
16 |+_ = f"{ {v: v**2 for v in nums} }"
|
||||
17 17 |
|
||||
18 18 | # False negatives.
|
||||
19 19 | map(lambda x=2, y=1: x + y, nums, nums)
|
||||
|
||||
C417.py:35:1: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
C417.py:34:1: C417 [*] Unnecessary `map` usage (rewrite using a generator expression)
|
||||
|
|
||||
34 | # Error: the `x` is overridden by the inner lambda.
|
||||
35 | map(lambda x: lambda x: x, range(4))
|
||||
33 | # Error: the `x` is overridden by the inner lambda.
|
||||
34 | map(lambda x: lambda x: x, range(4))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417
|
||||
36 |
|
||||
37 | # Ok because of the default parameters, and variadic arguments.
|
||||
35 |
|
||||
36 | # Ok because of the default parameters, and variadic arguments.
|
||||
|
|
||||
= help: Replace `map` with a generator expression
|
||||
|
||||
ℹ Suggested fix
|
||||
32 32 | map(lambda x: lambda: x, range(4))
|
||||
33 33 |
|
||||
34 34 | # Error: the `x` is overridden by the inner lambda.
|
||||
35 |-map(lambda x: lambda x: x, range(4))
|
||||
35 |+(lambda x: x for x in range(4))
|
||||
36 36 |
|
||||
37 37 | # Ok because of the default parameters, and variadic arguments.
|
||||
38 38 | map(lambda x=1: x, nums)
|
||||
31 31 | map(lambda x: lambda: x, range(4))
|
||||
32 32 |
|
||||
33 33 | # Error: the `x` is overridden by the inner lambda.
|
||||
34 |-map(lambda x: lambda x: x, range(4))
|
||||
34 |+(lambda x: x for x in range(4))
|
||||
35 35 |
|
||||
36 36 | # Ok because of the default parameters, and variadic arguments.
|
||||
37 37 | map(lambda x=1: x, nums)
|
||||
|
||||
|
||||
|
||||
@@ -149,17 +149,6 @@ import os
|
||||
# Content Content Content Content Content Content Content Content Content Content
|
||||
|
||||
# Copyright 2023
|
||||
"#
|
||||
.trim(),
|
||||
&settings::Settings::for_rules(vec![Rule::MissingCopyrightNotice]),
|
||||
);
|
||||
assert_messages!(diagnostics);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_boundary() {
|
||||
let diagnostics = test_snippet(
|
||||
r#"কককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককক
|
||||
"#
|
||||
.trim(),
|
||||
&settings::Settings::for_rules(vec![Rule::MissingCopyrightNotice]),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::settings::Settings;
|
||||
|
||||
@@ -32,7 +33,11 @@ pub(crate) fn missing_copyright_notice(
|
||||
}
|
||||
|
||||
// Only search the first 1024 bytes in the file.
|
||||
let contents = locator.up_to(locator.floor_char_boundary(TextSize::new(1024)));
|
||||
let contents = if locator.len() < 1024 {
|
||||
locator.contents()
|
||||
} else {
|
||||
locator.up_to(TextSize::from(1024))
|
||||
};
|
||||
|
||||
// Locate the copyright notice.
|
||||
if let Some(match_) = settings.flake8_copyright.notice_rgx.find(contents) {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_copyright/mod.rs
|
||||
---
|
||||
<filename>:1:1: CPY001 Missing copyright notice at top of file
|
||||
|
|
||||
1 | কককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককককক
|
||||
| CPY001
|
||||
|
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user