Compare commits

..

1 Commits

Author SHA1 Message Date
Zanie
56fbde05b6 Use nextest to run tests in CI 2023-08-29 09:45:41 -05:00
378 changed files with 14621 additions and 20422 deletions

View File

@@ -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)"

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -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))

View File

@@ -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.
"""

View File

@@ -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]

View File

@@ -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"""

View File

@@ -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)

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View 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

View File

@@ -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")

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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))

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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': '',

View File

@@ -165,9 +165,3 @@ def f():
x = 1
y = 2
def f():
(x) = foo()
((x)) = foo()
(x) = (y.z) = foo()

View File

@@ -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:
...

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -1,4 +1,4 @@
# OK
# These should NOT change
def f():
for x in z:
yield

View File

@@ -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)

View File

@@ -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[:]

View File

@@ -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")

View File

@@ -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], [])

View File

@@ -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 {

View File

@@ -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(),
}
]
);
}

View File

@@ -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> {

View File

@@ -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(),

View File

@@ -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(),
));

View File

@@ -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);

View File

@@ -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);
}
}
_ => {}
}

View File

@@ -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.

View File

@@ -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(),
)));

View File

@@ -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,
})
}

View File

@@ -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());

View File

@@ -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 {

View File

@@ -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(&notebook_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(&notebook_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(&notebook_path("invalid_extension.ipynb")),
Err(NotebookError::InvalidJson(_))
));
assert!(matches!(
Notebook::from_path(&notebook_path("not_json.ipynb")),
Err(NotebookError::InvalidJson(_))
));
assert!(matches!(
Notebook::from_path(&notebook_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(&notebook_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(())
}
}

View File

@@ -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 {

View File

@@ -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
|

View File

@@ -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
|

View File

@@ -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
|

View File

@@ -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;

View File

@@ -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(&notebook_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(&notebook_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(())
}
}

View File

@@ -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,

View File

@@ -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::{

View File

@@ -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;

View File

@@ -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};

View File

@@ -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,

View File

@@ -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.")
/// ```

View File

@@ -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(),
)));
}

View File

@@ -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 {

View File

@@ -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),

View File

@@ -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(),
)));
}

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)?;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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]),

View File

@@ -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) {

View File

@@ -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