Compare commits
34 Commits
v0.2.1
...
charlie/pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ae4fb3b9f | ||
|
|
c67d68271d | ||
|
|
56b148bb43 | ||
|
|
0a5a4f6d92 | ||
|
|
00ef01d035 | ||
|
|
52ebfc9718 | ||
|
|
12a91f4e90 | ||
|
|
b4f2882b72 | ||
|
|
49fe1b85f2 | ||
|
|
bd8123c0d8 | ||
|
|
49c5e715f9 | ||
|
|
fe7d965334 | ||
|
|
9027169125 | ||
|
|
688177ff6a | ||
|
|
eb2784c495 | ||
|
|
6fffde72e7 | ||
|
|
ad313b9089 | ||
|
|
f76a3e8502 | ||
|
|
ed07fa08bd | ||
|
|
45937426c7 | ||
|
|
533dcfb114 | ||
|
|
bc023f47a1 | ||
|
|
aa38307415 | ||
|
|
e9ddd4819a | ||
|
|
fdb5eefb33 | ||
|
|
daae28efc7 | ||
|
|
75553ab1c0 | ||
|
|
c34908f5ad | ||
|
|
a662c2447c | ||
|
|
df7fb95cbc | ||
|
|
83195a6030 | ||
|
|
d31d09d7cd | ||
|
|
0f436b71f3 | ||
|
|
cd5bcd815d |
5
.github/workflows/ci.yaml
vendored
5
.github/workflows/ci.yaml
vendored
@@ -117,10 +117,7 @@ jobs:
|
||||
tool: cargo-insta
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run tests"
|
||||
run: cargo insta test --all --exclude ruff_dev --all-features --unreferenced reject
|
||||
- name: "Run dev tests"
|
||||
# e.g. generating the schema — these should not run with all features enabled
|
||||
run: cargo insta test -p ruff_dev --unreferenced reject
|
||||
run: cargo insta test --all --all-features --unreferenced reject
|
||||
# Check for broken links in the documentation.
|
||||
- run: cargo doc --all --no-deps
|
||||
env:
|
||||
|
||||
@@ -7,7 +7,7 @@ within a source file).
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`refurn`\] Implement `missing-f-string-syntax` (`RUF027`) ([#9728](https://github.com/astral-sh/ruff/pull/9728))
|
||||
- \[`refurb`\] Implement `missing-f-string-syntax` (`RUF027`) ([#9728](https://github.com/astral-sh/ruff/pull/9728))
|
||||
- Format module-level docstrings ([#9725](https://github.com/astral-sh/ruff/pull/9725))
|
||||
|
||||
### Formatter
|
||||
|
||||
@@ -231,7 +231,7 @@ Once you've completed the code for the rule itself, you can define tests with th
|
||||
For example, if you're adding a new rule named `E402`, you would run:
|
||||
|
||||
```shell
|
||||
cargo run -p ruff -- check crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402
|
||||
cargo run -p ruff -- check crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py --no-cache --preview --select E402
|
||||
```
|
||||
|
||||
**Note:** Only a subset of rules are enabled by default. When testing a new rule, ensure that
|
||||
|
||||
47
Cargo.lock
generated
47
Cargo.lock
generated
@@ -217,12 +217,12 @@ checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.6.2"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
|
||||
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.3.9",
|
||||
"regex-automata 0.4.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -1569,18 +1569,6 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
|
||||
|
||||
[[package]]
|
||||
name = "pep440_rs"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "887f66cc62717ea72caac4f1eb4e6f392224da3ffff3f40ec13ab427802746d6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"serde",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pep440_rs"
|
||||
version = "0.4.0"
|
||||
@@ -1595,12 +1583,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pep508_rs"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0713d7bb861ca2b7d4c50a38e1f31a4b63a2e2df35ef1e5855cc29e108453e2"
|
||||
checksum = "910c513bea0f4f833122321c0f20e8c704e01de98692f6989c2ec21f43d88b1e"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pep440_rs 0.3.12",
|
||||
"pep440_rs",
|
||||
"regex",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -1780,12 +1768,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyproject-toml"
|
||||
version = "0.8.2"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef61ae096a2f8c8b49eca360679dbc25f57c99145f6634b6bc18fedb1f9c6c30"
|
||||
checksum = "95c3dd745f99aa3c554b7bb00859f7d18c2f1d6afd749ccc86d60b61e702abd9"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs 0.4.0",
|
||||
"pep440_rs",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"toml",
|
||||
@@ -1933,12 +1921,6 @@ dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
@@ -2189,7 +2171,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
"pathdiff",
|
||||
"pep440_rs 0.4.0",
|
||||
"pep440_rs",
|
||||
"pretty_assertions",
|
||||
"pyproject-toml",
|
||||
"quick-junit",
|
||||
@@ -2277,7 +2259,6 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2355,16 +2336,22 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.1",
|
||||
"bstr",
|
||||
"codspeed-criterion-compat",
|
||||
"criterion",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools 0.12.1",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"memchr",
|
||||
"mimalloc",
|
||||
"once_cell",
|
||||
"ruff_python_ast",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"static_assertions",
|
||||
"tikv-jemallocator",
|
||||
"tiny-keccak",
|
||||
"unicode-ident",
|
||||
"unicode_names2",
|
||||
@@ -2494,7 +2481,7 @@ dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
"pep440_rs 0.4.0",
|
||||
"pep440_rs",
|
||||
"regex",
|
||||
"ruff_cache",
|
||||
"ruff_formatter",
|
||||
|
||||
@@ -19,6 +19,7 @@ argfile = { version = "0.1.6" }
|
||||
assert_cmd = { version = "2.0.13" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
bstr = { version = "1.9.0" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.33", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.18", features = ["derive"] }
|
||||
@@ -65,7 +66,7 @@ pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.4.0", features = ["serde"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
proc-macro2 = { version = "1.0.78" }
|
||||
pyproject-toml = { version = "0.8.2" }
|
||||
pyproject-toml = { version = "0.9.0" }
|
||||
quick-junit = { version = "0.3.5" }
|
||||
quote = { version = "1.0.23" }
|
||||
rand = { version = "0.8.5" }
|
||||
|
||||
@@ -31,9 +31,9 @@ pub(crate) fn show_settings(
|
||||
|
||||
let settings = resolver.resolve(&path);
|
||||
|
||||
writeln!(writer, "Resolved settings for: {path:?}")?;
|
||||
writeln!(writer, "Resolved settings for: \"{}\"", path.display())?;
|
||||
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
||||
writeln!(writer, "Settings path: {settings_path:?}")?;
|
||||
writeln!(writer, "Settings path: \"{}\"", settings_path.display())?;
|
||||
}
|
||||
write!(writer, "{settings}")?;
|
||||
|
||||
|
||||
@@ -4,25 +4,29 @@ use std::process::Command;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const TEST_FILTERS: &[(&str, &str)] = &[
|
||||
("\"[^\\*\"]*/pyproject.toml", "\"[BASEPATH]/pyproject.toml"),
|
||||
("\".*/crates", "\"[BASEPATH]/crates"),
|
||||
("\".*/\\.ruff_cache", "\"[BASEPATH]/.ruff_cache"),
|
||||
("\".*/ruff\"", "\"[BASEPATH]\""),
|
||||
];
|
||||
#[cfg(target_os = "windows")]
|
||||
const TEST_FILTERS: &[(&str, &str)] = &[
|
||||
(r#""[^\*"]*\\pyproject.toml"#, "\"[BASEPATH]/pyproject.toml"),
|
||||
(r#"".*\\crates"#, "\"[BASEPATH]/crates"),
|
||||
(r#"".*\\\.ruff_cache"#, "\"[BASEPATH]/.ruff_cache"),
|
||||
(r#"".*\\ruff""#, "\"[BASEPATH]\""),
|
||||
(r#"\\+(\w\w|\s|")"#, "/$1"),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn display_default_settings() {
|
||||
insta::with_settings!({ filters => TEST_FILTERS.to_vec() }, {
|
||||
// Navigate from the crate directory to the workspace root.
|
||||
let base_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap();
|
||||
let base_path = base_path.to_string_lossy();
|
||||
|
||||
// Escape the backslashes for the regex.
|
||||
let base_path = regex::escape(&base_path);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let test_filters = &[(base_path.as_ref(), "[BASEPATH]")];
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let test_filters = &[
|
||||
(base_path.as_ref(), "[BASEPATH]"),
|
||||
(r#"\\+(\w\w|\s|\.|")"#, "/$1"),
|
||||
];
|
||||
|
||||
insta::with_settings!({ filters => test_filters.to_vec() }, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--show-settings", "unformatted.py"]).current_dir(Path::new("./resources/test/fixtures")));
|
||||
});
|
||||
|
||||
@@ -205,7 +205,9 @@ linter.external = []
|
||||
linter.ignore_init_module_imports = false
|
||||
linter.logger_objects = []
|
||||
linter.namespace_packages = []
|
||||
linter.src = ["[BASEPATH]"]
|
||||
linter.src = [
|
||||
"[BASEPATH]",
|
||||
]
|
||||
linter.tab_size = 4
|
||||
linter.line_length = 88
|
||||
linter.task_tags = [
|
||||
|
||||
@@ -21,8 +21,7 @@ impl<'a> LineSuffixes<'a> {
|
||||
/// Takes all the pending line suffixes.
|
||||
pub(super) fn take_pending<'l>(
|
||||
&'l mut self,
|
||||
) -> impl Iterator<Item = LineSuffixEntry<'a>> + DoubleEndedIterator + 'l + ExactSizeIterator
|
||||
{
|
||||
) -> impl DoubleEndedIterator<Item = LineSuffixEntry<'a>> + 'l + ExactSizeIterator {
|
||||
self.suffixes.drain(..)
|
||||
}
|
||||
|
||||
|
||||
22
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S308.py
vendored
Normal file
22
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S308.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
def some_func():
|
||||
return mark_safe('<script>alert("evil!")</script>')
|
||||
|
||||
|
||||
@mark_safe
|
||||
def some_func():
|
||||
return '<script>alert("evil!")</script>'
|
||||
|
||||
|
||||
from django.utils.html import mark_safe
|
||||
|
||||
|
||||
def some_func():
|
||||
return mark_safe('<script>alert("evil!")</script>')
|
||||
|
||||
|
||||
@mark_safe
|
||||
def some_func():
|
||||
return '<script>alert("evil!")</script>'
|
||||
@@ -1,18 +1,27 @@
|
||||
import trio
|
||||
|
||||
|
||||
async def foo():
|
||||
async def func():
|
||||
with trio.fail_after():
|
||||
...
|
||||
|
||||
async def foo():
|
||||
|
||||
async def func():
|
||||
with trio.fail_at():
|
||||
await ...
|
||||
|
||||
async def foo():
|
||||
|
||||
async def func():
|
||||
with trio.move_on_after():
|
||||
...
|
||||
|
||||
async def foo():
|
||||
|
||||
async def func():
|
||||
with trio.move_at():
|
||||
await ...
|
||||
|
||||
|
||||
async def func():
|
||||
with trio.move_at():
|
||||
async with trio.open_nursery() as nursery:
|
||||
...
|
||||
|
||||
@@ -19,8 +19,11 @@ numpy.random.seed()
|
||||
numpy.random.get_state()
|
||||
numpy.random.set_state()
|
||||
numpy.random.rand()
|
||||
numpy.random.ranf()
|
||||
numpy.random.sample()
|
||||
numpy.random.randn()
|
||||
numpy.random.randint()
|
||||
numpy.random.random()
|
||||
numpy.random.random_integers()
|
||||
numpy.random.random_sample()
|
||||
numpy.random.choice()
|
||||
@@ -35,7 +38,6 @@ numpy.random.exponential()
|
||||
numpy.random.f()
|
||||
numpy.random.gamma()
|
||||
numpy.random.geometric()
|
||||
numpy.random.get_state()
|
||||
numpy.random.gumbel()
|
||||
numpy.random.hypergeometric()
|
||||
numpy.random.laplace()
|
||||
|
||||
848
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py
vendored
Normal file
848
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py
vendored
Normal file
@@ -0,0 +1,848 @@
|
||||
"""Fixtures for the errors E301, E302, E303, E304, E305 and E306.
|
||||
Since these errors are about new lines, each test starts with either "No error" or "# E30X".
|
||||
Each test's end is signaled by a "# end" line.
|
||||
There should be no E30X error outside of a test's bound.
|
||||
"""
|
||||
|
||||
|
||||
# No error
|
||||
class Class:
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
class Class:
|
||||
"""Docstring"""
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
def func():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
# comment
|
||||
class Class:
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
# comment
|
||||
def func():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
def foo():
|
||||
pass
|
||||
|
||||
|
||||
def bar():
|
||||
pass
|
||||
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
|
||||
class Bar(object):
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
class Class(object):
|
||||
|
||||
def func1():
|
||||
pass
|
||||
|
||||
def func2():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
class Class(object):
|
||||
|
||||
def func1():
|
||||
pass
|
||||
|
||||
# comment
|
||||
def func2():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
class Class:
|
||||
|
||||
def func1():
|
||||
pass
|
||||
|
||||
# comment
|
||||
def func2():
|
||||
pass
|
||||
|
||||
# This is a
|
||||
# ... multi-line comment
|
||||
|
||||
def func3():
|
||||
pass
|
||||
|
||||
|
||||
# This is a
|
||||
# ... multi-line comment
|
||||
|
||||
@decorator
|
||||
class Class:
|
||||
|
||||
def func1():
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
def func2():
|
||||
pass
|
||||
|
||||
@property
|
||||
def func3():
|
||||
pass
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
try:
|
||||
from nonexistent import Bar
|
||||
except ImportError:
|
||||
class Bar(object):
|
||||
"""This is a Bar replacement"""
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
def with_feature(f):
|
||||
"""Some decorator"""
|
||||
wrapper = f
|
||||
if has_this_feature(f):
|
||||
def wrapper(*args):
|
||||
call_feature(args[0])
|
||||
return f(*args)
|
||||
return wrapper
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
try:
|
||||
next
|
||||
except NameError:
|
||||
def next(iterator, default):
|
||||
for item in iterator:
|
||||
return item
|
||||
return default
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
def fn():
|
||||
pass
|
||||
|
||||
|
||||
class Foo():
|
||||
"""Class Foo"""
|
||||
|
||||
def fn():
|
||||
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# No error
|
||||
# comment
|
||||
def c():
|
||||
pass
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
def d():
|
||||
pass
|
||||
|
||||
# This is a
|
||||
# ... multi-line comment
|
||||
|
||||
# And this one is
|
||||
# ... a second paragraph
|
||||
# ... which spans on 3 lines
|
||||
|
||||
|
||||
# Function `e` is below
|
||||
# NOTE: Hey this is a testcase
|
||||
|
||||
def e():
|
||||
pass
|
||||
|
||||
|
||||
def fn():
|
||||
print()
|
||||
|
||||
# comment
|
||||
|
||||
print()
|
||||
|
||||
print()
|
||||
|
||||
# Comment 1
|
||||
|
||||
# Comment 2
|
||||
|
||||
|
||||
# Comment 3
|
||||
|
||||
def fn2():
|
||||
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
if __name__ == '__main__':
|
||||
foo()
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
defaults = {}
|
||||
defaults.update({})
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
def foo(x):
|
||||
classification = x
|
||||
definitely = not classification
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
def bar(): pass
|
||||
def baz(): pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
def foo():
|
||||
def bar(): pass
|
||||
def baz(): pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
from typing import overload
|
||||
from typing import Union
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
@overload
|
||||
def f(x: int) -> int: ...
|
||||
@overload
|
||||
def f(x: str) -> str: ...
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
def f(x: Union[int, str]) -> Union[int, str]:
|
||||
return x
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class C(Protocol):
|
||||
@property
|
||||
def f(self) -> int: ...
|
||||
@property
|
||||
def g(self) -> str: ...
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
def f(
|
||||
a,
|
||||
):
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
if True:
|
||||
class Class:
|
||||
"""Docstring"""
|
||||
|
||||
def function(self):
|
||||
...
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
if True:
|
||||
def function(self):
|
||||
...
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
@decorator
|
||||
# comment
|
||||
@decorator
|
||||
def function():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
class Class:
|
||||
def method(self):
|
||||
if True:
|
||||
def function():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
@decorator
|
||||
async def function(data: None) -> None:
|
||||
...
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
class Class:
|
||||
def method():
|
||||
"""docstring"""
|
||||
# comment
|
||||
def function():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
try:
|
||||
if True:
|
||||
# comment
|
||||
class Class:
|
||||
pass
|
||||
|
||||
except:
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
def f():
|
||||
def f():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
class MyClass:
|
||||
# comment
|
||||
def method(self) -> None:
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
def function1():
|
||||
# Comment
|
||||
def function2():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
async def function1():
|
||||
await function2()
|
||||
async with function3():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
if (
|
||||
cond1
|
||||
|
||||
|
||||
|
||||
|
||||
and cond2
|
||||
):
|
||||
pass
|
||||
#end
|
||||
|
||||
|
||||
# no error
|
||||
async def function1():
|
||||
await function2()
|
||||
async with function3():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
async def function1():
|
||||
await function2()
|
||||
async with function3():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
async def function1():
|
||||
await function2()
|
||||
async with function3():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
class Test:
|
||||
async
|
||||
|
||||
def a(self): pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
class Test:
|
||||
def a():
|
||||
pass
|
||||
# wrongly indented comment
|
||||
|
||||
def b():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
def test():
|
||||
pass
|
||||
|
||||
# Wrongly indented comment
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E301
|
||||
class Class(object):
|
||||
|
||||
def func1():
|
||||
pass
|
||||
def func2():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E301
|
||||
class Class:
|
||||
|
||||
def fn1():
|
||||
pass
|
||||
# comment
|
||||
def fn2():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
"""Main module."""
|
||||
def fn():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
import sys
|
||||
def get_sys_path():
|
||||
return sys.path
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
def a():
|
||||
pass
|
||||
|
||||
def b():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
def a():
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
def b():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
def a():
|
||||
pass
|
||||
|
||||
async def b():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
async def x():
|
||||
pass
|
||||
|
||||
async def x(y: int = 1):
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
def bar():
|
||||
pass
|
||||
def baz(): pass
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
def bar(): pass
|
||||
def baz():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
def f():
|
||||
pass
|
||||
|
||||
# comment
|
||||
@decorator
|
||||
def g():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
class Test:
|
||||
|
||||
pass
|
||||
|
||||
def method1():
|
||||
return 1
|
||||
|
||||
|
||||
def method2():
|
||||
return 22
|
||||
# end
|
||||
|
||||
|
||||
# E303
|
||||
def fn():
|
||||
_ = None
|
||||
|
||||
|
||||
# arbitrary comment
|
||||
|
||||
def inner(): # E306 not expected (pycodestyle detects E306)
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E303
|
||||
def fn():
|
||||
_ = None
|
||||
|
||||
|
||||
# arbitrary comment
|
||||
def inner(): # E306 not expected (pycodestyle detects E306)
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E303
|
||||
print()
|
||||
|
||||
|
||||
|
||||
print()
|
||||
# end
|
||||
|
||||
|
||||
# E303:5:1
|
||||
print()
|
||||
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
print()
|
||||
# end
|
||||
|
||||
|
||||
# E303:5:5 E303:8:5
|
||||
def a():
|
||||
print()
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
# another comment
|
||||
|
||||
print()
|
||||
# end
|
||||
|
||||
|
||||
# E303
|
||||
#!python
|
||||
|
||||
|
||||
|
||||
"""This class docstring comes on line 5.
|
||||
It gives error E303: too many blank lines (3)
|
||||
"""
|
||||
# end
|
||||
|
||||
|
||||
# E303
|
||||
class Class:
|
||||
def a(self):
|
||||
pass
|
||||
|
||||
|
||||
def b(self):
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E303
|
||||
if True:
|
||||
a = 1
|
||||
|
||||
|
||||
a = 2
|
||||
# end
|
||||
|
||||
|
||||
# E303
|
||||
class Test:
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
# another comment
|
||||
|
||||
def test(self): pass
|
||||
# end
|
||||
|
||||
|
||||
# E303
|
||||
class Test:
|
||||
def a(self):
|
||||
pass
|
||||
|
||||
# wrongly indented comment
|
||||
|
||||
|
||||
def b(self):
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E303
|
||||
def fn():
|
||||
pass
|
||||
|
||||
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E304
|
||||
@decorator
|
||||
|
||||
def function():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E304
|
||||
@decorator
|
||||
|
||||
# comment E304 not expected
|
||||
def function():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E304
|
||||
@decorator
|
||||
|
||||
# comment E304 not expected
|
||||
|
||||
|
||||
# second comment E304 not expected
|
||||
def function():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E305:7:1
|
||||
def fn():
|
||||
print()
|
||||
|
||||
# comment
|
||||
|
||||
# another comment
|
||||
fn()
|
||||
# end
|
||||
|
||||
|
||||
# E305
|
||||
class Class():
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
# another comment
|
||||
a = 1
|
||||
# end
|
||||
|
||||
|
||||
# E305:8:1
|
||||
def fn():
|
||||
print()
|
||||
|
||||
# comment
|
||||
|
||||
# another comment
|
||||
|
||||
try:
|
||||
fn()
|
||||
except Exception:
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E305:5:1
|
||||
def a():
|
||||
print()
|
||||
|
||||
# Two spaces before comments, too.
|
||||
if a():
|
||||
a()
|
||||
# end
|
||||
|
||||
|
||||
#: E305:8:1
|
||||
# Example from https://github.com/PyCQA/pycodestyle/issues/400
|
||||
import stuff
|
||||
|
||||
|
||||
def main():
|
||||
blah, blah
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
# end
|
||||
|
||||
|
||||
# E306:3:5
|
||||
def a():
|
||||
x = 1
|
||||
def b():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
#: E306:3:5
|
||||
async def a():
|
||||
x = 1
|
||||
def b():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
#: E306:3:5 E306:5:9
|
||||
def a():
|
||||
x = 2
|
||||
def b():
|
||||
x = 1
|
||||
def c():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E306:3:5 E306:6:5
|
||||
def a():
|
||||
x = 1
|
||||
class C:
|
||||
pass
|
||||
x = 2
|
||||
def b():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E306
|
||||
def foo():
|
||||
def bar():
|
||||
pass
|
||||
def baz(): pass
|
||||
# end
|
||||
|
||||
|
||||
# E306:3:5
|
||||
def foo():
|
||||
def bar(): pass
|
||||
def baz():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E306
|
||||
def a():
|
||||
x = 2
|
||||
@decorator
|
||||
def b():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E306
|
||||
def a():
|
||||
x = 2
|
||||
@decorator
|
||||
async def b():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E306
|
||||
def a():
|
||||
x = 2
|
||||
async def b():
|
||||
pass
|
||||
# end
|
||||
@@ -1,2 +1,15 @@
|
||||
'''trailing whitespace
|
||||
inside a multiline string'''
|
||||
|
||||
f'''trailing whitespace
|
||||
inside a multiline f-string'''
|
||||
|
||||
# Trailing whitespace after `{`
|
||||
f'abc {
|
||||
1 + 2
|
||||
}'
|
||||
|
||||
# Trailing whitespace after `2`
|
||||
f'abc {
|
||||
1 + 2
|
||||
}'
|
||||
|
||||
@@ -562,3 +562,46 @@ def titlecase_sub_section_header():
|
||||
|
||||
Returns:
|
||||
"""
|
||||
|
||||
|
||||
def test_method_should_be_correctly_capitalized(parameters: list[str], other_parameters: dict[str, str]): # noqa: D213
|
||||
"""Test parameters and attributes sections are capitalized correctly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parameters:
|
||||
A list of string parameters
|
||||
other_parameters:
|
||||
A dictionary of string attributes
|
||||
|
||||
Other Parameters
|
||||
----------
|
||||
other_parameters:
|
||||
A dictionary of string attributes
|
||||
parameters:
|
||||
A list of string parameters
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def test_lowercase_sub_section_header_should_be_valid(parameters: list[str], value: int): # noqa: D213
|
||||
"""Test that lower case subsection header is valid even if it has the same name as section kind.
|
||||
|
||||
Parameters:
|
||||
----------
|
||||
parameters:
|
||||
A list of string parameters
|
||||
value:
|
||||
Some value
|
||||
"""
|
||||
|
||||
|
||||
def test_lowercase_sub_section_header_different_kind(returns: int):
|
||||
"""Test that lower case subsection header is valid even if it is of a different kind.
|
||||
|
||||
Parameters
|
||||
-‐-----------------
|
||||
returns:
|
||||
some value
|
||||
|
||||
"""
|
||||
|
||||
@@ -46,3 +46,8 @@ x: typing.TypeAlias = list[T]
|
||||
# OK
|
||||
x: TypeAlias
|
||||
x: int = 1
|
||||
|
||||
# Ensure that "T" appears only once in the type parameters for the modernized
|
||||
# type alias.
|
||||
T = typing.TypeVar["T"]
|
||||
Decorator: TypeAlias = typing.Callable[[T], T]
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
val = 2
|
||||
|
||||
def simple_cases():
|
||||
a = 4
|
||||
b = "{a}" # RUF027
|
||||
c = "{a} {b} f'{val}' " # RUF027
|
||||
|
||||
def escaped_string():
|
||||
a = 4
|
||||
b = "escaped string: {{ brackets surround me }}" # RUF027
|
||||
|
||||
def raw_string():
|
||||
a = 4
|
||||
b = r"raw string with formatting: {a}" # RUF027
|
||||
c = r"raw string with \backslashes\ and \"escaped quotes\": {a}" # RUF027
|
||||
|
||||
def print_name(name: str):
|
||||
a = 4
|
||||
print("Hello, {name}!") # RUF027
|
||||
print("The test value we're using today is {a}") # RUF027
|
||||
|
||||
def do_nothing(a):
|
||||
return a
|
||||
|
||||
def nested_funcs():
|
||||
a = 4
|
||||
print(do_nothing(do_nothing("{a}"))) # RUF027
|
||||
|
||||
def tripled_quoted():
|
||||
a = 4
|
||||
c = a
|
||||
single_line = """ {a} """ # RUF027
|
||||
# RUF027
|
||||
multi_line = a = """b { # comment
|
||||
c} d
|
||||
"""
|
||||
|
||||
def single_quoted_multi_line():
|
||||
a = 4
|
||||
# RUF027
|
||||
b = " {\
|
||||
a} \
|
||||
"
|
||||
|
||||
def implicit_concat():
|
||||
a = 4
|
||||
b = "{a}" "+" "{b}" r" \\ " # RUF027 for the first part only
|
||||
print(f"{a}" "{a}" f"{b}") # RUF027
|
||||
|
||||
def escaped_chars():
|
||||
a = 4
|
||||
b = "\"not escaped:\" \'{a}\' \"escaped:\": \'{{c}}\'" # RUF027
|
||||
|
||||
def alternative_formatter(src, **kwargs):
|
||||
src.format(**kwargs)
|
||||
|
||||
def format2(src, *args):
|
||||
pass
|
||||
|
||||
# These should not cause an RUF027 message
|
||||
def negative_cases():
|
||||
a = 4
|
||||
positive = False
|
||||
"""{a}"""
|
||||
"don't format: {a}"
|
||||
c = """ {b} """
|
||||
d = "bad variable: {invalid}"
|
||||
e = "incorrect syntax: {}"
|
||||
json = "{ positive: false }"
|
||||
json2 = "{ 'positive': false }"
|
||||
json3 = "{ 'positive': 'false' }"
|
||||
alternative_formatter("{a}", a = 5)
|
||||
formatted = "{a}".fmt(a = 7)
|
||||
print(do_nothing("{a}".format(a=3)))
|
||||
print(do_nothing(alternative_formatter("{a}", a = 5)))
|
||||
print(format(do_nothing("{a}"), a = 5))
|
||||
print("{a}".to_upper())
|
||||
print(do_nothing("{a}").format(a = "Test"))
|
||||
print(do_nothing("{a}").format2(a))
|
||||
|
||||
a = 4
|
||||
|
||||
"always ignore this: {a}"
|
||||
|
||||
print("but don't ignore this: {val}") # RUF027
|
||||
70
crates/ruff_linter/resources/test/fixtures/ruff/RUF027_0.py
vendored
Normal file
70
crates/ruff_linter/resources/test/fixtures/ruff/RUF027_0.py
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
val = 2
|
||||
|
||||
"always ignore this: {val}"
|
||||
|
||||
print("but don't ignore this: {val}") # RUF027
|
||||
|
||||
|
||||
def simple_cases():
|
||||
a = 4
|
||||
b = "{a}" # RUF027
|
||||
c = "{a} {b} f'{val}' " # RUF027
|
||||
|
||||
|
||||
def escaped_string():
|
||||
a = 4
|
||||
b = "escaped string: {{ brackets surround me }}" # RUF027
|
||||
|
||||
|
||||
def raw_string():
|
||||
a = 4
|
||||
b = r"raw string with formatting: {a}" # RUF027
|
||||
c = r"raw string with \backslashes\ and \"escaped quotes\": {a}" # RUF027
|
||||
|
||||
|
||||
def print_name(name: str):
|
||||
a = 4
|
||||
print("Hello, {name}!") # RUF027
|
||||
print("The test value we're using today is {a}") # RUF027
|
||||
|
||||
|
||||
def nested_funcs():
|
||||
a = 4
|
||||
print(do_nothing(do_nothing("{a}"))) # RUF027
|
||||
|
||||
|
||||
def tripled_quoted():
|
||||
a = 4
|
||||
c = a
|
||||
single_line = """ {a} """ # RUF027
|
||||
# RUF027
|
||||
multi_line = a = """b { # comment
|
||||
c} d
|
||||
"""
|
||||
|
||||
|
||||
def single_quoted_multi_line():
|
||||
a = 4
|
||||
# RUF027
|
||||
b = " {\
|
||||
a} \
|
||||
"
|
||||
|
||||
|
||||
def implicit_concat():
|
||||
a = 4
|
||||
b = "{a}" "+" "{b}" r" \\ " # RUF027 for the first part only
|
||||
print(f"{a}" "{a}" f"{b}") # RUF027
|
||||
|
||||
|
||||
def escaped_chars():
|
||||
a = 4
|
||||
b = "\"not escaped:\" '{a}' \"escaped:\": '{{c}}'" # RUF027
|
||||
|
||||
|
||||
def method_calls():
|
||||
value = {}
|
||||
value.method = print_name
|
||||
first = "Wendy"
|
||||
last = "Appleseed"
|
||||
value.method("{first} {last}") # RUF027
|
||||
36
crates/ruff_linter/resources/test/fixtures/ruff/RUF027_1.py
vendored
Normal file
36
crates/ruff_linter/resources/test/fixtures/ruff/RUF027_1.py
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
def do_nothing(a):
|
||||
return a
|
||||
|
||||
|
||||
def alternative_formatter(src, **kwargs):
|
||||
src.format(**kwargs)
|
||||
|
||||
|
||||
def format2(src, *args):
|
||||
pass
|
||||
|
||||
|
||||
# These should not cause an RUF027 message
|
||||
def negative_cases():
|
||||
a = 4
|
||||
positive = False
|
||||
"""{a}"""
|
||||
"don't format: {a}"
|
||||
c = """ {b} """
|
||||
d = "bad variable: {invalid}"
|
||||
e = "incorrect syntax: {}"
|
||||
f = "uses a builtin: {max}"
|
||||
json = "{ positive: false }"
|
||||
json2 = "{ 'positive': false }"
|
||||
json3 = "{ 'positive': 'false' }"
|
||||
alternative_formatter("{a}", a=5)
|
||||
formatted = "{a}".fmt(a=7)
|
||||
print(do_nothing("{a}".format(a=3)))
|
||||
print(do_nothing(alternative_formatter("{a}", a=5)))
|
||||
print(format(do_nothing("{a}"), a=5))
|
||||
print("{a}".to_upper())
|
||||
print(do_nothing("{a}").format(a="Test"))
|
||||
print(do_nothing("{a}").format2(a))
|
||||
print(("{a}" "{c}").format(a=1, c=2))
|
||||
print("{a}".attribute.chaining.call(a=2))
|
||||
print("{a} {c}".format(a))
|
||||
@@ -247,6 +247,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::HardcodedPasswordDefault) {
|
||||
flake8_bandit::rules::hardcoded_password_default(checker, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::SuspiciousMarkSafeUsage) {
|
||||
for decorator in decorator_list {
|
||||
flake8_bandit::rules::suspicious_function_decorator(checker, decorator);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::PropertyWithParameters) {
|
||||
pylint::rules::property_with_parameters(checker, stmt, decorator_list, parameters);
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ use std::path::Path;
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arguments, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext,
|
||||
Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
|
||||
self as ast, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext, Keyword,
|
||||
MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -989,12 +989,7 @@ where
|
||||
}
|
||||
Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range: _,
|
||||
}) => {
|
||||
self.visit_expr(func);
|
||||
@@ -1037,7 +1032,7 @@ where
|
||||
});
|
||||
match callable {
|
||||
Some(typing::Callable::Bool) => {
|
||||
let mut args = args.iter();
|
||||
let mut args = arguments.args.iter();
|
||||
if let Some(arg) = args.next() {
|
||||
self.visit_boolean_test(arg);
|
||||
}
|
||||
@@ -1046,7 +1041,7 @@ where
|
||||
}
|
||||
}
|
||||
Some(typing::Callable::Cast) => {
|
||||
let mut args = args.iter();
|
||||
let mut args = arguments.args.iter();
|
||||
if let Some(arg) = args.next() {
|
||||
self.visit_type_definition(arg);
|
||||
}
|
||||
@@ -1055,7 +1050,7 @@ where
|
||||
}
|
||||
}
|
||||
Some(typing::Callable::NewType) => {
|
||||
let mut args = args.iter();
|
||||
let mut args = arguments.args.iter();
|
||||
if let Some(arg) = args.next() {
|
||||
self.visit_non_type_definition(arg);
|
||||
}
|
||||
@@ -1064,21 +1059,21 @@ where
|
||||
}
|
||||
}
|
||||
Some(typing::Callable::TypeVar) => {
|
||||
let mut args = args.iter();
|
||||
let mut args = arguments.args.iter();
|
||||
if let Some(arg) = args.next() {
|
||||
self.visit_non_type_definition(arg);
|
||||
}
|
||||
for arg in args {
|
||||
self.visit_type_definition(arg);
|
||||
}
|
||||
for keyword in keywords {
|
||||
for keyword in arguments.keywords.iter() {
|
||||
let Keyword {
|
||||
arg,
|
||||
value,
|
||||
range: _,
|
||||
} = keyword;
|
||||
if let Some(id) = arg {
|
||||
if id == "bound" {
|
||||
if id.as_str() == "bound" {
|
||||
self.visit_type_definition(value);
|
||||
} else {
|
||||
self.visit_non_type_definition(value);
|
||||
@@ -1088,7 +1083,7 @@ where
|
||||
}
|
||||
Some(typing::Callable::NamedTuple) => {
|
||||
// Ex) NamedTuple("a", [("a", int)])
|
||||
let mut args = args.iter();
|
||||
let mut args = arguments.args.iter();
|
||||
if let Some(arg) = args.next() {
|
||||
self.visit_non_type_definition(arg);
|
||||
}
|
||||
@@ -1117,7 +1112,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
for keyword in keywords {
|
||||
for keyword in arguments.keywords.iter() {
|
||||
let Keyword { arg, value, .. } = keyword;
|
||||
match (arg.as_ref(), value) {
|
||||
// Ex) NamedTuple("a", **{"a": int})
|
||||
@@ -1144,7 +1139,7 @@ where
|
||||
}
|
||||
Some(typing::Callable::TypedDict) => {
|
||||
// Ex) TypedDict("a", {"a": int})
|
||||
let mut args = args.iter();
|
||||
let mut args = arguments.args.iter();
|
||||
if let Some(arg) = args.next() {
|
||||
self.visit_non_type_definition(arg);
|
||||
}
|
||||
@@ -1167,13 +1162,13 @@ where
|
||||
}
|
||||
|
||||
// Ex) TypedDict("a", a=int)
|
||||
for keyword in keywords {
|
||||
for keyword in arguments.keywords.iter() {
|
||||
let Keyword { value, .. } = keyword;
|
||||
self.visit_type_definition(value);
|
||||
}
|
||||
}
|
||||
Some(typing::Callable::MypyExtension) => {
|
||||
let mut args = args.iter();
|
||||
let mut args = arguments.args.iter();
|
||||
if let Some(arg) = args.next() {
|
||||
// Ex) DefaultNamedArg(bool | None, name="some_prop_name")
|
||||
self.visit_type_definition(arg);
|
||||
@@ -1181,13 +1176,13 @@ where
|
||||
for arg in args {
|
||||
self.visit_non_type_definition(arg);
|
||||
}
|
||||
for keyword in keywords {
|
||||
for keyword in arguments.keywords.iter() {
|
||||
let Keyword { value, .. } = keyword;
|
||||
self.visit_non_type_definition(value);
|
||||
}
|
||||
} else {
|
||||
// Ex) DefaultNamedArg(type="bool", name="some_prop_name")
|
||||
for keyword in keywords {
|
||||
for keyword in arguments.keywords.iter() {
|
||||
let Keyword {
|
||||
value,
|
||||
arg,
|
||||
@@ -1205,10 +1200,10 @@ where
|
||||
// If we're in a type definition, we need to treat the arguments to any
|
||||
// other callables as non-type definitions (i.e., we don't want to treat
|
||||
// any strings as deferred type definitions).
|
||||
for arg in args {
|
||||
for arg in arguments.args.iter() {
|
||||
self.visit_non_type_definition(arg);
|
||||
}
|
||||
for keyword in keywords {
|
||||
for keyword in arguments.keywords.iter() {
|
||||
let Keyword { value, .. } = keyword;
|
||||
self.visit_non_type_definition(value);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::line_width::IndentWidth;
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
@@ -15,11 +16,11 @@ use crate::rules::pycodestyle::rules::logical_lines::{
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
/// Return the amount of indentation, expanding tabs to the next multiple of the settings' tab size.
|
||||
fn expand_indent(line: &str, settings: &LinterSettings) -> usize {
|
||||
pub(crate) fn expand_indent(line: &str, indent_width: IndentWidth) -> usize {
|
||||
let line = line.trim_end_matches(['\n', '\r']);
|
||||
|
||||
let mut indent = 0;
|
||||
let tab_size = settings.tab_size.as_usize();
|
||||
let tab_size = indent_width.as_usize();
|
||||
for c in line.bytes() {
|
||||
match c {
|
||||
b'\t' => indent = (indent / tab_size) * tab_size + tab_size,
|
||||
@@ -85,7 +86,7 @@ pub(crate) fn check_logical_lines(
|
||||
TextRange::new(locator.line_start(first_token.start()), first_token.start())
|
||||
};
|
||||
|
||||
let indent_level = expand_indent(locator.slice(range), settings);
|
||||
let indent_level = expand_indent(locator.slice(range), settings.tab_size);
|
||||
|
||||
let indent_size = 4;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::path::Path;
|
||||
|
||||
use ruff_notebook::CellOffsets;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::Tok;
|
||||
|
||||
@@ -14,6 +15,7 @@ use ruff_source_file::Locator;
|
||||
use crate::directives::TodoComment;
|
||||
use crate::lex::docstring_detection::StateMachine;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pycodestyle::rules::BlankLinesChecker;
|
||||
use crate::rules::ruff::rules::Context;
|
||||
use crate::rules::{
|
||||
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
|
||||
@@ -21,17 +23,37 @@ use crate::rules::{
|
||||
};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn check_tokens(
|
||||
tokens: &[LexResult],
|
||||
path: &Path,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
settings: &LinterSettings,
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&CellOffsets>,
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||
|
||||
if settings.rules.any_enabled(&[
|
||||
Rule::BlankLineBetweenMethods,
|
||||
Rule::BlankLinesTopLevel,
|
||||
Rule::TooManyBlankLines,
|
||||
Rule::BlankLineAfterDecorator,
|
||||
Rule::BlankLinesAfterFunctionOrClass,
|
||||
Rule::BlankLinesBeforeNestedDefinition,
|
||||
]) {
|
||||
let mut blank_lines_checker = BlankLinesChecker::default();
|
||||
blank_lines_checker.check_lines(
|
||||
tokens,
|
||||
locator,
|
||||
stylist,
|
||||
settings.tab_size,
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::BlanketNOQA) {
|
||||
pygrep_hooks::rules::blanket_noqa(&mut diagnostics, indexer, locator);
|
||||
}
|
||||
@@ -95,7 +117,7 @@ pub(crate) fn check_tokens(
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::TabIndentation) {
|
||||
pycodestyle::rules::tab_indentation(&mut diagnostics, tokens, locator, indexer);
|
||||
pycodestyle::rules::tab_indentation(&mut diagnostics, locator, indexer);
|
||||
}
|
||||
|
||||
if settings.rules.any_enabled(&[
|
||||
|
||||
@@ -137,6 +137,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pycodestyle, "E274") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E275") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword),
|
||||
(Pycodestyle, "E301") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLineBetweenMethods),
|
||||
(Pycodestyle, "E302") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLinesTopLevel),
|
||||
(Pycodestyle, "E303") => (RuleGroup::Preview, rules::pycodestyle::rules::TooManyBlankLines),
|
||||
(Pycodestyle, "E304") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLineAfterDecorator),
|
||||
(Pycodestyle, "E305") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLinesAfterFunctionOrClass),
|
||||
(Pycodestyle, "E306") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLinesBeforeNestedDefinition),
|
||||
(Pycodestyle, "E401") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleImportsOnOneLine),
|
||||
(Pycodestyle, "E402") => (RuleGroup::Stable, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile),
|
||||
(Pycodestyle, "E501") => (RuleGroup::Stable, rules::pycodestyle::rules::LineTooLong),
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_python_ast::docstrings::{leading_space, leading_words};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use ruff_source_file::{Line, UniversalNewlineIterator, UniversalNewlines};
|
||||
use ruff_source_file::{Line, NewlineWithTrailingNewline, UniversalNewlines};
|
||||
|
||||
use crate::docstrings::styles::SectionStyle;
|
||||
use crate::docstrings::{Docstring, DocstringBody};
|
||||
@@ -130,6 +130,34 @@ impl SectionKind {
|
||||
Self::Yields => "Yields",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a section can contain subsections, as in:
|
||||
/// ```python
|
||||
/// Yields
|
||||
/// ------
|
||||
/// int
|
||||
/// Description of the anonymous integer return value.
|
||||
/// ```
|
||||
///
|
||||
/// For NumPy, see: <https://numpydoc.readthedocs.io/en/latest/format.html>
|
||||
///
|
||||
/// For Google, see: <https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings>
|
||||
pub(crate) fn has_subsections(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Args
|
||||
| Self::Arguments
|
||||
| Self::OtherArgs
|
||||
| Self::OtherParameters
|
||||
| Self::OtherParams
|
||||
| Self::Parameters
|
||||
| Self::Raises
|
||||
| Self::Returns
|
||||
| Self::SeeAlso
|
||||
| Self::Warns
|
||||
| Self::Yields
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SectionContexts<'a> {
|
||||
@@ -356,13 +384,16 @@ impl<'a> SectionContext<'a> {
|
||||
pub(crate) fn previous_line(&self) -> Option<&'a str> {
|
||||
let previous =
|
||||
&self.docstring_body.as_str()[TextRange::up_to(self.range_relative().start())];
|
||||
previous.universal_newlines().last().map(|l| l.as_str())
|
||||
previous
|
||||
.universal_newlines()
|
||||
.last()
|
||||
.map(|line| line.as_str())
|
||||
}
|
||||
|
||||
/// Returns the lines belonging to this section after the summary line.
|
||||
pub(crate) fn following_lines(&self) -> UniversalNewlineIterator<'a> {
|
||||
pub(crate) fn following_lines(&self) -> NewlineWithTrailingNewline<'a> {
|
||||
let lines = self.following_lines_str();
|
||||
UniversalNewlineIterator::with_offset(lines, self.offset() + self.data.summary_full_end)
|
||||
NewlineWithTrailingNewline::with_offset(lines, self.offset() + self.data.summary_full_end)
|
||||
}
|
||||
|
||||
fn following_lines_str(&self) -> &'a str {
|
||||
@@ -459,13 +490,54 @@ fn is_docstring_section(
|
||||
// args: The arguments to the function.
|
||||
// """
|
||||
// ```
|
||||
// Or `parameters` in:
|
||||
// ```python
|
||||
// def func(parameters: tuple[int]):
|
||||
// """Toggle the gizmo.
|
||||
//
|
||||
// Parameters:
|
||||
// -----
|
||||
// parameters:
|
||||
// The arguments to the function.
|
||||
// """
|
||||
// ```
|
||||
// However, if the header is an _exact_ match (like `Returns:`, as opposed to `returns:`), then
|
||||
// continue to treat it as a section header.
|
||||
if let Some(previous_section) = previous_section {
|
||||
if previous_section.indent_size < indent_size {
|
||||
if section_kind.has_subsections() {
|
||||
if let Some(previous_section) = previous_section {
|
||||
let verbatim = &line[TextRange::at(indent_size, section_name_size)];
|
||||
if section_kind.as_str() != verbatim {
|
||||
return false;
|
||||
|
||||
// If the section is more deeply indented, assume it's a subsection, as in:
|
||||
// ```python
|
||||
// def func(args: tuple[int]):
|
||||
// """Toggle the gizmo.
|
||||
//
|
||||
// Args:
|
||||
// args: The arguments to the function.
|
||||
// """
|
||||
// ```
|
||||
if previous_section.indent_size < indent_size {
|
||||
if section_kind.as_str() != verbatim {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the section isn't underlined, and isn't title-cased, assume it's a subsection,
|
||||
// as in:
|
||||
// ```python
|
||||
// def func(parameters: tuple[int]):
|
||||
// """Toggle the gizmo.
|
||||
//
|
||||
// Parameters:
|
||||
// -----
|
||||
// parameters:
|
||||
// The arguments to the function.
|
||||
// """
|
||||
// ```
|
||||
if !next_line_is_underline && verbatim.chars().next().is_some_and(char::is_lowercase) {
|
||||
if section_kind.as_str() != verbatim {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ pub fn check_path(
|
||||
path,
|
||||
locator,
|
||||
indexer,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
source_kind.as_ipy_notebook().map(Notebook::cell_offsets),
|
||||
|
||||
@@ -264,6 +264,11 @@ impl Rule {
|
||||
| Rule::BadQuotesMultilineString
|
||||
| Rule::BlanketNOQA
|
||||
| Rule::BlanketTypeIgnore
|
||||
| Rule::BlankLineAfterDecorator
|
||||
| Rule::BlankLineBetweenMethods
|
||||
| Rule::BlankLinesAfterFunctionOrClass
|
||||
| Rule::BlankLinesBeforeNestedDefinition
|
||||
| Rule::BlankLinesTopLevel
|
||||
| Rule::CommentedOutCode
|
||||
| Rule::EmptyComment
|
||||
| Rule::ExtraneousParentheses
|
||||
@@ -296,6 +301,7 @@ impl Rule {
|
||||
| Rule::ShebangNotFirstLine
|
||||
| Rule::SingleLineImplicitStringConcatenation
|
||||
| Rule::TabIndentation
|
||||
| Rule::TooManyBlankLines
|
||||
| Rule::TrailingCommaOnBareTuple
|
||||
| Rule::TypeCommentInStub
|
||||
| Rule::UselessSemicolon
|
||||
|
||||
@@ -321,6 +321,16 @@ mod schema {
|
||||
true
|
||||
}
|
||||
})
|
||||
.filter(|_rule| {
|
||||
// Filter out all test-only rules
|
||||
#[cfg(feature = "test-rules")]
|
||||
#[allow(clippy::used_underscore_binding)]
|
||||
if _rule.starts_with("RUF9") {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.sorted()
|
||||
.map(Value::String)
|
||||
.collect(),
|
||||
|
||||
@@ -46,6 +46,7 @@ mod tests {
|
||||
#[test_case(Rule::SubprocessWithoutShellEqualsTrue, Path::new("S603.py"))]
|
||||
#[test_case(Rule::SuspiciousPickleUsage, Path::new("S301.py"))]
|
||||
#[test_case(Rule::SuspiciousEvalUsage, Path::new("S307.py"))]
|
||||
#[test_case(Rule::SuspiciousMarkSafeUsage, Path::new("S308.py"))]
|
||||
#[test_case(Rule::SuspiciousURLOpenUsage, Path::new("S310.py"))]
|
||||
#[test_case(Rule::SuspiciousTelnetUsage, Path::new("S312.py"))]
|
||||
#[test_case(Rule::SuspiciousTelnetlibImport, Path::new("S401.py"))]
|
||||
|
||||
@@ -40,7 +40,9 @@ impl Violation for HardcodedBindAllInterfaces {
|
||||
pub(crate) fn hardcoded_bind_all_interfaces(checker: &mut Checker, string: StringLike) {
|
||||
let is_bind_all_interface = match string {
|
||||
StringLike::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "0.0.0.0",
|
||||
StringLike::FStringLiteral(ast::FStringLiteralElement { value, .. }) => value == "0.0.0.0",
|
||||
StringLike::FStringLiteral(ast::FStringLiteralElement { value, .. }) => {
|
||||
&**value == "0.0.0.0"
|
||||
}
|
||||
StringLike::BytesLiteral(_) => return,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! See: <https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html>
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall};
|
||||
use ruff_python_ast::{self as ast, Decorator, Expr, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -848,7 +848,7 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
|
||||
// Eval
|
||||
["" | "builtins", "eval"] => Some(SuspiciousEvalUsage.into()),
|
||||
// MarkSafe
|
||||
["django", "utils", "safestring", "mark_safe"] => Some(SuspiciousMarkSafeUsage.into()),
|
||||
["django", "utils", "safestring" | "html", "mark_safe"] => Some(SuspiciousMarkSafeUsage.into()),
|
||||
// URLOpen (`urlopen`, `urlretrieve`, `Request`)
|
||||
["urllib", "request", "urlopen" | "urlretrieve" | "Request"] |
|
||||
["six", "moves", "urllib", "request", "urlopen" | "urlretrieve" | "Request"] => {
|
||||
@@ -901,3 +901,27 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
/// S308
|
||||
pub(crate) fn suspicious_function_decorator(checker: &mut Checker, decorator: &Decorator) {
|
||||
let Some(diagnostic_kind) = checker
|
||||
.semantic()
|
||||
.resolve_call_path(&decorator.expression)
|
||||
.and_then(|call_path| {
|
||||
match call_path.as_slice() {
|
||||
// MarkSafe
|
||||
["django", "utils", "safestring" | "html", "mark_safe"] => {
|
||||
Some(SuspiciousMarkSafeUsage.into())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let diagnostic = Diagnostic::new::<DiagnosticKind>(diagnostic_kind, decorator.range());
|
||||
if checker.enabled(diagnostic.kind.rule()) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S308.py:5:12: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
|
|
||||
4 | def some_func():
|
||||
5 | return mark_safe('<script>alert("evil!")</script>')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
|
||||
|
|
||||
|
||||
S308.py:8:1: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
|
|
||||
8 | @mark_safe
|
||||
| ^^^^^^^^^^ S308
|
||||
9 | def some_func():
|
||||
10 | return '<script>alert("evil!")</script>'
|
||||
|
|
||||
|
||||
S308.py:17:12: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
|
|
||||
16 | def some_func():
|
||||
17 | return mark_safe('<script>alert("evil!")</script>')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
|
||||
|
|
||||
|
||||
S308.py:20:1: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
|
|
||||
20 | @mark_safe
|
||||
| ^^^^^^^^^^ S308
|
||||
21 | def some_func():
|
||||
22 | return '<script>alert("evil!")</script>'
|
||||
|
|
||||
|
||||
|
||||
@@ -59,11 +59,11 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
})),
|
||||
arguments: Arguments {
|
||||
args: if let Some(msg) = msg {
|
||||
vec![msg.clone()]
|
||||
Box::from([msg.clone()])
|
||||
} else {
|
||||
vec![]
|
||||
Box::from([])
|
||||
},
|
||||
keywords: vec![],
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -91,7 +91,7 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem])
|
||||
return;
|
||||
}
|
||||
|
||||
let [arg] = arguments.args.as_slice() else {
|
||||
let [arg] = &*arguments.args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::Node;
|
||||
use ruff_python_ast::visitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, ExprContext, Stmt};
|
||||
use ruff_python_ast::{self as ast, Comprehension, Expr, ExprContext, Stmt};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -126,18 +126,13 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
match expr {
|
||||
Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range: _,
|
||||
}) => {
|
||||
match func.as_ref() {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
if matches!(id.as_str(), "filter" | "reduce" | "map") {
|
||||
for arg in args {
|
||||
for arg in arguments.args.iter() {
|
||||
if arg.is_lambda_expr() {
|
||||
self.safe_functions.push(arg);
|
||||
}
|
||||
@@ -148,7 +143,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
if attr == "reduce" {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() {
|
||||
if id == "functools" {
|
||||
for arg in args {
|
||||
for arg in arguments.args.iter() {
|
||||
if arg.is_lambda_expr() {
|
||||
self.safe_functions.push(arg);
|
||||
}
|
||||
@@ -160,7 +155,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
for keyword in keywords {
|
||||
for keyword in arguments.keywords.iter() {
|
||||
if keyword.arg.as_ref().is_some_and(|arg| arg == "key")
|
||||
&& keyword.value.is_lambda_expr()
|
||||
{
|
||||
|
||||
@@ -114,7 +114,7 @@ fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||
}
|
||||
|
||||
// Ex) `iterools.repeat(1, times=None)`
|
||||
for keyword in keywords {
|
||||
for keyword in keywords.iter() {
|
||||
if keyword.arg.as_ref().is_some_and(|name| name == "times") {
|
||||
if keyword.value.is_none_literal_expr() {
|
||||
return true;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::lexer::{LexResult, Spanned};
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
@@ -12,20 +10,20 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
/// Simplified token type.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
enum TokenType {
|
||||
Irrelevant,
|
||||
NonLogicalNewline,
|
||||
Newline,
|
||||
Comma,
|
||||
OpeningBracket,
|
||||
OpeningSquareBracket,
|
||||
OpeningCurlyBracket,
|
||||
ClosingBracket,
|
||||
For,
|
||||
Named,
|
||||
Def,
|
||||
Lambda,
|
||||
Colon,
|
||||
String,
|
||||
Newline,
|
||||
NonLogicalNewline,
|
||||
OpeningBracket,
|
||||
ClosingBracket,
|
||||
OpeningSquareBracket,
|
||||
Colon,
|
||||
Comma,
|
||||
OpeningCurlyBracket,
|
||||
Def,
|
||||
For,
|
||||
Lambda,
|
||||
Irrelevant,
|
||||
}
|
||||
|
||||
/// Simplified token specialized for the task.
|
||||
@@ -54,30 +52,30 @@ impl Token {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Spanned> for Token {
|
||||
fn from(spanned: &Spanned) -> Self {
|
||||
let r#type = match &spanned.0 {
|
||||
Tok::NonLogicalNewline => TokenType::NonLogicalNewline,
|
||||
impl From<(&Tok, TextRange)> for Token {
|
||||
fn from((tok, range): (&Tok, TextRange)) -> Self {
|
||||
let r#type = match tok {
|
||||
Tok::Name { .. } => TokenType::Named,
|
||||
Tok::String { .. } => TokenType::String,
|
||||
Tok::Newline => TokenType::Newline,
|
||||
Tok::For => TokenType::For,
|
||||
Tok::NonLogicalNewline => TokenType::NonLogicalNewline,
|
||||
Tok::Lpar => TokenType::OpeningBracket,
|
||||
Tok::Rpar => TokenType::ClosingBracket,
|
||||
Tok::Lsqb => TokenType::OpeningSquareBracket,
|
||||
Tok::Rsqb => TokenType::ClosingBracket,
|
||||
Tok::Colon => TokenType::Colon,
|
||||
Tok::Comma => TokenType::Comma,
|
||||
Tok::Lbrace => TokenType::OpeningCurlyBracket,
|
||||
Tok::Rbrace => TokenType::ClosingBracket,
|
||||
Tok::Def => TokenType::Def,
|
||||
Tok::For => TokenType::For,
|
||||
Tok::Lambda => TokenType::Lambda,
|
||||
// Import treated like a function.
|
||||
Tok::Import => TokenType::Named,
|
||||
Tok::Name { .. } => TokenType::Named,
|
||||
Tok::String { .. } => TokenType::String,
|
||||
Tok::Comma => TokenType::Comma,
|
||||
Tok::Lpar => TokenType::OpeningBracket,
|
||||
Tok::Lsqb => TokenType::OpeningSquareBracket,
|
||||
Tok::Lbrace => TokenType::OpeningCurlyBracket,
|
||||
Tok::Rpar | Tok::Rsqb | Tok::Rbrace => TokenType::ClosingBracket,
|
||||
Tok::Colon => TokenType::Colon,
|
||||
_ => TokenType::Irrelevant,
|
||||
};
|
||||
Self {
|
||||
range: spanned.1,
|
||||
r#type,
|
||||
}
|
||||
#[allow(clippy::inconsistent_struct_constructor)]
|
||||
Self { range, r#type }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,10 +235,12 @@ pub(crate) fn trailing_commas(
|
||||
indexer: &Indexer,
|
||||
) {
|
||||
let mut fstrings = 0u32;
|
||||
let tokens = tokens
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter_map(|spanned @ (tok, tok_range)| match tok {
|
||||
let tokens = tokens.iter().filter_map(|result| {
|
||||
let Ok((tok, tok_range)) = result else {
|
||||
return None;
|
||||
};
|
||||
|
||||
match tok {
|
||||
// Completely ignore comments -- they just interfere with the logic.
|
||||
Tok::Comment(_) => None,
|
||||
// F-strings are handled as `String` token type with the complete range
|
||||
@@ -263,69 +263,30 @@ pub(crate) fn trailing_commas(
|
||||
}
|
||||
_ => {
|
||||
if fstrings == 0 {
|
||||
Some(Token::from(spanned))
|
||||
Some(Token::from((tok, *tok_range)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
let tokens = [Token::irrelevant(), Token::irrelevant()]
|
||||
.into_iter()
|
||||
.chain(tokens);
|
||||
// Collapse consecutive newlines to the first one -- trailing commas are
|
||||
// added before the first newline.
|
||||
let tokens = tokens.coalesce(|previous, current| {
|
||||
if previous.r#type == TokenType::NonLogicalNewline
|
||||
&& current.r#type == TokenType::NonLogicalNewline
|
||||
{
|
||||
Ok(previous)
|
||||
} else {
|
||||
Err((previous, current))
|
||||
}
|
||||
});
|
||||
|
||||
// The current nesting of the comma contexts.
|
||||
let mut prev = Token::irrelevant();
|
||||
let mut prev_prev = Token::irrelevant();
|
||||
|
||||
let mut stack = vec![Context::new(ContextType::No)];
|
||||
|
||||
for (prev_prev, prev, token) in tokens.tuple_windows() {
|
||||
// Update the comma context stack.
|
||||
match token.r#type {
|
||||
TokenType::OpeningBracket => match (prev.r#type, prev_prev.r#type) {
|
||||
(TokenType::Named, TokenType::Def) => {
|
||||
stack.push(Context::new(ContextType::FunctionParameters));
|
||||
}
|
||||
(TokenType::Named | TokenType::ClosingBracket, _) => {
|
||||
stack.push(Context::new(ContextType::CallArguments));
|
||||
}
|
||||
_ => {
|
||||
stack.push(Context::new(ContextType::Tuple));
|
||||
}
|
||||
},
|
||||
TokenType::OpeningSquareBracket => match prev.r#type {
|
||||
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
|
||||
stack.push(Context::new(ContextType::Subscript));
|
||||
}
|
||||
_ => {
|
||||
stack.push(Context::new(ContextType::List));
|
||||
}
|
||||
},
|
||||
TokenType::OpeningCurlyBracket => {
|
||||
stack.push(Context::new(ContextType::Dict));
|
||||
}
|
||||
TokenType::Lambda => {
|
||||
stack.push(Context::new(ContextType::LambdaParameters));
|
||||
}
|
||||
TokenType::For => {
|
||||
let len = stack.len();
|
||||
stack[len - 1] = Context::new(ContextType::No);
|
||||
}
|
||||
TokenType::Comma => {
|
||||
let len = stack.len();
|
||||
stack[len - 1].inc();
|
||||
}
|
||||
_ => {}
|
||||
for token in tokens {
|
||||
if prev.r#type == TokenType::NonLogicalNewline
|
||||
&& token.r#type == TokenType::NonLogicalNewline
|
||||
{
|
||||
// Collapse consecutive newlines to the first one -- trailing commas are
|
||||
// added before the first newline.
|
||||
continue;
|
||||
}
|
||||
let context = &stack[stack.len() - 1];
|
||||
|
||||
// Update the comma context stack.
|
||||
let context = update_context(token, prev, prev_prev, &mut stack);
|
||||
|
||||
// Is it allowed to have a trailing comma before this token?
|
||||
let comma_allowed = token.r#type == TokenType::ClosingBracket
|
||||
@@ -412,5 +373,47 @@ pub(crate) fn trailing_commas(
|
||||
if pop_context && stack.len() > 1 {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
prev_prev = prev;
|
||||
prev = token;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_context(
|
||||
token: Token,
|
||||
prev: Token,
|
||||
prev_prev: Token,
|
||||
stack: &mut Vec<Context>,
|
||||
) -> Context {
|
||||
let new_context = match token.r#type {
|
||||
TokenType::OpeningBracket => match (prev.r#type, prev_prev.r#type) {
|
||||
(TokenType::Named, TokenType::Def) => Context::new(ContextType::FunctionParameters),
|
||||
(TokenType::Named | TokenType::ClosingBracket, _) => {
|
||||
Context::new(ContextType::CallArguments)
|
||||
}
|
||||
_ => Context::new(ContextType::Tuple),
|
||||
},
|
||||
TokenType::OpeningSquareBracket => match prev.r#type {
|
||||
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
|
||||
Context::new(ContextType::Subscript)
|
||||
}
|
||||
_ => Context::new(ContextType::List),
|
||||
},
|
||||
TokenType::OpeningCurlyBracket => Context::new(ContextType::Dict),
|
||||
TokenType::Lambda => Context::new(ContextType::LambdaParameters),
|
||||
TokenType::For => {
|
||||
let last = stack.last_mut().expect("Stack to never be empty");
|
||||
*last = Context::new(ContextType::No);
|
||||
return *last;
|
||||
}
|
||||
TokenType::Comma => {
|
||||
let last = stack.last_mut().expect("Stack to never be empty");
|
||||
last.inc();
|
||||
return *last;
|
||||
}
|
||||
_ => return stack.last().copied().expect("Stack to never be empty"),
|
||||
};
|
||||
|
||||
stack.push(new_context);
|
||||
new_context
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ use crate::rules::flake8_comprehensions::settings::Settings;
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
|
||||
/// when rewriting the call. In most cases, though, comments will be preserved.
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.flake8-comprehensions.allow-dict-calls-with-keyword-arguments`
|
||||
#[violation]
|
||||
pub struct UnnecessaryCollectionCall {
|
||||
obj_type: String,
|
||||
|
||||
@@ -88,7 +88,7 @@ fn is_nullable_field<'a>(value: &'a Expr, semantic: &'a SemanticModel) -> Option
|
||||
let mut null_key = false;
|
||||
let mut blank_key = false;
|
||||
let mut unique_key = false;
|
||||
for keyword in &call.arguments.keywords {
|
||||
for keyword in call.arguments.keywords.iter() {
|
||||
let Some(argument) = &keyword.arg else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) {
|
||||
.resolve_call_path(func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["", "dict"]))
|
||||
{
|
||||
for keyword in keywords {
|
||||
for keyword in keywords.iter() {
|
||||
if let Some(attr) = &keyword.arg {
|
||||
if is_reserved_attr(attr) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
|
||||
@@ -97,7 +97,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let [arg] = args.as_slice() else {
|
||||
let [arg] = &**args else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -188,8 +188,8 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||
let node3 = Expr::Call(ast::ExprCall {
|
||||
func: Box::new(node2),
|
||||
arguments: Arguments {
|
||||
args: vec![node],
|
||||
keywords: vec![],
|
||||
args: Box::from([node]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -59,7 +59,7 @@ impl Violation for UnnecessaryDictKwargs {
|
||||
/// PIE804
|
||||
pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
let mut duplicate_keywords = None;
|
||||
for keyword in &call.arguments.keywords {
|
||||
for keyword in call.arguments.keywords.iter() {
|
||||
// keyword is a spread operator (indicated by None).
|
||||
if keyword.arg.is_some() {
|
||||
continue;
|
||||
@@ -145,7 +145,7 @@ fn duplicates(call: &ast::ExprCall) -> FxHashSet<&str> {
|
||||
call.arguments.keywords.len(),
|
||||
BuildHasherDefault::default(),
|
||||
);
|
||||
for keyword in &call.arguments.keywords {
|
||||
for keyword in call.arguments.keywords.iter() {
|
||||
if let Some(name) = &keyword.arg {
|
||||
if !seen.insert(name.as_str()) {
|
||||
duplicates.insert(name.as_str());
|
||||
|
||||
@@ -60,7 +60,7 @@ pub(crate) fn unnecessary_range_start(checker: &mut Checker, call: &ast::ExprCal
|
||||
}
|
||||
|
||||
// Verify that the call has exactly two arguments (no `step`).
|
||||
let [start, _] = call.arguments.args.as_slice() else {
|
||||
let [start, _] = &*call.arguments.args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ pub(crate) fn bad_version_info_comparison(checker: &mut Checker, test: &Expr) {
|
||||
return;
|
||||
};
|
||||
|
||||
let ([op], [_right]) = (ops.as_slice(), comparators.as_slice()) else {
|
||||
let ([op], [_right]) = (&**ops, &**comparators) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ pub(crate) fn unrecognized_platform(checker: &mut Checker, test: &Expr) {
|
||||
return;
|
||||
};
|
||||
|
||||
let ([op], [right]) = (ops.as_slice(), comparators.as_slice()) else {
|
||||
let ([op], [right]) = (&**ops, &**comparators) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ pub(crate) fn unrecognized_version_info(checker: &mut Checker, test: &Expr) {
|
||||
return;
|
||||
};
|
||||
|
||||
let ([op], [comparator]) = (ops.as_slice(), comparators.as_slice()) else {
|
||||
let ([op], [comparator]) = (&**ops, &**comparators) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -411,7 +411,7 @@ fn to_pytest_raises_args<'a>(
|
||||
) -> Option<Cow<'a, str>> {
|
||||
let args = match attr {
|
||||
"assertRaises" | "failUnlessRaises" => {
|
||||
match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
|
||||
match (&*arguments.args, &*arguments.keywords) {
|
||||
// Ex) `assertRaises(Exception)`
|
||||
([arg], []) => Cow::Borrowed(checker.locator().slice(arg)),
|
||||
// Ex) `assertRaises(expected_exception=Exception)`
|
||||
@@ -427,7 +427,7 @@ fn to_pytest_raises_args<'a>(
|
||||
}
|
||||
}
|
||||
"assertRaisesRegex" | "assertRaisesRegexp" => {
|
||||
match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
|
||||
match (&*arguments.args, &*arguments.keywords) {
|
||||
// Ex) `assertRaisesRegex(Exception, regex)`
|
||||
([arg1, arg2], []) => Cow::Owned(format!(
|
||||
"{}, match={}",
|
||||
|
||||
@@ -257,15 +257,18 @@ fn elts_to_csv(elts: &[Expr], generator: Generator) -> Option<String> {
|
||||
}
|
||||
|
||||
let node = Expr::from(ast::StringLiteral {
|
||||
value: elts.iter().fold(String::new(), |mut acc, elt| {
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = elt {
|
||||
if !acc.is_empty() {
|
||||
acc.push(',');
|
||||
value: elts
|
||||
.iter()
|
||||
.fold(String::new(), |mut acc, elt| {
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = elt {
|
||||
if !acc.is_empty() {
|
||||
acc.push(',');
|
||||
}
|
||||
acc.push_str(value.to_str());
|
||||
}
|
||||
acc.push_str(value.to_str());
|
||||
}
|
||||
acc
|
||||
}),
|
||||
acc
|
||||
})
|
||||
.into_boxed_str(),
|
||||
..ast::StringLiteral::default()
|
||||
});
|
||||
Some(generator.expr(&node))
|
||||
@@ -327,7 +330,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
|
||||
.iter()
|
||||
.map(|name| {
|
||||
Expr::from(ast::StringLiteral {
|
||||
value: (*name).to_string(),
|
||||
value: (*name).to_string().into_boxed_str(),
|
||||
..ast::StringLiteral::default()
|
||||
})
|
||||
})
|
||||
@@ -360,7 +363,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
|
||||
.iter()
|
||||
.map(|name| {
|
||||
Expr::from(ast::StringLiteral {
|
||||
value: (*name).to_string(),
|
||||
value: (*name).to_string().into_boxed_str(),
|
||||
..ast::StringLiteral::default()
|
||||
})
|
||||
})
|
||||
@@ -635,17 +638,17 @@ pub(crate) fn parametrize(checker: &mut Checker, decorators: &[Decorator]) {
|
||||
}) = &decorator.expression
|
||||
{
|
||||
if checker.enabled(Rule::PytestParametrizeNamesWrongType) {
|
||||
if let [names, ..] = args.as_slice() {
|
||||
if let [names, ..] = &**args {
|
||||
check_names(checker, decorator, names);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::PytestParametrizeValuesWrongType) {
|
||||
if let [names, values, ..] = args.as_slice() {
|
||||
if let [names, values, ..] = &**args {
|
||||
check_values(checker, names, values);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::PytestDuplicateParametrizeTestCases) {
|
||||
if let [_, values, ..] = args.as_slice() {
|
||||
if let [_, values, ..] = &**args {
|
||||
check_duplicates(checker, values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +173,8 @@ fn assert(expr: &Expr, msg: Option<&Expr>) -> Stmt {
|
||||
fn compare(left: &Expr, cmp_op: CmpOp, right: &Expr) -> Expr {
|
||||
Expr::Compare(ast::ExprCompare {
|
||||
left: Box::new(left.clone()),
|
||||
ops: vec![cmp_op],
|
||||
comparators: vec![right.clone()],
|
||||
ops: Box::from([cmp_op]),
|
||||
comparators: Box::from([right.clone()]),
|
||||
range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
@@ -390,8 +390,8 @@ impl UnittestAssert {
|
||||
let node1 = ast::ExprCall {
|
||||
func: Box::new(node.into()),
|
||||
arguments: Arguments {
|
||||
args: vec![(**obj).clone(), (**cls).clone()],
|
||||
keywords: vec![],
|
||||
args: Box::from([(**obj).clone(), (**cls).clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
@@ -434,8 +434,8 @@ impl UnittestAssert {
|
||||
let node2 = ast::ExprCall {
|
||||
func: Box::new(node1.into()),
|
||||
arguments: Arguments {
|
||||
args: vec![(**regex).clone(), (**text).clone()],
|
||||
keywords: vec![],
|
||||
args: Box::from([(**regex).clone(), (**text).clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -221,6 +221,7 @@ pub(crate) fn avoidable_escaped_quote(
|
||||
Tok::FStringMiddle {
|
||||
value: string_contents,
|
||||
is_raw,
|
||||
triple_quoted: _,
|
||||
} if !is_raw => {
|
||||
let Some(context) = fstrings.last_mut() else {
|
||||
continue;
|
||||
@@ -361,6 +362,7 @@ pub(crate) fn unnecessary_escaped_quote(
|
||||
Tok::FStringMiddle {
|
||||
value: string_contents,
|
||||
is_raw,
|
||||
triple_quoted: _,
|
||||
} if !is_raw => {
|
||||
let Some(context) = fstrings.last_mut() else {
|
||||
continue;
|
||||
|
||||
@@ -437,8 +437,8 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
|
||||
let node2 = ast::ExprCall {
|
||||
func: Box::new(node1.into()),
|
||||
arguments: Arguments {
|
||||
args: vec![target.clone(), node.into()],
|
||||
keywords: vec![],
|
||||
args: Box::from([target.clone(), node.into()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
@@ -480,13 +480,13 @@ fn match_eq_target(expr: &Expr) -> Option<(&str, &Expr)> {
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if ops != &[CmpOp::Eq] {
|
||||
if **ops != [CmpOp::Eq] {
|
||||
return None;
|
||||
}
|
||||
let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
let [comparator] = comparators.as_slice() else {
|
||||
let [comparator] = &**comparators else {
|
||||
return None;
|
||||
};
|
||||
if !comparator.is_name_expr() {
|
||||
@@ -551,8 +551,8 @@ pub(crate) fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
|
||||
};
|
||||
let node2 = ast::ExprCompare {
|
||||
left: Box::new(node1.into()),
|
||||
ops: vec![CmpOp::In],
|
||||
comparators: vec![node.into()],
|
||||
ops: Box::from([CmpOp::In]),
|
||||
comparators: Box::from([node.into()]),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
let in_expr = node2.into();
|
||||
|
||||
@@ -217,7 +217,7 @@ fn check_os_environ_subscript(checker: &mut Checker, expr: &Expr) {
|
||||
slice.range(),
|
||||
);
|
||||
let node = ast::StringLiteral {
|
||||
value: capital_env_var,
|
||||
value: capital_env_var.into_boxed_str(),
|
||||
unicode: env_var.is_unicode(),
|
||||
..ast::StringLiteral::default()
|
||||
};
|
||||
|
||||
@@ -185,8 +185,8 @@ pub(crate) fn if_expr_with_true_false(
|
||||
.into(),
|
||||
),
|
||||
arguments: Arguments {
|
||||
args: vec![test.clone()],
|
||||
keywords: vec![],
|
||||
args: Box::from([test.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -176,7 +176,7 @@ pub(crate) fn negation_with_equal_op(
|
||||
);
|
||||
let node = ast::ExprCompare {
|
||||
left: left.clone(),
|
||||
ops: vec![CmpOp::NotEq],
|
||||
ops: Box::from([CmpOp::NotEq]),
|
||||
comparators: comparators.clone(),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
@@ -206,7 +206,7 @@ pub(crate) fn negation_with_not_equal_op(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !matches!(&ops[..], [CmpOp::NotEq]) {
|
||||
if !matches!(&**ops, [CmpOp::NotEq]) {
|
||||
return;
|
||||
}
|
||||
if is_exception_check(checker.semantic().current_statement()) {
|
||||
@@ -231,7 +231,7 @@ pub(crate) fn negation_with_not_equal_op(
|
||||
);
|
||||
let node = ast::ExprCompare {
|
||||
left: left.clone(),
|
||||
ops: vec![CmpOp::Eq],
|
||||
ops: Box::from([CmpOp::Eq]),
|
||||
comparators: comparators.clone(),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
@@ -279,8 +279,8 @@ pub(crate) fn double_negation(checker: &mut Checker, expr: &Expr, op: UnaryOp, o
|
||||
let node1 = ast::ExprCall {
|
||||
func: Box::new(node.into()),
|
||||
arguments: Arguments {
|
||||
args: vec![*operand.clone()],
|
||||
keywords: vec![],
|
||||
args: Box::from([*operand.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -253,8 +253,7 @@ fn is_main_check(expr: &Expr) -> bool {
|
||||
{
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() {
|
||||
if id == "__name__" {
|
||||
if let [Expr::StringLiteral(ast::ExprStringLiteral { value, .. })] =
|
||||
comparators.as_slice()
|
||||
if let [Expr::StringLiteral(ast::ExprStringLiteral { value, .. })] = &**comparators
|
||||
{
|
||||
if value == "__main__" {
|
||||
return true;
|
||||
|
||||
@@ -122,7 +122,7 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &mut Checker, stmt_if:
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let [test_dict] = test_dict.as_slice() else {
|
||||
let [test_dict] = &**test_dict else {
|
||||
return;
|
||||
};
|
||||
let (expected_var, expected_value, default_var, default_value) = match ops[..] {
|
||||
@@ -176,8 +176,8 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &mut Checker, stmt_if:
|
||||
let node3 = ast::ExprCall {
|
||||
func: Box::new(node2.into()),
|
||||
arguments: Arguments {
|
||||
args: vec![node1, node],
|
||||
keywords: vec![],
|
||||
args: Box::from([node1, node]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
@@ -233,11 +233,11 @@ pub(crate) fn if_exp_instead_of_dict_get(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let [test_dict] = test_dict.as_slice() else {
|
||||
let [test_dict] = &**test_dict else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (body, default_value) = match ops.as_slice() {
|
||||
let (body, default_value) = match &**ops {
|
||||
[CmpOp::In] => (body, orelse),
|
||||
[CmpOp::NotIn] => (orelse, body),
|
||||
_ => {
|
||||
@@ -276,8 +276,8 @@ pub(crate) fn if_exp_instead_of_dict_get(
|
||||
let fixed_node = ast::ExprCall {
|
||||
func: Box::new(dict_get_node.into()),
|
||||
arguments: Arguments {
|
||||
args: vec![dict_key_node, default_value_node],
|
||||
keywords: vec![],
|
||||
args: Box::from([dict_key_node, default_value_node]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -64,10 +64,10 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &mut Checker, stmt_i
|
||||
let Expr::Name(ast::ExprName { id: target, .. }) = left.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if ops != &[CmpOp::Eq] {
|
||||
if **ops != [CmpOp::Eq] {
|
||||
return;
|
||||
}
|
||||
let [expr] = comparators.as_slice() else {
|
||||
let [expr] = &**comparators else {
|
||||
return;
|
||||
};
|
||||
let Some(literal_expr) = expr.as_literal_expr() else {
|
||||
@@ -127,10 +127,10 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &mut Checker, stmt_i
|
||||
let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if id != target || ops != &[CmpOp::Eq] {
|
||||
if id != target || **ops != [CmpOp::Eq] {
|
||||
return;
|
||||
}
|
||||
let [expr] = comparators.as_slice() else {
|
||||
let [expr] = &**comparators else {
|
||||
return;
|
||||
};
|
||||
let Some(literal_expr) = expr.as_literal_expr() else {
|
||||
|
||||
@@ -194,7 +194,7 @@ pub(crate) fn key_in_dict_comprehension(checker: &mut Checker, comprehension: &C
|
||||
|
||||
/// SIM118 in a comparison.
|
||||
pub(crate) fn key_in_dict_compare(checker: &mut Checker, compare: &ast::ExprCompare) {
|
||||
let [op] = compare.ops.as_slice() else {
|
||||
let [op] = &*compare.ops else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -202,7 +202,7 @@ pub(crate) fn key_in_dict_compare(checker: &mut Checker, compare: &ast::ExprComp
|
||||
return;
|
||||
}
|
||||
|
||||
let [right] = compare.comparators.as_slice() else {
|
||||
let [right] = &*compare.comparators else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -161,8 +161,8 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt_if: &ast::StmtIf) {
|
||||
let value_node = ast::ExprCall {
|
||||
func: Box::new(func_node.into()),
|
||||
arguments: Arguments {
|
||||
args: vec![if_test.clone()],
|
||||
keywords: vec![],
|
||||
args: Box::from([if_test.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -140,7 +140,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) {
|
||||
range: _,
|
||||
}) = &loop_.test
|
||||
{
|
||||
if let ([op], [comparator]) = (ops.as_slice(), comparators.as_slice()) {
|
||||
if let ([op], [comparator]) = (&**ops, &**comparators) {
|
||||
let op = match op {
|
||||
CmpOp::Eq => CmpOp::NotEq,
|
||||
CmpOp::NotEq => CmpOp::Eq,
|
||||
@@ -155,8 +155,8 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) {
|
||||
};
|
||||
let node = ast::ExprCompare {
|
||||
left: left.clone(),
|
||||
ops: vec![op],
|
||||
comparators: vec![comparator.clone()],
|
||||
ops: Box::from([op]),
|
||||
comparators: Box::from([comparator.clone()]),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
node.into()
|
||||
@@ -391,8 +391,8 @@ fn return_stmt(id: &str, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
|
||||
let node2 = ast::ExprCall {
|
||||
func: Box::new(node1.into()),
|
||||
arguments: Arguments {
|
||||
args: vec![node.into()],
|
||||
keywords: vec![],
|
||||
args: Box::from([node.into()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -3,24 +3,20 @@ source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
|
||||
---
|
||||
TRIO100.py:5:5: TRIO100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
||||
|
|
||||
4 | async def foo():
|
||||
4 | async def func():
|
||||
5 | with trio.fail_after():
|
||||
| _____^
|
||||
6 | | ...
|
||||
| |___________^ TRIO100
|
||||
7 |
|
||||
8 | async def foo():
|
||||
|
|
||||
|
||||
TRIO100.py:13:5: TRIO100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
||||
TRIO100.py:15:5: TRIO100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
||||
|
|
||||
12 | async def foo():
|
||||
13 | with trio.move_on_after():
|
||||
14 | async def func():
|
||||
15 | with trio.move_on_after():
|
||||
| _____^
|
||||
14 | | ...
|
||||
16 | | ...
|
||||
| |___________^ TRIO100
|
||||
15 |
|
||||
16 | async def foo():
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, ExprCall};
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -53,19 +52,15 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Call(ExprCall {
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
}) = expr
|
||||
else {
|
||||
let Expr::Call(ExprCall { arguments, .. }) = expr else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !keywords.is_empty() {
|
||||
if !arguments.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let [Expr::StringLiteral(ast::ExprStringLiteral { value, range })] = args.as_slice() else {
|
||||
let [Expr::StringLiteral(ast::ExprStringLiteral { value, range })] = &*arguments.args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -610,7 +610,7 @@ impl Violation for OsPathIsfile {
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_link()` can improve readability over the `os`
|
||||
/// methods such as `Path.is_symlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.path.islink()`).
|
||||
///
|
||||
/// Note that `os` functions may be preferable if performance is a concern,
|
||||
@@ -627,11 +627,11 @@ impl Violation for OsPathIsfile {
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_link()
|
||||
/// Path("docs").is_symlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_link`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_link)
|
||||
/// - [Python documentation: `Path.is_symlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink)
|
||||
/// - [Python documentation: `os.path.islink`](https://docs.python.org/3/library/os.path.html#os.path.islink)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
|
||||
@@ -15,7 +15,7 @@ fn to_f_string_expression_element(inner: &Expr) -> ast::FStringElement {
|
||||
/// Convert a string to a [`ast::FStringElement::Literal`].
|
||||
pub(super) fn to_f_string_literal_element(s: &str) -> ast::FStringElement {
|
||||
ast::FStringElement::Literal(ast::FStringLiteralElement {
|
||||
value: s.to_owned(),
|
||||
value: s.to_string().into_boxed_str(),
|
||||
range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
@@ -53,7 +53,7 @@ pub(super) fn to_f_string_element(expr: &Expr) -> Option<ast::FStringElement> {
|
||||
match expr {
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, range }) => {
|
||||
Some(ast::FStringElement::Literal(ast::FStringLiteralElement {
|
||||
value: value.to_string(),
|
||||
value: value.to_string().into_boxed_str(),
|
||||
range: *range,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@ fn build_fstring(joiner: &str, joinees: &[Expr]) -> Option<Expr> {
|
||||
None
|
||||
}
|
||||
})
|
||||
.join(joiner),
|
||||
.join(joiner)
|
||||
.into_boxed_str(),
|
||||
..ast::StringLiteral::default()
|
||||
};
|
||||
return Some(node.into());
|
||||
@@ -115,7 +116,7 @@ pub(crate) fn static_join_to_fstring(checker: &mut Checker, expr: &Expr, joiner:
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
let [arg] = args.as_slice() else {
|
||||
let [arg] = &**args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -80,8 +80,11 @@ pub(crate) fn legacy_random(checker: &mut Checker, expr: &Expr) {
|
||||
"set_state" |
|
||||
// Simple random data
|
||||
"rand" |
|
||||
"ranf" |
|
||||
"sample" |
|
||||
"randn" |
|
||||
"randint" |
|
||||
"random" |
|
||||
"random_integers" |
|
||||
"random_sample" |
|
||||
"choice" |
|
||||
|
||||
@@ -45,7 +45,7 @@ NPY002.py:20:1: NPY002 Replace legacy `np.random.set_state` call with `np.random
|
||||
20 | numpy.random.set_state()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
21 | numpy.random.rand()
|
||||
22 | numpy.random.randn()
|
||||
22 | numpy.random.ranf()
|
||||
|
|
||||
|
||||
NPY002.py:21:1: NPY002 Replace legacy `np.random.rand` call with `np.random.Generator`
|
||||
@@ -54,444 +54,464 @@ NPY002.py:21:1: NPY002 Replace legacy `np.random.rand` call with `np.random.Gene
|
||||
20 | numpy.random.set_state()
|
||||
21 | numpy.random.rand()
|
||||
| ^^^^^^^^^^^^^^^^^ NPY002
|
||||
22 | numpy.random.randn()
|
||||
23 | numpy.random.randint()
|
||||
22 | numpy.random.ranf()
|
||||
23 | numpy.random.sample()
|
||||
|
|
||||
|
||||
NPY002.py:22:1: NPY002 Replace legacy `np.random.randn` call with `np.random.Generator`
|
||||
NPY002.py:22:1: NPY002 Replace legacy `np.random.ranf` call with `np.random.Generator`
|
||||
|
|
||||
20 | numpy.random.set_state()
|
||||
21 | numpy.random.rand()
|
||||
22 | numpy.random.randn()
|
||||
| ^^^^^^^^^^^^^^^^^^ NPY002
|
||||
23 | numpy.random.randint()
|
||||
24 | numpy.random.random_integers()
|
||||
22 | numpy.random.ranf()
|
||||
| ^^^^^^^^^^^^^^^^^ NPY002
|
||||
23 | numpy.random.sample()
|
||||
24 | numpy.random.randn()
|
||||
|
|
||||
|
||||
NPY002.py:23:1: NPY002 Replace legacy `np.random.randint` call with `np.random.Generator`
|
||||
NPY002.py:23:1: NPY002 Replace legacy `np.random.sample` call with `np.random.Generator`
|
||||
|
|
||||
21 | numpy.random.rand()
|
||||
22 | numpy.random.randn()
|
||||
23 | numpy.random.randint()
|
||||
22 | numpy.random.ranf()
|
||||
23 | numpy.random.sample()
|
||||
| ^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
24 | numpy.random.randn()
|
||||
25 | numpy.random.randint()
|
||||
|
|
||||
|
||||
NPY002.py:24:1: NPY002 Replace legacy `np.random.randn` call with `np.random.Generator`
|
||||
|
|
||||
22 | numpy.random.ranf()
|
||||
23 | numpy.random.sample()
|
||||
24 | numpy.random.randn()
|
||||
| ^^^^^^^^^^^^^^^^^^ NPY002
|
||||
25 | numpy.random.randint()
|
||||
26 | numpy.random.random()
|
||||
|
|
||||
|
||||
NPY002.py:25:1: NPY002 Replace legacy `np.random.randint` call with `np.random.Generator`
|
||||
|
|
||||
23 | numpy.random.sample()
|
||||
24 | numpy.random.randn()
|
||||
25 | numpy.random.randint()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
24 | numpy.random.random_integers()
|
||||
25 | numpy.random.random_sample()
|
||||
26 | numpy.random.random()
|
||||
27 | numpy.random.random_integers()
|
||||
|
|
||||
|
||||
NPY002.py:24:1: NPY002 Replace legacy `np.random.random_integers` call with `np.random.Generator`
|
||||
NPY002.py:26:1: NPY002 Replace legacy `np.random.random` call with `np.random.Generator`
|
||||
|
|
||||
22 | numpy.random.randn()
|
||||
23 | numpy.random.randint()
|
||||
24 | numpy.random.random_integers()
|
||||
24 | numpy.random.randn()
|
||||
25 | numpy.random.randint()
|
||||
26 | numpy.random.random()
|
||||
| ^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
27 | numpy.random.random_integers()
|
||||
28 | numpy.random.random_sample()
|
||||
|
|
||||
|
||||
NPY002.py:27:1: NPY002 Replace legacy `np.random.random_integers` call with `np.random.Generator`
|
||||
|
|
||||
25 | numpy.random.randint()
|
||||
26 | numpy.random.random()
|
||||
27 | numpy.random.random_integers()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
25 | numpy.random.random_sample()
|
||||
26 | numpy.random.choice()
|
||||
28 | numpy.random.random_sample()
|
||||
29 | numpy.random.choice()
|
||||
|
|
||||
|
||||
NPY002.py:25:1: NPY002 Replace legacy `np.random.random_sample` call with `np.random.Generator`
|
||||
NPY002.py:28:1: NPY002 Replace legacy `np.random.random_sample` call with `np.random.Generator`
|
||||
|
|
||||
23 | numpy.random.randint()
|
||||
24 | numpy.random.random_integers()
|
||||
25 | numpy.random.random_sample()
|
||||
26 | numpy.random.random()
|
||||
27 | numpy.random.random_integers()
|
||||
28 | numpy.random.random_sample()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
26 | numpy.random.choice()
|
||||
27 | numpy.random.bytes()
|
||||
29 | numpy.random.choice()
|
||||
30 | numpy.random.bytes()
|
||||
|
|
||||
|
||||
NPY002.py:26:1: NPY002 Replace legacy `np.random.choice` call with `np.random.Generator`
|
||||
NPY002.py:29:1: NPY002 Replace legacy `np.random.choice` call with `np.random.Generator`
|
||||
|
|
||||
24 | numpy.random.random_integers()
|
||||
25 | numpy.random.random_sample()
|
||||
26 | numpy.random.choice()
|
||||
27 | numpy.random.random_integers()
|
||||
28 | numpy.random.random_sample()
|
||||
29 | numpy.random.choice()
|
||||
| ^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
27 | numpy.random.bytes()
|
||||
28 | numpy.random.shuffle()
|
||||
30 | numpy.random.bytes()
|
||||
31 | numpy.random.shuffle()
|
||||
|
|
||||
|
||||
NPY002.py:27:1: NPY002 Replace legacy `np.random.bytes` call with `np.random.Generator`
|
||||
NPY002.py:30:1: NPY002 Replace legacy `np.random.bytes` call with `np.random.Generator`
|
||||
|
|
||||
25 | numpy.random.random_sample()
|
||||
26 | numpy.random.choice()
|
||||
27 | numpy.random.bytes()
|
||||
28 | numpy.random.random_sample()
|
||||
29 | numpy.random.choice()
|
||||
30 | numpy.random.bytes()
|
||||
| ^^^^^^^^^^^^^^^^^^ NPY002
|
||||
28 | numpy.random.shuffle()
|
||||
29 | numpy.random.permutation()
|
||||
31 | numpy.random.shuffle()
|
||||
32 | numpy.random.permutation()
|
||||
|
|
||||
|
||||
NPY002.py:28:1: NPY002 Replace legacy `np.random.shuffle` call with `np.random.Generator`
|
||||
NPY002.py:31:1: NPY002 Replace legacy `np.random.shuffle` call with `np.random.Generator`
|
||||
|
|
||||
26 | numpy.random.choice()
|
||||
27 | numpy.random.bytes()
|
||||
28 | numpy.random.shuffle()
|
||||
29 | numpy.random.choice()
|
||||
30 | numpy.random.bytes()
|
||||
31 | numpy.random.shuffle()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
29 | numpy.random.permutation()
|
||||
30 | numpy.random.beta()
|
||||
32 | numpy.random.permutation()
|
||||
33 | numpy.random.beta()
|
||||
|
|
||||
|
||||
NPY002.py:29:1: NPY002 Replace legacy `np.random.permutation` call with `np.random.Generator`
|
||||
NPY002.py:32:1: NPY002 Replace legacy `np.random.permutation` call with `np.random.Generator`
|
||||
|
|
||||
27 | numpy.random.bytes()
|
||||
28 | numpy.random.shuffle()
|
||||
29 | numpy.random.permutation()
|
||||
30 | numpy.random.bytes()
|
||||
31 | numpy.random.shuffle()
|
||||
32 | numpy.random.permutation()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
30 | numpy.random.beta()
|
||||
31 | numpy.random.binomial()
|
||||
33 | numpy.random.beta()
|
||||
34 | numpy.random.binomial()
|
||||
|
|
||||
|
||||
NPY002.py:30:1: NPY002 Replace legacy `np.random.beta` call with `np.random.Generator`
|
||||
NPY002.py:33:1: NPY002 Replace legacy `np.random.beta` call with `np.random.Generator`
|
||||
|
|
||||
28 | numpy.random.shuffle()
|
||||
29 | numpy.random.permutation()
|
||||
30 | numpy.random.beta()
|
||||
31 | numpy.random.shuffle()
|
||||
32 | numpy.random.permutation()
|
||||
33 | numpy.random.beta()
|
||||
| ^^^^^^^^^^^^^^^^^ NPY002
|
||||
31 | numpy.random.binomial()
|
||||
32 | numpy.random.chisquare()
|
||||
34 | numpy.random.binomial()
|
||||
35 | numpy.random.chisquare()
|
||||
|
|
||||
|
||||
NPY002.py:31:1: NPY002 Replace legacy `np.random.binomial` call with `np.random.Generator`
|
||||
NPY002.py:34:1: NPY002 Replace legacy `np.random.binomial` call with `np.random.Generator`
|
||||
|
|
||||
29 | numpy.random.permutation()
|
||||
30 | numpy.random.beta()
|
||||
31 | numpy.random.binomial()
|
||||
32 | numpy.random.permutation()
|
||||
33 | numpy.random.beta()
|
||||
34 | numpy.random.binomial()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
32 | numpy.random.chisquare()
|
||||
33 | numpy.random.dirichlet()
|
||||
35 | numpy.random.chisquare()
|
||||
36 | numpy.random.dirichlet()
|
||||
|
|
||||
|
||||
NPY002.py:32:1: NPY002 Replace legacy `np.random.chisquare` call with `np.random.Generator`
|
||||
NPY002.py:35:1: NPY002 Replace legacy `np.random.chisquare` call with `np.random.Generator`
|
||||
|
|
||||
30 | numpy.random.beta()
|
||||
31 | numpy.random.binomial()
|
||||
32 | numpy.random.chisquare()
|
||||
33 | numpy.random.beta()
|
||||
34 | numpy.random.binomial()
|
||||
35 | numpy.random.chisquare()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
33 | numpy.random.dirichlet()
|
||||
34 | numpy.random.exponential()
|
||||
36 | numpy.random.dirichlet()
|
||||
37 | numpy.random.exponential()
|
||||
|
|
||||
|
||||
NPY002.py:33:1: NPY002 Replace legacy `np.random.dirichlet` call with `np.random.Generator`
|
||||
NPY002.py:36:1: NPY002 Replace legacy `np.random.dirichlet` call with `np.random.Generator`
|
||||
|
|
||||
31 | numpy.random.binomial()
|
||||
32 | numpy.random.chisquare()
|
||||
33 | numpy.random.dirichlet()
|
||||
34 | numpy.random.binomial()
|
||||
35 | numpy.random.chisquare()
|
||||
36 | numpy.random.dirichlet()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
34 | numpy.random.exponential()
|
||||
35 | numpy.random.f()
|
||||
37 | numpy.random.exponential()
|
||||
38 | numpy.random.f()
|
||||
|
|
||||
|
||||
NPY002.py:34:1: NPY002 Replace legacy `np.random.exponential` call with `np.random.Generator`
|
||||
NPY002.py:37:1: NPY002 Replace legacy `np.random.exponential` call with `np.random.Generator`
|
||||
|
|
||||
32 | numpy.random.chisquare()
|
||||
33 | numpy.random.dirichlet()
|
||||
34 | numpy.random.exponential()
|
||||
35 | numpy.random.chisquare()
|
||||
36 | numpy.random.dirichlet()
|
||||
37 | numpy.random.exponential()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
35 | numpy.random.f()
|
||||
36 | numpy.random.gamma()
|
||||
38 | numpy.random.f()
|
||||
39 | numpy.random.gamma()
|
||||
|
|
||||
|
||||
NPY002.py:35:1: NPY002 Replace legacy `np.random.f` call with `np.random.Generator`
|
||||
NPY002.py:38:1: NPY002 Replace legacy `np.random.f` call with `np.random.Generator`
|
||||
|
|
||||
33 | numpy.random.dirichlet()
|
||||
34 | numpy.random.exponential()
|
||||
35 | numpy.random.f()
|
||||
36 | numpy.random.dirichlet()
|
||||
37 | numpy.random.exponential()
|
||||
38 | numpy.random.f()
|
||||
| ^^^^^^^^^^^^^^ NPY002
|
||||
36 | numpy.random.gamma()
|
||||
37 | numpy.random.geometric()
|
||||
39 | numpy.random.gamma()
|
||||
40 | numpy.random.geometric()
|
||||
|
|
||||
|
||||
NPY002.py:36:1: NPY002 Replace legacy `np.random.gamma` call with `np.random.Generator`
|
||||
NPY002.py:39:1: NPY002 Replace legacy `np.random.gamma` call with `np.random.Generator`
|
||||
|
|
||||
34 | numpy.random.exponential()
|
||||
35 | numpy.random.f()
|
||||
36 | numpy.random.gamma()
|
||||
37 | numpy.random.exponential()
|
||||
38 | numpy.random.f()
|
||||
39 | numpy.random.gamma()
|
||||
| ^^^^^^^^^^^^^^^^^^ NPY002
|
||||
37 | numpy.random.geometric()
|
||||
38 | numpy.random.get_state()
|
||||
40 | numpy.random.geometric()
|
||||
41 | numpy.random.gumbel()
|
||||
|
|
||||
|
||||
NPY002.py:37:1: NPY002 Replace legacy `np.random.geometric` call with `np.random.Generator`
|
||||
NPY002.py:40:1: NPY002 Replace legacy `np.random.geometric` call with `np.random.Generator`
|
||||
|
|
||||
35 | numpy.random.f()
|
||||
36 | numpy.random.gamma()
|
||||
37 | numpy.random.geometric()
|
||||
38 | numpy.random.f()
|
||||
39 | numpy.random.gamma()
|
||||
40 | numpy.random.geometric()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
38 | numpy.random.get_state()
|
||||
39 | numpy.random.gumbel()
|
||||
41 | numpy.random.gumbel()
|
||||
42 | numpy.random.hypergeometric()
|
||||
|
|
||||
|
||||
NPY002.py:38:1: NPY002 Replace legacy `np.random.get_state` call with `np.random.Generator`
|
||||
NPY002.py:41:1: NPY002 Replace legacy `np.random.gumbel` call with `np.random.Generator`
|
||||
|
|
||||
36 | numpy.random.gamma()
|
||||
37 | numpy.random.geometric()
|
||||
38 | numpy.random.get_state()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
39 | numpy.random.gumbel()
|
||||
40 | numpy.random.hypergeometric()
|
||||
|
|
||||
|
||||
NPY002.py:39:1: NPY002 Replace legacy `np.random.gumbel` call with `np.random.Generator`
|
||||
|
|
||||
37 | numpy.random.geometric()
|
||||
38 | numpy.random.get_state()
|
||||
39 | numpy.random.gumbel()
|
||||
39 | numpy.random.gamma()
|
||||
40 | numpy.random.geometric()
|
||||
41 | numpy.random.gumbel()
|
||||
| ^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
40 | numpy.random.hypergeometric()
|
||||
41 | numpy.random.laplace()
|
||||
42 | numpy.random.hypergeometric()
|
||||
43 | numpy.random.laplace()
|
||||
|
|
||||
|
||||
NPY002.py:40:1: NPY002 Replace legacy `np.random.hypergeometric` call with `np.random.Generator`
|
||||
NPY002.py:42:1: NPY002 Replace legacy `np.random.hypergeometric` call with `np.random.Generator`
|
||||
|
|
||||
38 | numpy.random.get_state()
|
||||
39 | numpy.random.gumbel()
|
||||
40 | numpy.random.hypergeometric()
|
||||
40 | numpy.random.geometric()
|
||||
41 | numpy.random.gumbel()
|
||||
42 | numpy.random.hypergeometric()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
41 | numpy.random.laplace()
|
||||
42 | numpy.random.logistic()
|
||||
43 | numpy.random.laplace()
|
||||
44 | numpy.random.logistic()
|
||||
|
|
||||
|
||||
NPY002.py:41:1: NPY002 Replace legacy `np.random.laplace` call with `np.random.Generator`
|
||||
NPY002.py:43:1: NPY002 Replace legacy `np.random.laplace` call with `np.random.Generator`
|
||||
|
|
||||
39 | numpy.random.gumbel()
|
||||
40 | numpy.random.hypergeometric()
|
||||
41 | numpy.random.laplace()
|
||||
41 | numpy.random.gumbel()
|
||||
42 | numpy.random.hypergeometric()
|
||||
43 | numpy.random.laplace()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
42 | numpy.random.logistic()
|
||||
43 | numpy.random.lognormal()
|
||||
44 | numpy.random.logistic()
|
||||
45 | numpy.random.lognormal()
|
||||
|
|
||||
|
||||
NPY002.py:42:1: NPY002 Replace legacy `np.random.logistic` call with `np.random.Generator`
|
||||
NPY002.py:44:1: NPY002 Replace legacy `np.random.logistic` call with `np.random.Generator`
|
||||
|
|
||||
40 | numpy.random.hypergeometric()
|
||||
41 | numpy.random.laplace()
|
||||
42 | numpy.random.logistic()
|
||||
42 | numpy.random.hypergeometric()
|
||||
43 | numpy.random.laplace()
|
||||
44 | numpy.random.logistic()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
43 | numpy.random.lognormal()
|
||||
44 | numpy.random.logseries()
|
||||
45 | numpy.random.lognormal()
|
||||
46 | numpy.random.logseries()
|
||||
|
|
||||
|
||||
NPY002.py:43:1: NPY002 Replace legacy `np.random.lognormal` call with `np.random.Generator`
|
||||
NPY002.py:45:1: NPY002 Replace legacy `np.random.lognormal` call with `np.random.Generator`
|
||||
|
|
||||
41 | numpy.random.laplace()
|
||||
42 | numpy.random.logistic()
|
||||
43 | numpy.random.lognormal()
|
||||
43 | numpy.random.laplace()
|
||||
44 | numpy.random.logistic()
|
||||
45 | numpy.random.lognormal()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
44 | numpy.random.logseries()
|
||||
45 | numpy.random.multinomial()
|
||||
46 | numpy.random.logseries()
|
||||
47 | numpy.random.multinomial()
|
||||
|
|
||||
|
||||
NPY002.py:44:1: NPY002 Replace legacy `np.random.logseries` call with `np.random.Generator`
|
||||
NPY002.py:46:1: NPY002 Replace legacy `np.random.logseries` call with `np.random.Generator`
|
||||
|
|
||||
42 | numpy.random.logistic()
|
||||
43 | numpy.random.lognormal()
|
||||
44 | numpy.random.logseries()
|
||||
44 | numpy.random.logistic()
|
||||
45 | numpy.random.lognormal()
|
||||
46 | numpy.random.logseries()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
45 | numpy.random.multinomial()
|
||||
46 | numpy.random.multivariate_normal()
|
||||
47 | numpy.random.multinomial()
|
||||
48 | numpy.random.multivariate_normal()
|
||||
|
|
||||
|
||||
NPY002.py:45:1: NPY002 Replace legacy `np.random.multinomial` call with `np.random.Generator`
|
||||
NPY002.py:47:1: NPY002 Replace legacy `np.random.multinomial` call with `np.random.Generator`
|
||||
|
|
||||
43 | numpy.random.lognormal()
|
||||
44 | numpy.random.logseries()
|
||||
45 | numpy.random.multinomial()
|
||||
45 | numpy.random.lognormal()
|
||||
46 | numpy.random.logseries()
|
||||
47 | numpy.random.multinomial()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
46 | numpy.random.multivariate_normal()
|
||||
47 | numpy.random.negative_binomial()
|
||||
48 | numpy.random.multivariate_normal()
|
||||
49 | numpy.random.negative_binomial()
|
||||
|
|
||||
|
||||
NPY002.py:46:1: NPY002 Replace legacy `np.random.multivariate_normal` call with `np.random.Generator`
|
||||
NPY002.py:48:1: NPY002 Replace legacy `np.random.multivariate_normal` call with `np.random.Generator`
|
||||
|
|
||||
44 | numpy.random.logseries()
|
||||
45 | numpy.random.multinomial()
|
||||
46 | numpy.random.multivariate_normal()
|
||||
46 | numpy.random.logseries()
|
||||
47 | numpy.random.multinomial()
|
||||
48 | numpy.random.multivariate_normal()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
47 | numpy.random.negative_binomial()
|
||||
48 | numpy.random.noncentral_chisquare()
|
||||
49 | numpy.random.negative_binomial()
|
||||
50 | numpy.random.noncentral_chisquare()
|
||||
|
|
||||
|
||||
NPY002.py:47:1: NPY002 Replace legacy `np.random.negative_binomial` call with `np.random.Generator`
|
||||
NPY002.py:49:1: NPY002 Replace legacy `np.random.negative_binomial` call with `np.random.Generator`
|
||||
|
|
||||
45 | numpy.random.multinomial()
|
||||
46 | numpy.random.multivariate_normal()
|
||||
47 | numpy.random.negative_binomial()
|
||||
47 | numpy.random.multinomial()
|
||||
48 | numpy.random.multivariate_normal()
|
||||
49 | numpy.random.negative_binomial()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
48 | numpy.random.noncentral_chisquare()
|
||||
49 | numpy.random.noncentral_f()
|
||||
50 | numpy.random.noncentral_chisquare()
|
||||
51 | numpy.random.noncentral_f()
|
||||
|
|
||||
|
||||
NPY002.py:48:1: NPY002 Replace legacy `np.random.noncentral_chisquare` call with `np.random.Generator`
|
||||
NPY002.py:50:1: NPY002 Replace legacy `np.random.noncentral_chisquare` call with `np.random.Generator`
|
||||
|
|
||||
46 | numpy.random.multivariate_normal()
|
||||
47 | numpy.random.negative_binomial()
|
||||
48 | numpy.random.noncentral_chisquare()
|
||||
48 | numpy.random.multivariate_normal()
|
||||
49 | numpy.random.negative_binomial()
|
||||
50 | numpy.random.noncentral_chisquare()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
49 | numpy.random.noncentral_f()
|
||||
50 | numpy.random.normal()
|
||||
51 | numpy.random.noncentral_f()
|
||||
52 | numpy.random.normal()
|
||||
|
|
||||
|
||||
NPY002.py:49:1: NPY002 Replace legacy `np.random.noncentral_f` call with `np.random.Generator`
|
||||
NPY002.py:51:1: NPY002 Replace legacy `np.random.noncentral_f` call with `np.random.Generator`
|
||||
|
|
||||
47 | numpy.random.negative_binomial()
|
||||
48 | numpy.random.noncentral_chisquare()
|
||||
49 | numpy.random.noncentral_f()
|
||||
49 | numpy.random.negative_binomial()
|
||||
50 | numpy.random.noncentral_chisquare()
|
||||
51 | numpy.random.noncentral_f()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
50 | numpy.random.normal()
|
||||
51 | numpy.random.pareto()
|
||||
52 | numpy.random.normal()
|
||||
53 | numpy.random.pareto()
|
||||
|
|
||||
|
||||
NPY002.py:50:1: NPY002 Replace legacy `np.random.normal` call with `np.random.Generator`
|
||||
NPY002.py:52:1: NPY002 Replace legacy `np.random.normal` call with `np.random.Generator`
|
||||
|
|
||||
48 | numpy.random.noncentral_chisquare()
|
||||
49 | numpy.random.noncentral_f()
|
||||
50 | numpy.random.normal()
|
||||
50 | numpy.random.noncentral_chisquare()
|
||||
51 | numpy.random.noncentral_f()
|
||||
52 | numpy.random.normal()
|
||||
| ^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
51 | numpy.random.pareto()
|
||||
52 | numpy.random.poisson()
|
||||
53 | numpy.random.pareto()
|
||||
54 | numpy.random.poisson()
|
||||
|
|
||||
|
||||
NPY002.py:51:1: NPY002 Replace legacy `np.random.pareto` call with `np.random.Generator`
|
||||
NPY002.py:53:1: NPY002 Replace legacy `np.random.pareto` call with `np.random.Generator`
|
||||
|
|
||||
49 | numpy.random.noncentral_f()
|
||||
50 | numpy.random.normal()
|
||||
51 | numpy.random.pareto()
|
||||
51 | numpy.random.noncentral_f()
|
||||
52 | numpy.random.normal()
|
||||
53 | numpy.random.pareto()
|
||||
| ^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
52 | numpy.random.poisson()
|
||||
53 | numpy.random.power()
|
||||
54 | numpy.random.poisson()
|
||||
55 | numpy.random.power()
|
||||
|
|
||||
|
||||
NPY002.py:52:1: NPY002 Replace legacy `np.random.poisson` call with `np.random.Generator`
|
||||
NPY002.py:54:1: NPY002 Replace legacy `np.random.poisson` call with `np.random.Generator`
|
||||
|
|
||||
50 | numpy.random.normal()
|
||||
51 | numpy.random.pareto()
|
||||
52 | numpy.random.poisson()
|
||||
52 | numpy.random.normal()
|
||||
53 | numpy.random.pareto()
|
||||
54 | numpy.random.poisson()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
53 | numpy.random.power()
|
||||
54 | numpy.random.rayleigh()
|
||||
55 | numpy.random.power()
|
||||
56 | numpy.random.rayleigh()
|
||||
|
|
||||
|
||||
NPY002.py:53:1: NPY002 Replace legacy `np.random.power` call with `np.random.Generator`
|
||||
NPY002.py:55:1: NPY002 Replace legacy `np.random.power` call with `np.random.Generator`
|
||||
|
|
||||
51 | numpy.random.pareto()
|
||||
52 | numpy.random.poisson()
|
||||
53 | numpy.random.power()
|
||||
53 | numpy.random.pareto()
|
||||
54 | numpy.random.poisson()
|
||||
55 | numpy.random.power()
|
||||
| ^^^^^^^^^^^^^^^^^^ NPY002
|
||||
54 | numpy.random.rayleigh()
|
||||
55 | numpy.random.standard_cauchy()
|
||||
56 | numpy.random.rayleigh()
|
||||
57 | numpy.random.standard_cauchy()
|
||||
|
|
||||
|
||||
NPY002.py:54:1: NPY002 Replace legacy `np.random.rayleigh` call with `np.random.Generator`
|
||||
NPY002.py:56:1: NPY002 Replace legacy `np.random.rayleigh` call with `np.random.Generator`
|
||||
|
|
||||
52 | numpy.random.poisson()
|
||||
53 | numpy.random.power()
|
||||
54 | numpy.random.rayleigh()
|
||||
54 | numpy.random.poisson()
|
||||
55 | numpy.random.power()
|
||||
56 | numpy.random.rayleigh()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
55 | numpy.random.standard_cauchy()
|
||||
56 | numpy.random.standard_exponential()
|
||||
57 | numpy.random.standard_cauchy()
|
||||
58 | numpy.random.standard_exponential()
|
||||
|
|
||||
|
||||
NPY002.py:55:1: NPY002 Replace legacy `np.random.standard_cauchy` call with `np.random.Generator`
|
||||
NPY002.py:57:1: NPY002 Replace legacy `np.random.standard_cauchy` call with `np.random.Generator`
|
||||
|
|
||||
53 | numpy.random.power()
|
||||
54 | numpy.random.rayleigh()
|
||||
55 | numpy.random.standard_cauchy()
|
||||
55 | numpy.random.power()
|
||||
56 | numpy.random.rayleigh()
|
||||
57 | numpy.random.standard_cauchy()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
56 | numpy.random.standard_exponential()
|
||||
57 | numpy.random.standard_gamma()
|
||||
58 | numpy.random.standard_exponential()
|
||||
59 | numpy.random.standard_gamma()
|
||||
|
|
||||
|
||||
NPY002.py:56:1: NPY002 Replace legacy `np.random.standard_exponential` call with `np.random.Generator`
|
||||
NPY002.py:58:1: NPY002 Replace legacy `np.random.standard_exponential` call with `np.random.Generator`
|
||||
|
|
||||
54 | numpy.random.rayleigh()
|
||||
55 | numpy.random.standard_cauchy()
|
||||
56 | numpy.random.standard_exponential()
|
||||
56 | numpy.random.rayleigh()
|
||||
57 | numpy.random.standard_cauchy()
|
||||
58 | numpy.random.standard_exponential()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
57 | numpy.random.standard_gamma()
|
||||
58 | numpy.random.standard_normal()
|
||||
59 | numpy.random.standard_gamma()
|
||||
60 | numpy.random.standard_normal()
|
||||
|
|
||||
|
||||
NPY002.py:57:1: NPY002 Replace legacy `np.random.standard_gamma` call with `np.random.Generator`
|
||||
NPY002.py:59:1: NPY002 Replace legacy `np.random.standard_gamma` call with `np.random.Generator`
|
||||
|
|
||||
55 | numpy.random.standard_cauchy()
|
||||
56 | numpy.random.standard_exponential()
|
||||
57 | numpy.random.standard_gamma()
|
||||
57 | numpy.random.standard_cauchy()
|
||||
58 | numpy.random.standard_exponential()
|
||||
59 | numpy.random.standard_gamma()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
58 | numpy.random.standard_normal()
|
||||
59 | numpy.random.standard_t()
|
||||
60 | numpy.random.standard_normal()
|
||||
61 | numpy.random.standard_t()
|
||||
|
|
||||
|
||||
NPY002.py:58:1: NPY002 Replace legacy `np.random.standard_normal` call with `np.random.Generator`
|
||||
NPY002.py:60:1: NPY002 Replace legacy `np.random.standard_normal` call with `np.random.Generator`
|
||||
|
|
||||
56 | numpy.random.standard_exponential()
|
||||
57 | numpy.random.standard_gamma()
|
||||
58 | numpy.random.standard_normal()
|
||||
58 | numpy.random.standard_exponential()
|
||||
59 | numpy.random.standard_gamma()
|
||||
60 | numpy.random.standard_normal()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
59 | numpy.random.standard_t()
|
||||
60 | numpy.random.triangular()
|
||||
61 | numpy.random.standard_t()
|
||||
62 | numpy.random.triangular()
|
||||
|
|
||||
|
||||
NPY002.py:59:1: NPY002 Replace legacy `np.random.standard_t` call with `np.random.Generator`
|
||||
NPY002.py:61:1: NPY002 Replace legacy `np.random.standard_t` call with `np.random.Generator`
|
||||
|
|
||||
57 | numpy.random.standard_gamma()
|
||||
58 | numpy.random.standard_normal()
|
||||
59 | numpy.random.standard_t()
|
||||
59 | numpy.random.standard_gamma()
|
||||
60 | numpy.random.standard_normal()
|
||||
61 | numpy.random.standard_t()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
60 | numpy.random.triangular()
|
||||
61 | numpy.random.uniform()
|
||||
62 | numpy.random.triangular()
|
||||
63 | numpy.random.uniform()
|
||||
|
|
||||
|
||||
NPY002.py:60:1: NPY002 Replace legacy `np.random.triangular` call with `np.random.Generator`
|
||||
NPY002.py:62:1: NPY002 Replace legacy `np.random.triangular` call with `np.random.Generator`
|
||||
|
|
||||
58 | numpy.random.standard_normal()
|
||||
59 | numpy.random.standard_t()
|
||||
60 | numpy.random.triangular()
|
||||
60 | numpy.random.standard_normal()
|
||||
61 | numpy.random.standard_t()
|
||||
62 | numpy.random.triangular()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
61 | numpy.random.uniform()
|
||||
62 | numpy.random.vonmises()
|
||||
63 | numpy.random.uniform()
|
||||
64 | numpy.random.vonmises()
|
||||
|
|
||||
|
||||
NPY002.py:61:1: NPY002 Replace legacy `np.random.uniform` call with `np.random.Generator`
|
||||
NPY002.py:63:1: NPY002 Replace legacy `np.random.uniform` call with `np.random.Generator`
|
||||
|
|
||||
59 | numpy.random.standard_t()
|
||||
60 | numpy.random.triangular()
|
||||
61 | numpy.random.uniform()
|
||||
61 | numpy.random.standard_t()
|
||||
62 | numpy.random.triangular()
|
||||
63 | numpy.random.uniform()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
62 | numpy.random.vonmises()
|
||||
63 | numpy.random.wald()
|
||||
64 | numpy.random.vonmises()
|
||||
65 | numpy.random.wald()
|
||||
|
|
||||
|
||||
NPY002.py:62:1: NPY002 Replace legacy `np.random.vonmises` call with `np.random.Generator`
|
||||
NPY002.py:64:1: NPY002 Replace legacy `np.random.vonmises` call with `np.random.Generator`
|
||||
|
|
||||
60 | numpy.random.triangular()
|
||||
61 | numpy.random.uniform()
|
||||
62 | numpy.random.vonmises()
|
||||
62 | numpy.random.triangular()
|
||||
63 | numpy.random.uniform()
|
||||
64 | numpy.random.vonmises()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
63 | numpy.random.wald()
|
||||
64 | numpy.random.weibull()
|
||||
65 | numpy.random.wald()
|
||||
66 | numpy.random.weibull()
|
||||
|
|
||||
|
||||
NPY002.py:63:1: NPY002 Replace legacy `np.random.wald` call with `np.random.Generator`
|
||||
NPY002.py:65:1: NPY002 Replace legacy `np.random.wald` call with `np.random.Generator`
|
||||
|
|
||||
61 | numpy.random.uniform()
|
||||
62 | numpy.random.vonmises()
|
||||
63 | numpy.random.wald()
|
||||
63 | numpy.random.uniform()
|
||||
64 | numpy.random.vonmises()
|
||||
65 | numpy.random.wald()
|
||||
| ^^^^^^^^^^^^^^^^^ NPY002
|
||||
64 | numpy.random.weibull()
|
||||
65 | numpy.random.zipf()
|
||||
66 | numpy.random.weibull()
|
||||
67 | numpy.random.zipf()
|
||||
|
|
||||
|
||||
NPY002.py:64:1: NPY002 Replace legacy `np.random.weibull` call with `np.random.Generator`
|
||||
NPY002.py:66:1: NPY002 Replace legacy `np.random.weibull` call with `np.random.Generator`
|
||||
|
|
||||
62 | numpy.random.vonmises()
|
||||
63 | numpy.random.wald()
|
||||
64 | numpy.random.weibull()
|
||||
64 | numpy.random.vonmises()
|
||||
65 | numpy.random.wald()
|
||||
66 | numpy.random.weibull()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ NPY002
|
||||
65 | numpy.random.zipf()
|
||||
67 | numpy.random.zipf()
|
||||
|
|
||||
|
||||
NPY002.py:65:1: NPY002 Replace legacy `np.random.zipf` call with `np.random.Generator`
|
||||
NPY002.py:67:1: NPY002 Replace legacy `np.random.zipf` call with `np.random.Generator`
|
||||
|
|
||||
63 | numpy.random.wald()
|
||||
64 | numpy.random.weibull()
|
||||
65 | numpy.random.zipf()
|
||||
65 | numpy.random.wald()
|
||||
66 | numpy.random.weibull()
|
||||
67 | numpy.random.zipf()
|
||||
| ^^^^^^^^^^^^^^^^^ NPY002
|
||||
|
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
|
||||
return;
|
||||
}
|
||||
|
||||
let [arg] = args.as_slice() else {
|
||||
let [arg] = &**args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stm
|
||||
return;
|
||||
}
|
||||
|
||||
let [arg] = args.as_slice() else {
|
||||
let [arg] = &**args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr, body: &[
|
||||
return;
|
||||
};
|
||||
|
||||
let [arg] = args.as_slice() else {
|
||||
let [arg] = &**args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -136,6 +136,13 @@ mod tests {
|
||||
Path::new("E25.py")
|
||||
)]
|
||||
#[test_case(Rule::MissingWhitespaceAroundParameterEquals, Path::new("E25.py"))]
|
||||
#[test_case(Rule::BlankLineBetweenMethods, Path::new("E30.py"))]
|
||||
#[test_case(Rule::BlankLinesTopLevel, Path::new("E30.py"))]
|
||||
#[test_case(Rule::TooManyBlankLines, Path::new("E30.py"))]
|
||||
#[test_case(Rule::BlankLineAfterDecorator, Path::new("E30.py"))]
|
||||
#[test_case(Rule::BlankLinesAfterFunctionOrClass, Path::new("E30.py"))]
|
||||
#[test_case(Rule::BlankLinesBeforeNestedDefinition, Path::new("E30.py"))]
|
||||
|
||||
fn logical(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
895
crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs
Normal file
895
crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs
Normal file
@@ -0,0 +1,895 @@
|
||||
use itertools::Itertools;
|
||||
use std::cmp::Ordering;
|
||||
use std::num::NonZeroU32;
|
||||
use std::slice::Iter;
|
||||
|
||||
use ruff_diagnostics::AlwaysFixableViolation;
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::lexer::LexicalError;
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_python_parser::TokenKind;
|
||||
use ruff_source_file::{Locator, UniversalNewlines};
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use crate::checkers::logical_lines::expand_indent;
|
||||
use crate::line_width::IndentWidth;
|
||||
use ruff_python_trivia::PythonWhitespace;
|
||||
|
||||
/// Number of blank lines around top level classes and functions.
|
||||
const BLANK_LINES_TOP_LEVEL: u32 = 2;
|
||||
/// Number of blank lines around methods and nested classes and functions.
|
||||
const BLANK_LINES_METHOD_LEVEL: u32 = 1;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for missing blank lines between methods of a class.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// PEP 8 recommends exactly one blank line between methods of a class.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class MyClass(object):
|
||||
/// def func1():
|
||||
/// pass
|
||||
/// def func2():
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class MyClass(object):
|
||||
/// def func1():
|
||||
/// pass
|
||||
///
|
||||
/// def func2():
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E301.html)
|
||||
#[violation]
|
||||
pub struct BlankLineBetweenMethods;
|
||||
|
||||
impl AlwaysFixableViolation for BlankLineBetweenMethods {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Expected {BLANK_LINES_METHOD_LEVEL:?} blank line, found 0")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Add missing blank line".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for missing blank lines between top level functions and classes.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// PEP 8 recommends exactly two blank lines between top level functions and classes.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def func1():
|
||||
/// pass
|
||||
/// def func2():
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def func1():
|
||||
/// pass
|
||||
///
|
||||
///
|
||||
/// def func2():
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E302.html)
|
||||
#[violation]
|
||||
pub struct BlankLinesTopLevel {
|
||||
actual_blank_lines: u32,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for BlankLinesTopLevel {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let BlankLinesTopLevel {
|
||||
actual_blank_lines: nb_blank_lines,
|
||||
} = self;
|
||||
|
||||
format!("Expected {BLANK_LINES_TOP_LEVEL:?} blank lines, found {nb_blank_lines}")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Add missing blank line(s)".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for extraneous blank lines.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// PEP 8 recommends using blank lines as follows:
|
||||
/// - No more than two blank lines between top-level statements.
|
||||
/// - No more than one blank line between non-top-level statements.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def func1():
|
||||
/// pass
|
||||
///
|
||||
///
|
||||
///
|
||||
/// def func2():
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def func1():
|
||||
/// pass
|
||||
///
|
||||
///
|
||||
/// def func2():
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E303.html)
|
||||
#[violation]
|
||||
pub struct TooManyBlankLines {
|
||||
actual_blank_lines: u32,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for TooManyBlankLines {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let TooManyBlankLines {
|
||||
actual_blank_lines: nb_blank_lines,
|
||||
} = self;
|
||||
format!("Too many blank lines ({nb_blank_lines})")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove extraneous blank line(s)".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for extraneous blank line(s) after function decorators.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// There should be no blank lines between a decorator and the object it is decorating.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class User(object):
|
||||
///
|
||||
/// @property
|
||||
///
|
||||
/// def name(self):
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class User(object):
|
||||
///
|
||||
/// @property
|
||||
/// def name(self):
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E304.html)
|
||||
#[violation]
|
||||
pub struct BlankLineAfterDecorator {
|
||||
actual_blank_lines: u32,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for BlankLineAfterDecorator {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Blank lines found after function decorator ({lines})",
|
||||
lines = self.actual_blank_lines
|
||||
)
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove extraneous blank line(s)".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for missing blank lines after the end of function or class.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// PEP 8 recommends using blank lines as following:
|
||||
/// - Two blank lines are expected between functions and classes
|
||||
/// - One blank line is expected between methods of a class.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class User(object):
|
||||
/// pass
|
||||
/// user = User()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class User(object):
|
||||
/// pass
|
||||
///
|
||||
///
|
||||
/// user = User()
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E305.html)
|
||||
#[violation]
|
||||
pub struct BlankLinesAfterFunctionOrClass {
|
||||
actual_blank_lines: u32,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for BlankLinesAfterFunctionOrClass {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let BlankLinesAfterFunctionOrClass {
|
||||
actual_blank_lines: blank_lines,
|
||||
} = self;
|
||||
format!("Expected 2 blank lines after class or function definition, found ({blank_lines})")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Add missing blank line(s)".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for 1 blank line between nested function or class definitions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// PEP 8 recommends using blank lines as following:
|
||||
/// - Two blank lines are expected between functions and classes
|
||||
/// - One blank line is expected between methods of a class.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def outer():
|
||||
/// def inner():
|
||||
/// pass
|
||||
/// def inner2():
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def outer():
|
||||
/// def inner():
|
||||
/// pass
|
||||
///
|
||||
/// def inner2():
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E306.html)
|
||||
#[violation]
|
||||
pub struct BlankLinesBeforeNestedDefinition;
|
||||
|
||||
impl AlwaysFixableViolation for BlankLinesBeforeNestedDefinition {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Expected 1 blank line before a nested definition, found 0")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Add missing blank line".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LogicalLineInfo {
|
||||
kind: LogicalLineKind,
|
||||
first_token_range: TextRange,
|
||||
|
||||
// The token's kind right before the newline ending the logical line.
|
||||
last_token: TokenKind,
|
||||
|
||||
// The end of the logical line including the newline.
|
||||
logical_line_end: TextSize,
|
||||
|
||||
// `true` if this is not a blank but only consists of a comment.
|
||||
is_comment_only: bool,
|
||||
|
||||
/// `true` if the line is a string only (including trivia tokens) line, which is a docstring if coming right after a class/function definition.
|
||||
is_docstring: bool,
|
||||
|
||||
/// The indentation length in columns. See [`expand_indent`] for the computation of the indent.
|
||||
indent_length: usize,
|
||||
|
||||
/// The number of blank lines preceding the current line.
|
||||
blank_lines: BlankLines,
|
||||
|
||||
/// The maximum number of consecutive blank lines between the current line
|
||||
/// and the previous non-comment logical line.
|
||||
/// One of its main uses is to allow a comments to directly precede or follow a class/function definition.
|
||||
/// As such, `preceding_blank_lines` is used for rules that cannot trigger on comments (all rules except E303),
|
||||
/// and `blank_lines` is used for the rule that can trigger on comments (E303).
|
||||
preceding_blank_lines: BlankLines,
|
||||
}
|
||||
|
||||
/// Iterator that processes tokens until a full logical line (or comment line) is "built".
|
||||
/// It then returns characteristics of that logical line (see `LogicalLineInfo`).
|
||||
struct LinePreprocessor<'a> {
|
||||
tokens: Iter<'a, Result<(Tok, TextRange), LexicalError>>,
|
||||
locator: &'a Locator<'a>,
|
||||
indent_width: IndentWidth,
|
||||
/// The start position of the next logical line.
|
||||
line_start: TextSize,
|
||||
/// Maximum number of consecutive blank lines between the current line and the previous non-comment logical line.
|
||||
/// One of its main uses is to allow a comment to directly precede a class/function definition.
|
||||
max_preceding_blank_lines: BlankLines,
|
||||
}
|
||||
|
||||
impl<'a> LinePreprocessor<'a> {
|
||||
fn new(
|
||||
tokens: &'a [LexResult],
|
||||
locator: &'a Locator,
|
||||
indent_width: IndentWidth,
|
||||
) -> LinePreprocessor<'a> {
|
||||
LinePreprocessor {
|
||||
tokens: tokens.iter(),
|
||||
locator,
|
||||
line_start: TextSize::new(0),
|
||||
max_preceding_blank_lines: BlankLines::Zero,
|
||||
indent_width,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for LinePreprocessor<'a> {
|
||||
type Item = LogicalLineInfo;
|
||||
|
||||
fn next(&mut self) -> Option<LogicalLineInfo> {
|
||||
let mut line_is_comment_only = true;
|
||||
let mut is_docstring = false;
|
||||
// Number of consecutive blank lines directly preceding this logical line.
|
||||
let mut blank_lines = BlankLines::Zero;
|
||||
let mut first_logical_line_token: Option<(LogicalLineKind, TextRange)> = None;
|
||||
let mut last_token: TokenKind = TokenKind::EndOfFile;
|
||||
let mut parens = 0u32;
|
||||
|
||||
while let Some(result) = self.tokens.next() {
|
||||
let Ok((token, range)) = result else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if matches!(token, Tok::Indent | Tok::Dedent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let token_kind = TokenKind::from_token(token);
|
||||
|
||||
let (logical_line_kind, first_token_range) = if let Some(first_token_range) =
|
||||
first_logical_line_token
|
||||
{
|
||||
first_token_range
|
||||
}
|
||||
// At the start of the line...
|
||||
else {
|
||||
// An empty line
|
||||
if token_kind == TokenKind::NonLogicalNewline {
|
||||
blank_lines.add(*range);
|
||||
|
||||
self.line_start = range.end();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
is_docstring = token_kind == TokenKind::String;
|
||||
|
||||
let logical_line_kind = match token_kind {
|
||||
TokenKind::Class => LogicalLineKind::Class,
|
||||
TokenKind::Comment => LogicalLineKind::Comment,
|
||||
TokenKind::At => LogicalLineKind::Decorator,
|
||||
TokenKind::Def => LogicalLineKind::Function,
|
||||
// Lookahead to distinguish `async def` from `async with`.
|
||||
TokenKind::Async
|
||||
if matches!(self.tokens.as_slice().first(), Some(Ok((Tok::Def, _)))) =>
|
||||
{
|
||||
LogicalLineKind::Function
|
||||
}
|
||||
_ => LogicalLineKind::Other,
|
||||
};
|
||||
|
||||
first_logical_line_token = Some((logical_line_kind, *range));
|
||||
|
||||
(logical_line_kind, *range)
|
||||
};
|
||||
|
||||
if !token_kind.is_trivia() {
|
||||
line_is_comment_only = false;
|
||||
}
|
||||
|
||||
// A docstring line is composed only of the docstring (TokenKind::String) and trivia tokens.
|
||||
// (If a comment follows a docstring, we still count the line as a docstring)
|
||||
if token_kind != TokenKind::String && !token_kind.is_trivia() {
|
||||
is_docstring = false;
|
||||
}
|
||||
|
||||
match token_kind {
|
||||
TokenKind::Lbrace | TokenKind::Lpar | TokenKind::Lsqb => {
|
||||
parens = parens.saturating_add(1);
|
||||
}
|
||||
TokenKind::Rbrace | TokenKind::Rpar | TokenKind::Rsqb => {
|
||||
parens = parens.saturating_sub(1);
|
||||
}
|
||||
TokenKind::Newline | TokenKind::NonLogicalNewline if parens == 0 => {
|
||||
let indent_range = TextRange::new(self.line_start, first_token_range.start());
|
||||
|
||||
let indent_length =
|
||||
expand_indent(self.locator.slice(indent_range), self.indent_width);
|
||||
|
||||
self.max_preceding_blank_lines =
|
||||
self.max_preceding_blank_lines.max(blank_lines);
|
||||
|
||||
let logical_line = LogicalLineInfo {
|
||||
kind: logical_line_kind,
|
||||
first_token_range,
|
||||
last_token,
|
||||
logical_line_end: range.end(),
|
||||
is_comment_only: line_is_comment_only,
|
||||
is_docstring,
|
||||
indent_length,
|
||||
blank_lines,
|
||||
preceding_blank_lines: self.max_preceding_blank_lines,
|
||||
};
|
||||
|
||||
// Reset the blank lines after a non-comment only line.
|
||||
if !line_is_comment_only {
|
||||
self.max_preceding_blank_lines = BlankLines::Zero;
|
||||
}
|
||||
|
||||
// Set the start for the next logical line.
|
||||
self.line_start = range.end();
|
||||
|
||||
return Some(logical_line);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
last_token = token_kind;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
enum BlankLines {
|
||||
/// No blank lines
|
||||
#[default]
|
||||
Zero,
|
||||
|
||||
/// One or more blank lines
|
||||
Many { count: NonZeroU32, range: TextRange },
|
||||
}
|
||||
|
||||
impl BlankLines {
|
||||
fn add(&mut self, line_range: TextRange) {
|
||||
match self {
|
||||
BlankLines::Zero => {
|
||||
*self = BlankLines::Many {
|
||||
count: NonZeroU32::MIN,
|
||||
range: line_range,
|
||||
}
|
||||
}
|
||||
BlankLines::Many { count, range } => {
|
||||
*count = count.saturating_add(1);
|
||||
*range = TextRange::new(range.start(), line_range.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
match self {
|
||||
BlankLines::Zero => 0,
|
||||
BlankLines::Many { count, .. } => count.get(),
|
||||
}
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
match self {
|
||||
BlankLines::Zero => None,
|
||||
BlankLines::Many { range, .. } => Some(*range),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u32> for BlankLines {
|
||||
fn eq(&self, other: &u32) -> bool {
|
||||
self.partial_cmp(other) == Some(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<u32> for BlankLines {
|
||||
fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
|
||||
self.count().partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for BlankLines {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for BlankLines {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.count().cmp(&other.count())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for BlankLines {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.count() == other.count()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for BlankLines {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
enum Follows {
|
||||
#[default]
|
||||
Other,
|
||||
Decorator,
|
||||
Def,
|
||||
Docstring,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
enum Status {
|
||||
/// Stores the indent level where the nesting started.
|
||||
Inside(usize),
|
||||
/// This is used to rectify a Inside switched to a Outside because of a dedented comment.
|
||||
CommentAfter(usize),
|
||||
#[default]
|
||||
Outside,
|
||||
}
|
||||
|
||||
impl Status {
|
||||
fn update(&mut self, line: &LogicalLineInfo) {
|
||||
match *self {
|
||||
Status::Inside(nesting_indent) => {
|
||||
if line.indent_length <= nesting_indent {
|
||||
if line.is_comment_only {
|
||||
*self = Status::CommentAfter(nesting_indent);
|
||||
} else {
|
||||
*self = Status::Outside;
|
||||
}
|
||||
}
|
||||
}
|
||||
Status::CommentAfter(indent) => {
|
||||
if !line.is_comment_only {
|
||||
if line.indent_length > indent {
|
||||
*self = Status::Inside(indent);
|
||||
} else {
|
||||
*self = Status::Outside;
|
||||
}
|
||||
}
|
||||
}
|
||||
Status::Outside => {
|
||||
// Nothing to do
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains variables used for the linting of blank lines.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct BlankLinesChecker {
|
||||
follows: Follows,
|
||||
fn_status: Status,
|
||||
class_status: Status,
|
||||
/// First line that is not a comment.
|
||||
is_not_first_logical_line: bool,
|
||||
/// Used for the fix in case a comment separates two non-comment logical lines to make the comment "stick"
|
||||
/// to the second line instead of the first.
|
||||
last_non_comment_line_end: TextSize,
|
||||
previous_unindented_line_kind: Option<LogicalLineKind>,
|
||||
}
|
||||
|
||||
impl BlankLinesChecker {
|
||||
/// E301, E302, E303, E304, E305, E306
|
||||
pub(crate) fn check_lines(
|
||||
&mut self,
|
||||
tokens: &[LexResult],
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
indent_width: IndentWidth,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
let mut prev_indent_length: Option<usize> = None;
|
||||
let line_preprocessor = LinePreprocessor::new(tokens, locator, indent_width);
|
||||
|
||||
for logical_line in line_preprocessor {
|
||||
self.check_line(
|
||||
&logical_line,
|
||||
prev_indent_length,
|
||||
locator,
|
||||
stylist,
|
||||
diagnostics,
|
||||
);
|
||||
if !logical_line.is_comment_only {
|
||||
prev_indent_length = Some(logical_line.indent_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
fn check_line(
|
||||
&mut self,
|
||||
line: &LogicalLineInfo,
|
||||
prev_indent_length: Option<usize>,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
self.class_status.update(line);
|
||||
self.fn_status.update(line);
|
||||
|
||||
// Don't expect blank lines before the first non comment line.
|
||||
if self.is_not_first_logical_line {
|
||||
if line.preceding_blank_lines == 0
|
||||
// Only applies to methods.
|
||||
&& matches!(line.kind, LogicalLineKind::Function)
|
||||
&& matches!(self.class_status, Status::Inside(_))
|
||||
// The class/parent method's docstring can directly precede the def.
|
||||
// Allow following a decorator (if there is an error it will be triggered on the first decorator).
|
||||
&& !matches!(self.follows, Follows::Docstring | Follows::Decorator)
|
||||
// Do not trigger when the def follows an if/while/etc...
|
||||
&& prev_indent_length.is_some_and(|prev_indent_length| prev_indent_length >= line.indent_length)
|
||||
{
|
||||
// E301
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(BlankLineBetweenMethods, line.first_token_range);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
|
||||
stylist.line_ending().to_string(),
|
||||
locator.line_start(self.last_non_comment_line_end),
|
||||
)));
|
||||
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
if line.preceding_blank_lines < BLANK_LINES_TOP_LEVEL
|
||||
// Allow following a decorator (if there is an error it will be triggered on the first decorator).
|
||||
&& !matches!(self.follows, Follows::Decorator)
|
||||
// Allow groups of one-liners.
|
||||
&& !(matches!(self.follows, Follows::Def) && !matches!(line.last_token, TokenKind::Colon))
|
||||
// Only trigger on non-indented classes and functions (for example functions within an if are ignored)
|
||||
&& line.indent_length == 0
|
||||
// Only apply to functions or classes.
|
||||
&& line.kind.is_top_level()
|
||||
{
|
||||
// E302
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
BlankLinesTopLevel {
|
||||
actual_blank_lines: line.preceding_blank_lines.count(),
|
||||
},
|
||||
line.first_token_range,
|
||||
);
|
||||
|
||||
if let Some(blank_lines_range) = line.blank_lines.range() {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
stylist.line_ending().repeat(BLANK_LINES_TOP_LEVEL as usize),
|
||||
blank_lines_range,
|
||||
)));
|
||||
} else {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
|
||||
stylist.line_ending().repeat(BLANK_LINES_TOP_LEVEL as usize),
|
||||
locator.line_start(self.last_non_comment_line_end),
|
||||
)));
|
||||
}
|
||||
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
let expected_blank_lines = if line.indent_length > 0 {
|
||||
BLANK_LINES_METHOD_LEVEL
|
||||
} else {
|
||||
BLANK_LINES_TOP_LEVEL
|
||||
};
|
||||
|
||||
if line.blank_lines > expected_blank_lines {
|
||||
// E303
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
TooManyBlankLines {
|
||||
actual_blank_lines: line.blank_lines.count(),
|
||||
},
|
||||
line.first_token_range,
|
||||
);
|
||||
|
||||
if let Some(blank_lines_range) = line.blank_lines.range() {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
stylist.line_ending().repeat(expected_blank_lines as usize),
|
||||
blank_lines_range,
|
||||
)));
|
||||
}
|
||||
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
if matches!(self.follows, Follows::Decorator)
|
||||
&& !line.is_comment_only
|
||||
&& line.preceding_blank_lines > 0
|
||||
{
|
||||
// E304
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
BlankLineAfterDecorator {
|
||||
actual_blank_lines: line.preceding_blank_lines.count(),
|
||||
},
|
||||
line.first_token_range,
|
||||
);
|
||||
|
||||
// Get all the lines between the last decorator line (included) and the current line (included).
|
||||
// Then remove all blank lines.
|
||||
let trivia_range = TextRange::new(
|
||||
self.last_non_comment_line_end,
|
||||
locator.line_start(line.first_token_range.start()),
|
||||
);
|
||||
let trivia_text = locator.slice(trivia_range);
|
||||
let mut trivia_without_blank_lines = trivia_text
|
||||
.universal_newlines()
|
||||
.filter_map(|line| {
|
||||
(!line.trim_whitespace().is_empty()).then_some(line.as_str())
|
||||
})
|
||||
.join(&stylist.line_ending());
|
||||
|
||||
let fix = if trivia_without_blank_lines.is_empty() {
|
||||
Fix::safe_edit(Edit::range_deletion(trivia_range))
|
||||
} else {
|
||||
trivia_without_blank_lines.push_str(&stylist.line_ending());
|
||||
Fix::safe_edit(Edit::range_replacement(
|
||||
trivia_without_blank_lines,
|
||||
trivia_range,
|
||||
))
|
||||
};
|
||||
|
||||
diagnostic.set_fix(fix);
|
||||
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
if line.preceding_blank_lines < BLANK_LINES_TOP_LEVEL
|
||||
&& self
|
||||
.previous_unindented_line_kind
|
||||
.is_some_and(LogicalLineKind::is_top_level)
|
||||
&& line.indent_length == 0
|
||||
&& !line.is_comment_only
|
||||
&& !line.kind.is_top_level()
|
||||
{
|
||||
// E305
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
BlankLinesAfterFunctionOrClass {
|
||||
actual_blank_lines: line.preceding_blank_lines.count(),
|
||||
},
|
||||
line.first_token_range,
|
||||
);
|
||||
|
||||
if let Some(blank_lines_range) = line.blank_lines.range() {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
stylist.line_ending().repeat(BLANK_LINES_TOP_LEVEL as usize),
|
||||
blank_lines_range,
|
||||
)));
|
||||
} else {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
|
||||
stylist.line_ending().repeat(BLANK_LINES_TOP_LEVEL as usize),
|
||||
locator.line_start(line.first_token_range.start()),
|
||||
)));
|
||||
}
|
||||
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
if line.preceding_blank_lines == 0
|
||||
// Only apply to nested functions.
|
||||
&& matches!(self.fn_status, Status::Inside(_))
|
||||
&& line.kind.is_top_level()
|
||||
// Allow following a decorator (if there is an error it will be triggered on the first decorator).
|
||||
&& !matches!(self.follows, Follows::Decorator)
|
||||
// The class's docstring can directly precede the first function.
|
||||
&& !matches!(self.follows, Follows::Docstring)
|
||||
// Do not trigger when the def/class follows an "indenting token" (if/while/etc...).
|
||||
&& prev_indent_length.is_some_and(|prev_indent_length| prev_indent_length >= line.indent_length)
|
||||
// Allow groups of one-liners.
|
||||
&& !(matches!(self.follows, Follows::Def) && line.last_token != TokenKind::Colon)
|
||||
{
|
||||
// E306
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(BlankLinesBeforeNestedDefinition, line.first_token_range);
|
||||
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
|
||||
stylist.line_ending().to_string(),
|
||||
locator.line_start(line.first_token_range.start()),
|
||||
)));
|
||||
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
match line.kind {
|
||||
LogicalLineKind::Class => {
|
||||
if matches!(self.class_status, Status::Outside) {
|
||||
self.class_status = Status::Inside(line.indent_length);
|
||||
}
|
||||
self.follows = Follows::Other;
|
||||
}
|
||||
LogicalLineKind::Decorator => {
|
||||
self.follows = Follows::Decorator;
|
||||
}
|
||||
LogicalLineKind::Function => {
|
||||
if matches!(self.fn_status, Status::Outside) {
|
||||
self.fn_status = Status::Inside(line.indent_length);
|
||||
}
|
||||
self.follows = Follows::Def;
|
||||
}
|
||||
LogicalLineKind::Comment => {}
|
||||
LogicalLineKind::Other => {
|
||||
self.follows = Follows::Other;
|
||||
}
|
||||
}
|
||||
|
||||
if line.is_docstring {
|
||||
self.follows = Follows::Docstring;
|
||||
}
|
||||
|
||||
if !line.is_comment_only {
|
||||
self.is_not_first_logical_line = true;
|
||||
|
||||
self.last_non_comment_line_end = line.logical_line_end;
|
||||
|
||||
if line.indent_length == 0 {
|
||||
self.previous_unindented_line_kind = Some(line.kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum LogicalLineKind {
|
||||
/// The clause header of a class definition
|
||||
Class,
|
||||
/// A decorator
|
||||
Decorator,
|
||||
/// The clause header of a function
|
||||
Function,
|
||||
/// A comment only line
|
||||
Comment,
|
||||
/// Any other statement or clause header
|
||||
Other,
|
||||
}
|
||||
|
||||
impl LogicalLineKind {
|
||||
fn is_top_level(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
LogicalLineKind::Class | LogicalLineKind::Function | LogicalLineKind::Decorator
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -67,14 +67,14 @@ pub(crate) fn invalid_escape_sequence(
|
||||
token_range: TextRange,
|
||||
) {
|
||||
let (token_source_code, string_start_location) = match token {
|
||||
Tok::FStringMiddle { value, is_raw } => {
|
||||
Tok::FStringMiddle { value, is_raw, .. } => {
|
||||
if *is_raw {
|
||||
return;
|
||||
}
|
||||
let Some(range) = indexer.fstring_ranges().innermost(token_range.start()) else {
|
||||
return;
|
||||
};
|
||||
(value.as_str(), range.start())
|
||||
(&**value, range.start())
|
||||
}
|
||||
Tok::String { kind, .. } => {
|
||||
if kind.is_raw() {
|
||||
|
||||
@@ -139,10 +139,10 @@ pub(crate) fn literal_comparisons(checker: &mut Checker, compare: &ast::ExprComp
|
||||
|
||||
// Check `left`.
|
||||
let mut comparator = compare.left.as_ref();
|
||||
let [op, ..] = compare.ops.as_slice() else {
|
||||
let [op, ..] = &*compare.ops else {
|
||||
return;
|
||||
};
|
||||
let [next, ..] = compare.comparators.as_slice() else {
|
||||
let [next, ..] = &*compare.comparators else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ pub(crate) use ambiguous_class_name::*;
|
||||
pub(crate) use ambiguous_function_name::*;
|
||||
pub(crate) use ambiguous_variable_name::*;
|
||||
pub(crate) use bare_except::*;
|
||||
pub(crate) use blank_lines::*;
|
||||
pub(crate) use compound_statements::*;
|
||||
pub(crate) use doc_line_too_long::*;
|
||||
pub(crate) use errors::*;
|
||||
@@ -23,6 +24,7 @@ mod ambiguous_class_name;
|
||||
mod ambiguous_function_name;
|
||||
mod ambiguous_variable_name;
|
||||
mod bare_except;
|
||||
mod blank_lines;
|
||||
mod compound_statements;
|
||||
mod doc_line_too_long;
|
||||
mod errors;
|
||||
|
||||
@@ -90,7 +90,7 @@ pub(crate) fn not_tests(checker: &mut Checker, unary_op: &ast::ExprUnaryOp) {
|
||||
return;
|
||||
};
|
||||
|
||||
match ops.as_slice() {
|
||||
match &**ops {
|
||||
[CmpOp::In] => {
|
||||
if checker.enabled(Rule::NotInTest) {
|
||||
let mut diagnostic = Diagnostic::new(NotInTest, unary_op.operand.range());
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_python_trivia::leading_indentation;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for indentation that uses tabs.
|
||||
@@ -48,44 +45,52 @@ impl Violation for TabIndentation {
|
||||
/// W191
|
||||
pub(crate) fn tab_indentation(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
tokens: &[LexResult],
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
) {
|
||||
// Always check the first line for tab indentation as there's no newline
|
||||
// token before it.
|
||||
tab_indentation_at_line_start(diagnostics, locator, TextSize::default());
|
||||
let contents = locator.contents().as_bytes();
|
||||
let mut offset = 0;
|
||||
while let Some(index) = memchr::memchr(b'\t', &contents[offset..]) {
|
||||
// If we find a tab in the file, grab the entire line.
|
||||
let range = locator.full_line_range(TextSize::try_from(offset + index).unwrap());
|
||||
|
||||
for (tok, range) in tokens.iter().flatten() {
|
||||
if matches!(tok, Tok::Newline | Tok::NonLogicalNewline) {
|
||||
tab_indentation_at_line_start(diagnostics, locator, range.end());
|
||||
// Determine whether the tab is part of the line's indentation.
|
||||
if let Some(indent) = tab_indentation_at_line_start(range.start(), locator, indexer) {
|
||||
diagnostics.push(Diagnostic::new(TabIndentation, indent));
|
||||
}
|
||||
|
||||
// Advance to the next line.
|
||||
offset = range.end().to_usize();
|
||||
}
|
||||
}
|
||||
|
||||
/// If a line includes tabs in its indentation, returns the range of the
|
||||
/// indent.
|
||||
fn tab_indentation_at_line_start(
|
||||
line_start: TextSize,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
) -> Option<TextRange> {
|
||||
let mut contains_tab = false;
|
||||
for (i, char) in locator.after(line_start).as_bytes().iter().enumerate() {
|
||||
match char {
|
||||
// If we find a tab character, report it as a violation.
|
||||
b'\t' => {
|
||||
contains_tab = true;
|
||||
}
|
||||
// If we find a space, continue.
|
||||
b' ' | b'\x0C' => {}
|
||||
// If we find a non-whitespace character, stop.
|
||||
_ => {
|
||||
if contains_tab {
|
||||
let range = TextRange::at(line_start, TextSize::try_from(i).unwrap());
|
||||
if !indexer.multiline_ranges().contains_range(range) {
|
||||
return Some(range);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The lexer doesn't emit `Newline` / `NonLogicalNewline` for a line
|
||||
// continuation character (`\`), so we need to manually check for tab
|
||||
// indentation for lines that follow a line continuation character.
|
||||
for continuation_line in indexer.continuation_line_starts() {
|
||||
tab_indentation_at_line_start(
|
||||
diagnostics,
|
||||
locator,
|
||||
locator.full_line_end(*continuation_line),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for indentation that uses tabs for a line starting at
|
||||
/// the given [`TextSize`].
|
||||
fn tab_indentation_at_line_start(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
locator: &Locator,
|
||||
line_start: TextSize,
|
||||
) {
|
||||
let indent = leading_indentation(locator.after(line_start));
|
||||
if indent.find('\t').is_some() {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
TabIndentation,
|
||||
TextRange::at(line_start, indent.text_len()),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30.py:453:5: E301 [*] Expected 1 blank line, found 0
|
||||
|
|
||||
451 | def func1():
|
||||
452 | pass
|
||||
453 | def func2():
|
||||
| ^^^ E301
|
||||
454 | pass
|
||||
455 | # end
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
450 450 |
|
||||
451 451 | def func1():
|
||||
452 452 | pass
|
||||
453 |+
|
||||
453 454 | def func2():
|
||||
454 455 | pass
|
||||
455 456 | # end
|
||||
|
||||
E30.py:464:5: E301 [*] Expected 1 blank line, found 0
|
||||
|
|
||||
462 | pass
|
||||
463 | # comment
|
||||
464 | def fn2():
|
||||
| ^^^ E301
|
||||
465 | pass
|
||||
466 | # end
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
460 460 |
|
||||
461 461 | def fn1():
|
||||
462 462 | pass
|
||||
463 |+
|
||||
463 464 | # comment
|
||||
464 465 | def fn2():
|
||||
465 466 | pass
|
||||
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30.py:471:1: E302 [*] Expected 2 blank lines, found 0
|
||||
|
|
||||
469 | # E302
|
||||
470 | """Main module."""
|
||||
471 | def fn():
|
||||
| ^^^ E302
|
||||
472 | pass
|
||||
473 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
468 468 |
|
||||
469 469 | # E302
|
||||
470 470 | """Main module."""
|
||||
471 |+
|
||||
472 |+
|
||||
471 473 | def fn():
|
||||
472 474 | pass
|
||||
473 475 | # end
|
||||
|
||||
E30.py:478:1: E302 [*] Expected 2 blank lines, found 0
|
||||
|
|
||||
476 | # E302
|
||||
477 | import sys
|
||||
478 | def get_sys_path():
|
||||
| ^^^ E302
|
||||
479 | return sys.path
|
||||
480 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
475 475 |
|
||||
476 476 | # E302
|
||||
477 477 | import sys
|
||||
478 |+
|
||||
479 |+
|
||||
478 480 | def get_sys_path():
|
||||
479 481 | return sys.path
|
||||
480 482 | # end
|
||||
|
||||
E30.py:487:1: E302 [*] Expected 2 blank lines, found 1
|
||||
|
|
||||
485 | pass
|
||||
486 |
|
||||
487 | def b():
|
||||
| ^^^ E302
|
||||
488 | pass
|
||||
489 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
484 484 | def a():
|
||||
485 485 | pass
|
||||
486 486 |
|
||||
487 |+
|
||||
487 488 | def b():
|
||||
488 489 | pass
|
||||
489 490 | # end
|
||||
|
||||
E30.py:498:1: E302 [*] Expected 2 blank lines, found 1
|
||||
|
|
||||
496 | # comment
|
||||
497 |
|
||||
498 | def b():
|
||||
| ^^^ E302
|
||||
499 | pass
|
||||
500 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
495 495 |
|
||||
496 496 | # comment
|
||||
497 497 |
|
||||
498 |+
|
||||
498 499 | def b():
|
||||
499 500 | pass
|
||||
500 501 | # end
|
||||
|
||||
E30.py:507:1: E302 [*] Expected 2 blank lines, found 1
|
||||
|
|
||||
505 | pass
|
||||
506 |
|
||||
507 | async def b():
|
||||
| ^^^^^ E302
|
||||
508 | pass
|
||||
509 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
504 504 | def a():
|
||||
505 505 | pass
|
||||
506 506 |
|
||||
507 |+
|
||||
507 508 | async def b():
|
||||
508 509 | pass
|
||||
509 510 | # end
|
||||
|
||||
E30.py:516:1: E302 [*] Expected 2 blank lines, found 1
|
||||
|
|
||||
514 | pass
|
||||
515 |
|
||||
516 | async def x(y: int = 1):
|
||||
| ^^^^^ E302
|
||||
517 | pass
|
||||
518 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
513 513 | async def x():
|
||||
514 514 | pass
|
||||
515 515 |
|
||||
516 |+
|
||||
516 517 | async def x(y: int = 1):
|
||||
517 518 | pass
|
||||
518 519 | # end
|
||||
|
||||
E30.py:524:1: E302 [*] Expected 2 blank lines, found 0
|
||||
|
|
||||
522 | def bar():
|
||||
523 | pass
|
||||
524 | def baz(): pass
|
||||
| ^^^ E302
|
||||
525 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
521 521 | # E302
|
||||
522 522 | def bar():
|
||||
523 523 | pass
|
||||
524 |+
|
||||
525 |+
|
||||
524 526 | def baz(): pass
|
||||
525 527 | # end
|
||||
526 528 |
|
||||
|
||||
E30.py:530:1: E302 [*] Expected 2 blank lines, found 0
|
||||
|
|
||||
528 | # E302
|
||||
529 | def bar(): pass
|
||||
530 | def baz():
|
||||
| ^^^ E302
|
||||
531 | pass
|
||||
532 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
527 527 |
|
||||
528 528 | # E302
|
||||
529 529 | def bar(): pass
|
||||
530 |+
|
||||
531 |+
|
||||
530 532 | def baz():
|
||||
531 533 | pass
|
||||
532 534 | # end
|
||||
|
||||
E30.py:540:1: E302 [*] Expected 2 blank lines, found 1
|
||||
|
|
||||
539 | # comment
|
||||
540 | @decorator
|
||||
| ^ E302
|
||||
541 | def g():
|
||||
542 | pass
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
536 536 | def f():
|
||||
537 537 | pass
|
||||
538 538 |
|
||||
539 |+
|
||||
540 |+
|
||||
539 541 | # comment
|
||||
540 542 | @decorator
|
||||
541 543 | def g():
|
||||
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30.py:555:2: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
555 | def method2():
|
||||
| ^^^ E303
|
||||
556 | return 22
|
||||
557 | # end
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
551 551 | def method1():
|
||||
552 552 | return 1
|
||||
553 553 |
|
||||
554 |-
|
||||
555 554 | def method2():
|
||||
556 555 | return 22
|
||||
557 556 | # end
|
||||
|
||||
E30.py:565:5: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
565 | # arbitrary comment
|
||||
| ^^^^^^^^^^^^^^^^^^^ E303
|
||||
566 |
|
||||
567 | def inner(): # E306 not expected (pycodestyle detects E306)
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
561 561 | def fn():
|
||||
562 562 | _ = None
|
||||
563 563 |
|
||||
564 |-
|
||||
565 564 | # arbitrary comment
|
||||
566 565 |
|
||||
567 566 | def inner(): # E306 not expected (pycodestyle detects E306)
|
||||
|
||||
E30.py:577:5: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
577 | # arbitrary comment
|
||||
| ^^^^^^^^^^^^^^^^^^^ E303
|
||||
578 | def inner(): # E306 not expected (pycodestyle detects E306)
|
||||
579 | pass
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
573 573 | def fn():
|
||||
574 574 | _ = None
|
||||
575 575 |
|
||||
576 |-
|
||||
577 576 | # arbitrary comment
|
||||
578 577 | def inner(): # E306 not expected (pycodestyle detects E306)
|
||||
579 578 | pass
|
||||
|
||||
E30.py:588:1: E303 [*] Too many blank lines (3)
|
||||
|
|
||||
588 | print()
|
||||
| ^^^^^ E303
|
||||
589 | # end
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
584 584 | print()
|
||||
585 585 |
|
||||
586 586 |
|
||||
587 |-
|
||||
588 587 | print()
|
||||
589 588 | # end
|
||||
590 589 |
|
||||
|
||||
E30.py:597:1: E303 [*] Too many blank lines (3)
|
||||
|
|
||||
597 | # comment
|
||||
| ^^^^^^^^^ E303
|
||||
598 |
|
||||
599 | print()
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
593 593 | print()
|
||||
594 594 |
|
||||
595 595 |
|
||||
596 |-
|
||||
597 596 | # comment
|
||||
598 597 |
|
||||
599 598 | print()
|
||||
|
||||
E30.py:608:5: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
608 | # comment
|
||||
| ^^^^^^^^^ E303
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
604 604 | def a():
|
||||
605 605 | print()
|
||||
606 606 |
|
||||
607 |-
|
||||
608 607 | # comment
|
||||
609 608 |
|
||||
610 609 |
|
||||
|
||||
E30.py:611:5: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
611 | # another comment
|
||||
| ^^^^^^^^^^^^^^^^^ E303
|
||||
612 |
|
||||
613 | print()
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
607 607 |
|
||||
608 608 | # comment
|
||||
609 609 |
|
||||
610 |-
|
||||
611 610 | # another comment
|
||||
612 611 |
|
||||
613 612 | print()
|
||||
|
||||
E30.py:622:1: E303 [*] Too many blank lines (3)
|
||||
|
|
||||
622 | / """This class docstring comes on line 5.
|
||||
623 | | It gives error E303: too many blank lines (3)
|
||||
624 | | """
|
||||
| |___^ E303
|
||||
625 | # end
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
618 618 | #!python
|
||||
619 619 |
|
||||
620 620 |
|
||||
621 |-
|
||||
622 621 | """This class docstring comes on line 5.
|
||||
623 622 | It gives error E303: too many blank lines (3)
|
||||
624 623 | """
|
||||
|
||||
E30.py:634:5: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
634 | def b(self):
|
||||
| ^^^ E303
|
||||
635 | pass
|
||||
636 | # end
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
630 630 | def a(self):
|
||||
631 631 | pass
|
||||
632 632 |
|
||||
633 |-
|
||||
634 633 | def b(self):
|
||||
635 634 | pass
|
||||
636 635 | # end
|
||||
|
||||
E30.py:644:5: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
644 | a = 2
|
||||
| ^ E303
|
||||
645 | # end
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
640 640 | if True:
|
||||
641 641 | a = 1
|
||||
642 642 |
|
||||
643 |-
|
||||
644 643 | a = 2
|
||||
645 644 | # end
|
||||
646 645 |
|
||||
|
||||
E30.py:652:5: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
652 | # comment
|
||||
| ^^^^^^^^^ E303
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
648 648 | # E303
|
||||
649 649 | class Test:
|
||||
650 650 |
|
||||
651 |-
|
||||
652 651 | # comment
|
||||
653 652 |
|
||||
654 653 |
|
||||
|
||||
E30.py:655:5: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
655 | # another comment
|
||||
| ^^^^^^^^^^^^^^^^^ E303
|
||||
656 |
|
||||
657 | def test(self): pass
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
651 651 |
|
||||
652 652 | # comment
|
||||
653 653 |
|
||||
654 |-
|
||||
655 654 | # another comment
|
||||
656 655 |
|
||||
657 656 | def test(self): pass
|
||||
|
||||
E30.py:669:5: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
669 | def b(self):
|
||||
| ^^^ E303
|
||||
670 | pass
|
||||
671 | # end
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
665 665 |
|
||||
666 666 | # wrongly indented comment
|
||||
667 667 |
|
||||
668 |-
|
||||
669 668 | def b(self):
|
||||
670 669 | pass
|
||||
671 670 | # end
|
||||
|
||||
E30.py:679:5: E303 [*] Too many blank lines (2)
|
||||
|
|
||||
679 | pass
|
||||
| ^^^^ E303
|
||||
680 | # end
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
675 675 | def fn():
|
||||
676 676 | pass
|
||||
677 677 |
|
||||
678 |-
|
||||
679 678 | pass
|
||||
680 679 | # end
|
||||
681 680 |
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30.py:686:1: E304 [*] Blank lines found after function decorator (1)
|
||||
|
|
||||
684 | @decorator
|
||||
685 |
|
||||
686 | def function():
|
||||
| ^^^ E304
|
||||
687 | pass
|
||||
688 | # end
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
682 682 |
|
||||
683 683 | # E304
|
||||
684 684 | @decorator
|
||||
685 |-
|
||||
686 685 | def function():
|
||||
687 686 | pass
|
||||
688 687 | # end
|
||||
|
||||
E30.py:695:1: E304 [*] Blank lines found after function decorator (1)
|
||||
|
|
||||
694 | # comment E304 not expected
|
||||
695 | def function():
|
||||
| ^^^ E304
|
||||
696 | pass
|
||||
697 | # end
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
690 690 |
|
||||
691 691 | # E304
|
||||
692 692 | @decorator
|
||||
693 |-
|
||||
694 693 | # comment E304 not expected
|
||||
695 694 | def function():
|
||||
696 695 | pass
|
||||
|
||||
E30.py:707:1: E304 [*] Blank lines found after function decorator (2)
|
||||
|
|
||||
706 | # second comment E304 not expected
|
||||
707 | def function():
|
||||
| ^^^ E304
|
||||
708 | pass
|
||||
709 | # end
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
699 699 |
|
||||
700 700 | # E304
|
||||
701 701 | @decorator
|
||||
702 |-
|
||||
703 702 | # comment E304 not expected
|
||||
704 |-
|
||||
705 |-
|
||||
706 703 | # second comment E304 not expected
|
||||
707 704 | def function():
|
||||
708 705 | pass
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30.py:719:1: E305 [*] Expected 2 blank lines after class or function definition, found (1)
|
||||
|
|
||||
718 | # another comment
|
||||
719 | fn()
|
||||
| ^^ E305
|
||||
720 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
716 716 | # comment
|
||||
717 717 |
|
||||
718 718 | # another comment
|
||||
719 |+
|
||||
720 |+
|
||||
719 721 | fn()
|
||||
720 722 | # end
|
||||
721 723 |
|
||||
|
||||
E30.py:730:1: E305 [*] Expected 2 blank lines after class or function definition, found (1)
|
||||
|
|
||||
729 | # another comment
|
||||
730 | a = 1
|
||||
| ^ E305
|
||||
731 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
727 727 | # comment
|
||||
728 728 |
|
||||
729 729 | # another comment
|
||||
730 |+
|
||||
731 |+
|
||||
730 732 | a = 1
|
||||
731 733 | # end
|
||||
732 734 |
|
||||
|
||||
E30.py:742:1: E305 [*] Expected 2 blank lines after class or function definition, found (1)
|
||||
|
|
||||
740 | # another comment
|
||||
741 |
|
||||
742 | try:
|
||||
| ^^^ E305
|
||||
743 | fn()
|
||||
744 | except Exception:
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
739 739 |
|
||||
740 740 | # another comment
|
||||
741 741 |
|
||||
742 |+
|
||||
742 743 | try:
|
||||
743 744 | fn()
|
||||
744 745 | except Exception:
|
||||
|
||||
E30.py:754:1: E305 [*] Expected 2 blank lines after class or function definition, found (1)
|
||||
|
|
||||
753 | # Two spaces before comments, too.
|
||||
754 | if a():
|
||||
| ^^ E305
|
||||
755 | a()
|
||||
756 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
751 751 | print()
|
||||
752 752 |
|
||||
753 753 | # Two spaces before comments, too.
|
||||
754 |+
|
||||
755 |+
|
||||
754 756 | if a():
|
||||
755 757 | a()
|
||||
756 758 | # end
|
||||
|
||||
E30.py:767:1: E305 [*] Expected 2 blank lines after class or function definition, found (1)
|
||||
|
|
||||
765 | blah, blah
|
||||
766 |
|
||||
767 | if __name__ == '__main__':
|
||||
| ^^ E305
|
||||
768 | main()
|
||||
769 | # end
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
ℹ Safe fix
|
||||
764 764 | def main():
|
||||
765 765 | blah, blah
|
||||
766 766 |
|
||||
767 |+
|
||||
767 768 | if __name__ == '__main__':
|
||||
768 769 | main()
|
||||
769 770 | # end
|
||||
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30.py:775:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
773 | def a():
|
||||
774 | x = 1
|
||||
775 | def b():
|
||||
| ^^^ E306
|
||||
776 | pass
|
||||
777 | # end
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
772 772 | # E306:3:5
|
||||
773 773 | def a():
|
||||
774 774 | x = 1
|
||||
775 |+
|
||||
775 776 | def b():
|
||||
776 777 | pass
|
||||
777 778 | # end
|
||||
|
||||
E30.py:783:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
781 | async def a():
|
||||
782 | x = 1
|
||||
783 | def b():
|
||||
| ^^^ E306
|
||||
784 | pass
|
||||
785 | # end
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
780 780 | #: E306:3:5
|
||||
781 781 | async def a():
|
||||
782 782 | x = 1
|
||||
783 |+
|
||||
783 784 | def b():
|
||||
784 785 | pass
|
||||
785 786 | # end
|
||||
|
||||
E30.py:791:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
789 | def a():
|
||||
790 | x = 2
|
||||
791 | def b():
|
||||
| ^^^ E306
|
||||
792 | x = 1
|
||||
793 | def c():
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
788 788 | #: E306:3:5 E306:5:9
|
||||
789 789 | def a():
|
||||
790 790 | x = 2
|
||||
791 |+
|
||||
791 792 | def b():
|
||||
792 793 | x = 1
|
||||
793 794 | def c():
|
||||
|
||||
E30.py:793:9: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
791 | def b():
|
||||
792 | x = 1
|
||||
793 | def c():
|
||||
| ^^^ E306
|
||||
794 | pass
|
||||
795 | # end
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
790 790 | x = 2
|
||||
791 791 | def b():
|
||||
792 792 | x = 1
|
||||
793 |+
|
||||
793 794 | def c():
|
||||
794 795 | pass
|
||||
795 796 | # end
|
||||
|
||||
E30.py:801:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
799 | def a():
|
||||
800 | x = 1
|
||||
801 | class C:
|
||||
| ^^^^^ E306
|
||||
802 | pass
|
||||
803 | x = 2
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
798 798 | # E306:3:5 E306:6:5
|
||||
799 799 | def a():
|
||||
800 800 | x = 1
|
||||
801 |+
|
||||
801 802 | class C:
|
||||
802 803 | pass
|
||||
803 804 | x = 2
|
||||
|
||||
E30.py:804:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
802 | pass
|
||||
803 | x = 2
|
||||
804 | def b():
|
||||
| ^^^ E306
|
||||
805 | pass
|
||||
806 | # end
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
801 801 | class C:
|
||||
802 802 | pass
|
||||
803 803 | x = 2
|
||||
804 |+
|
||||
804 805 | def b():
|
||||
805 806 | pass
|
||||
806 807 | # end
|
||||
|
||||
E30.py:813:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
811 | def bar():
|
||||
812 | pass
|
||||
813 | def baz(): pass
|
||||
| ^^^ E306
|
||||
814 | # end
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
810 810 | def foo():
|
||||
811 811 | def bar():
|
||||
812 812 | pass
|
||||
813 |+
|
||||
813 814 | def baz(): pass
|
||||
814 815 | # end
|
||||
815 816 |
|
||||
|
||||
E30.py:820:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
818 | def foo():
|
||||
819 | def bar(): pass
|
||||
820 | def baz():
|
||||
| ^^^ E306
|
||||
821 | pass
|
||||
822 | # end
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
817 817 | # E306:3:5
|
||||
818 818 | def foo():
|
||||
819 819 | def bar(): pass
|
||||
820 |+
|
||||
820 821 | def baz():
|
||||
821 822 | pass
|
||||
822 823 | # end
|
||||
|
||||
E30.py:828:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
826 | def a():
|
||||
827 | x = 2
|
||||
828 | @decorator
|
||||
| ^ E306
|
||||
829 | def b():
|
||||
830 | pass
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
825 825 | # E306
|
||||
826 826 | def a():
|
||||
827 827 | x = 2
|
||||
828 |+
|
||||
828 829 | @decorator
|
||||
829 830 | def b():
|
||||
830 831 | pass
|
||||
|
||||
E30.py:837:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
835 | def a():
|
||||
836 | x = 2
|
||||
837 | @decorator
|
||||
| ^ E306
|
||||
838 | async def b():
|
||||
839 | pass
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
834 834 | # E306
|
||||
835 835 | def a():
|
||||
836 836 | x = 2
|
||||
837 |+
|
||||
837 838 | @decorator
|
||||
838 839 | async def b():
|
||||
839 840 | pass
|
||||
|
||||
E30.py:846:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
||||
|
|
||||
844 | def a():
|
||||
845 | x = 2
|
||||
846 | async def b():
|
||||
| ^^^^^ E306
|
||||
847 | pass
|
||||
848 | # end
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
ℹ Safe fix
|
||||
843 843 | # E306
|
||||
844 844 | def a():
|
||||
845 845 | x = 2
|
||||
846 |+
|
||||
846 847 | async def b():
|
||||
847 848 | pass
|
||||
848 849 | # end
|
||||
|
||||
|
||||
@@ -13,5 +13,65 @@ W291.py:1:23: W291 [*] Trailing whitespace
|
||||
1 |-'''trailing whitespace
|
||||
1 |+'''trailing whitespace
|
||||
2 2 | inside a multiline string'''
|
||||
3 3 |
|
||||
4 4 | f'''trailing whitespace
|
||||
|
||||
W291.py:4:24: W291 [*] Trailing whitespace
|
||||
|
|
||||
2 | inside a multiline string'''
|
||||
3 |
|
||||
4 | f'''trailing whitespace
|
||||
| ^ W291
|
||||
5 | inside a multiline f-string'''
|
||||
|
|
||||
= help: Remove trailing whitespace
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | '''trailing whitespace
|
||||
2 2 | inside a multiline string'''
|
||||
3 3 |
|
||||
4 |-f'''trailing whitespace
|
||||
4 |+f'''trailing whitespace
|
||||
5 5 | inside a multiline f-string'''
|
||||
6 6 |
|
||||
7 7 | # Trailing whitespace after `{`
|
||||
|
||||
W291.py:8:8: W291 [*] Trailing whitespace
|
||||
|
|
||||
7 | # Trailing whitespace after `{`
|
||||
8 | f'abc {
|
||||
| ^ W291
|
||||
9 | 1 + 2
|
||||
10 | }'
|
||||
|
|
||||
= help: Remove trailing whitespace
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 | inside a multiline f-string'''
|
||||
6 6 |
|
||||
7 7 | # Trailing whitespace after `{`
|
||||
8 |-f'abc {
|
||||
8 |+f'abc {
|
||||
9 9 | 1 + 2
|
||||
10 10 | }'
|
||||
11 11 |
|
||||
|
||||
W291.py:14:10: W291 [*] Trailing whitespace
|
||||
|
|
||||
12 | # Trailing whitespace after `2`
|
||||
13 | f'abc {
|
||||
14 | 1 + 2
|
||||
| ^ W291
|
||||
15 | }'
|
||||
|
|
||||
= help: Remove trailing whitespace
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 |
|
||||
12 12 | # Trailing whitespace after `2`
|
||||
13 13 | f'abc {
|
||||
14 |- 1 + 2
|
||||
14 |+ 1 + 2
|
||||
15 15 | }'
|
||||
|
||||
|
||||
|
||||
@@ -1634,12 +1634,13 @@ fn common_section(
|
||||
let line_end = checker.stylist().line_ending().as_str();
|
||||
|
||||
if let Some(next) = next {
|
||||
if context
|
||||
.following_lines()
|
||||
.last()
|
||||
.map_or(true, |line| !line.trim().is_empty())
|
||||
{
|
||||
if checker.enabled(Rule::NoBlankLineAfterSection) {
|
||||
if checker.enabled(Rule::NoBlankLineAfterSection) {
|
||||
let num_blank_lines = context
|
||||
.following_lines()
|
||||
.rev()
|
||||
.take_while(|line| line.trim().is_empty())
|
||||
.count();
|
||||
if num_blank_lines < 2 {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NoBlankLineAfterSection {
|
||||
name: context.section_name().to_string(),
|
||||
@@ -1657,13 +1658,13 @@ fn common_section(
|
||||
} else {
|
||||
// The first blank line is the line containing the closing triple quotes, so we need at
|
||||
// least two.
|
||||
let num_blank_lines = context
|
||||
.following_lines()
|
||||
.rev()
|
||||
.take_while(|line| line.trim().is_empty())
|
||||
.count();
|
||||
if num_blank_lines < 2 {
|
||||
if checker.enabled(Rule::BlankLineAfterLastSection) {
|
||||
if checker.enabled(Rule::BlankLineAfterLastSection) {
|
||||
let num_blank_lines = context
|
||||
.following_lines()
|
||||
.rev()
|
||||
.take_while(|line| line.trim().is_empty())
|
||||
.count();
|
||||
if num_blank_lines < 2 {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
BlankLineAfterLastSection {
|
||||
name: context.section_name().to_string(),
|
||||
|
||||
@@ -49,5 +49,7 @@ sections.py:558:5: D214 [*] Section is over-indented ("Returns")
|
||||
563 |- Returns:
|
||||
563 |+ Returns:
|
||||
564 564 | """
|
||||
565 565 |
|
||||
566 566 |
|
||||
|
||||
|
||||
|
||||
@@ -61,4 +61,31 @@ sections.py:216:5: D406 [*] Section name should end with a newline ("Raises")
|
||||
229 229 |
|
||||
230 230 | """
|
||||
|
||||
sections.py:588:5: D406 [*] Section name should end with a newline ("Parameters")
|
||||
|
|
||||
587 | def test_lowercase_sub_section_header_should_be_valid(parameters: list[str], value: int): # noqa: D213
|
||||
588 | """Test that lower case subsection header is valid even if it has the same name as section kind.
|
||||
| _____^
|
||||
589 | |
|
||||
590 | | Parameters:
|
||||
591 | | ----------
|
||||
592 | | parameters:
|
||||
593 | | A list of string parameters
|
||||
594 | | value:
|
||||
595 | | Some value
|
||||
596 | | """
|
||||
| |_______^ D406
|
||||
|
|
||||
= help: Add newline after "Parameters"
|
||||
|
||||
ℹ Safe fix
|
||||
587 587 | def test_lowercase_sub_section_header_should_be_valid(parameters: list[str], value: int): # noqa: D213
|
||||
588 588 | """Test that lower case subsection header is valid even if it has the same name as section kind.
|
||||
589 589 |
|
||||
590 |- Parameters:
|
||||
590 |+ Parameters
|
||||
591 591 | ----------
|
||||
592 592 | parameters:
|
||||
593 593 | A list of string parameters
|
||||
|
||||
|
||||
|
||||
@@ -567,5 +567,32 @@ sections.py:558:5: D407 [*] Missing dashed underline after section ("Returns")
|
||||
563 563 | Returns:
|
||||
564 |+ -------
|
||||
564 565 | """
|
||||
565 566 |
|
||||
566 567 |
|
||||
|
||||
sections.py:600:4: D407 [*] Missing dashed underline after section ("Parameters")
|
||||
|
|
||||
599 | def test_lowercase_sub_section_header_different_kind(returns: int):
|
||||
600 | """Test that lower case subsection header is valid even if it is of a different kind.
|
||||
| ____^
|
||||
601 | |
|
||||
602 | | Parameters
|
||||
603 | | -‐-----------------
|
||||
604 | | returns:
|
||||
605 | | some value
|
||||
606 | |
|
||||
607 | | """
|
||||
| |______^ D407
|
||||
|
|
||||
= help: Add dashed line under "Parameters"
|
||||
|
||||
ℹ Safe fix
|
||||
600 600 | """Test that lower case subsection header is valid even if it is of a different kind.
|
||||
601 601 |
|
||||
602 602 | Parameters
|
||||
603 |+ ----------
|
||||
603 604 | -‐-----------------
|
||||
604 605 | returns:
|
||||
605 606 | some value
|
||||
|
||||
|
||||
|
||||
@@ -61,4 +61,39 @@ sections.py:216:5: D409 [*] Section underline should match the length of its nam
|
||||
227 227 | Raises:
|
||||
228 228 | My attention.
|
||||
|
||||
sections.py:568:5: D409 [*] Section underline should match the length of its name ("Other Parameters")
|
||||
|
|
||||
567 | def test_method_should_be_correctly_capitalized(parameters: list[str], other_parameters: dict[str, str]): # noqa: D213
|
||||
568 | """Test parameters and attributes sections are capitalized correctly.
|
||||
| _____^
|
||||
569 | |
|
||||
570 | | Parameters
|
||||
571 | | ----------
|
||||
572 | | parameters:
|
||||
573 | | A list of string parameters
|
||||
574 | | other_parameters:
|
||||
575 | | A dictionary of string attributes
|
||||
576 | |
|
||||
577 | | Other Parameters
|
||||
578 | | ----------
|
||||
579 | | other_parameters:
|
||||
580 | | A dictionary of string attributes
|
||||
581 | | parameters:
|
||||
582 | | A list of string parameters
|
||||
583 | |
|
||||
584 | | """
|
||||
| |_______^ D409
|
||||
|
|
||||
= help: Adjust underline length to match "Other Parameters"
|
||||
|
||||
ℹ Safe fix
|
||||
575 575 | A dictionary of string attributes
|
||||
576 576 |
|
||||
577 577 | Other Parameters
|
||||
578 |- ----------
|
||||
578 |+ ----------------
|
||||
579 579 | other_parameters:
|
||||
580 580 | A dictionary of string attributes
|
||||
581 581 | parameters:
|
||||
|
||||
|
||||
|
||||
@@ -21,10 +21,9 @@ D413.py:1:1: D413 [*] Missing blank line after last section ("Returns")
|
||||
7 7 | Returns:
|
||||
8 8 | the value
|
||||
9 |+
|
||||
10 |+
|
||||
9 11 | """
|
||||
10 12 |
|
||||
11 13 |
|
||||
9 10 | """
|
||||
10 11 |
|
||||
11 12 |
|
||||
|
||||
D413.py:13:5: D413 [*] Missing blank line after last section ("Returns")
|
||||
|
|
||||
|
||||
@@ -161,5 +161,33 @@ sections.py:558:5: D413 [*] Missing blank line after last section ("Returns")
|
||||
563 563 | Returns:
|
||||
564 |+
|
||||
564 565 | """
|
||||
565 566 |
|
||||
566 567 |
|
||||
|
||||
sections.py:588:5: D413 [*] Missing blank line after last section ("Parameters")
|
||||
|
|
||||
587 | def test_lowercase_sub_section_header_should_be_valid(parameters: list[str], value: int): # noqa: D213
|
||||
588 | """Test that lower case subsection header is valid even if it has the same name as section kind.
|
||||
| _____^
|
||||
589 | |
|
||||
590 | | Parameters:
|
||||
591 | | ----------
|
||||
592 | | parameters:
|
||||
593 | | A list of string parameters
|
||||
594 | | value:
|
||||
595 | | Some value
|
||||
596 | | """
|
||||
| |_______^ D413
|
||||
|
|
||||
= help: Add blank line after "Parameters"
|
||||
|
||||
ℹ Safe fix
|
||||
593 593 | A list of string parameters
|
||||
594 594 | value:
|
||||
595 595 | Some value
|
||||
596 |+
|
||||
596 597 | """
|
||||
597 598 |
|
||||
598 599 |
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,13 @@ use ruff_text_size::TextSize;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [mypy](https://mypy.readthedocs.io/en/stable/common_issues.html#spurious-errors-and-locally-silencing-the-checker)
|
||||
/// Mypy supports a [built-in setting](https://mypy.readthedocs.io/en/stable/error_code_list2.html#check-that-type-ignore-include-an-error-code-ignore-without-code)
|
||||
/// to enforce that all `type: ignore` annotations include an error code, akin
|
||||
/// to enabling this rule:
|
||||
/// ```toml
|
||||
/// [tool.mypy]
|
||||
/// enable_error_code = ["ignore-without-code"]
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct BlanketTypeIgnore;
|
||||
|
||||
|
||||
@@ -84,10 +84,10 @@ pub(crate) fn comparison_with_itself(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let [Expr::Name(left_arg)] = left_call.arguments.args.as_slice() else {
|
||||
let [Expr::Name(left_arg)] = &*left_call.arguments.args else {
|
||||
continue;
|
||||
};
|
||||
let [Expr::Name(right_right)] = right_call.arguments.args.as_slice() else {
|
||||
let [Expr::Name(right_right)] = &*right_call.arguments.args else {
|
||||
continue;
|
||||
};
|
||||
if left_arg.id != right_right.id {
|
||||
|
||||
@@ -59,7 +59,7 @@ pub(crate) fn duplicate_bases(checker: &mut Checker, name: &str, arguments: Opti
|
||||
|
||||
let mut seen: FxHashSet<&str> =
|
||||
FxHashSet::with_capacity_and_hasher(bases.len(), BuildHasherDefault::default());
|
||||
for base in bases {
|
||||
for base in bases.iter() {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = base {
|
||||
if !seen.insert(id) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
|
||||
@@ -45,7 +45,7 @@ impl AlwaysFixableViolation for LiteralMembership {
|
||||
|
||||
/// PLR6201
|
||||
pub(crate) fn literal_membership(checker: &mut Checker, compare: &ast::ExprCompare) {
|
||||
let [op] = compare.ops.as_slice() else {
|
||||
let [op] = &*compare.ops else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ pub(crate) fn literal_membership(checker: &mut Checker, compare: &ast::ExprCompa
|
||||
return;
|
||||
}
|
||||
|
||||
let [right] = compare.comparators.as_slice() else {
|
||||
let [right] = &*compare.comparators else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ fn collect_nested_args(min_max: MinMax, args: &[Expr], semantic: &SemanticModel)
|
||||
range: _,
|
||||
}) = arg
|
||||
{
|
||||
if let [arg] = args.as_slice() {
|
||||
if let [arg] = &**args {
|
||||
if arg.as_starred_expr().is_none() {
|
||||
let new_arg = Expr::Starred(ast::ExprStarred {
|
||||
value: Box::new(arg.clone()),
|
||||
@@ -164,8 +164,8 @@ pub(crate) fn nested_min_max(
|
||||
let flattened_expr = Expr::Call(ast::ExprCall {
|
||||
func: Box::new(func.clone()),
|
||||
arguments: Arguments {
|
||||
args: collect_nested_args(min_max, args, checker.semantic()),
|
||||
keywords: keywords.to_owned(),
|
||||
args: collect_nested_args(min_max, args, checker.semantic()).into_boxed_slice(),
|
||||
keywords: Box::from(keywords),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -96,7 +96,7 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
|
||||
};
|
||||
|
||||
// Enforced via `is_allowed_value`.
|
||||
let [right] = comparators.as_slice() else {
|
||||
let [right] = &**comparators else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -136,14 +136,14 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
|
||||
checker.generator().expr(&Expr::Compare(ast::ExprCompare {
|
||||
left: Box::new(value.as_expr().clone()),
|
||||
ops: match bool_op.op {
|
||||
BoolOp::Or => vec![CmpOp::In],
|
||||
BoolOp::And => vec![CmpOp::NotIn],
|
||||
BoolOp::Or => Box::from([CmpOp::In]),
|
||||
BoolOp::And => Box::from([CmpOp::NotIn]),
|
||||
},
|
||||
comparators: vec![Expr::Tuple(ast::ExprTuple {
|
||||
comparators: Box::from([Expr::Tuple(ast::ExprTuple {
|
||||
elts: comparators.iter().copied().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
})],
|
||||
})]),
|
||||
range: bool_op.range(),
|
||||
})),
|
||||
bool_op.range(),
|
||||
@@ -169,7 +169,7 @@ fn is_allowed_value(bool_op: BoolOp, value: &Expr) -> bool {
|
||||
};
|
||||
|
||||
// Ignore, e.g., `foo == bar == baz`.
|
||||
let [op] = ops.as_slice() else {
|
||||
let [op] = &**ops else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -181,7 +181,7 @@ fn is_allowed_value(bool_op: BoolOp, value: &Expr) -> bool {
|
||||
}
|
||||
|
||||
// Ignore self-comparisons, e.g., `foo == foo`.
|
||||
let [right] = comparators.as_slice() else {
|
||||
let [right] = &**comparators else {
|
||||
return false;
|
||||
};
|
||||
if ComparableExpr::from(left) == ComparableExpr::from(right) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Arguments, Expr, ExprCall, ExprDict, ExprStringLiteral};
|
||||
use ruff_python_ast::{Expr, ExprCall, ExprDict, ExprStringLiteral};
|
||||
use ruff_text_size::Ranged;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -37,15 +38,14 @@ impl Violation for RepeatedKeywordArgument {
|
||||
}
|
||||
|
||||
pub(crate) fn repeated_keyword_argument(checker: &mut Checker, call: &ExprCall) {
|
||||
let ExprCall {
|
||||
arguments: Arguments { keywords, .. },
|
||||
..
|
||||
} = call;
|
||||
let ExprCall { arguments, .. } = call;
|
||||
|
||||
let mut seen =
|
||||
FxHashSet::with_capacity_and_hasher(keywords.len(), BuildHasherDefault::default());
|
||||
let mut seen = FxHashSet::with_capacity_and_hasher(
|
||||
arguments.keywords.len(),
|
||||
BuildHasherDefault::default(),
|
||||
);
|
||||
|
||||
for keyword in keywords {
|
||||
for keyword in arguments.keywords.iter() {
|
||||
if let Some(id) = &keyword.arg {
|
||||
// Ex) `func(a=1, a=2)`
|
||||
if !seen.insert(id.as_str()) {
|
||||
|
||||
@@ -111,7 +111,7 @@ pub(crate) fn unnecessary_dunder_call(checker: &mut Checker, call: &ast::ExprCal
|
||||
let mut title: Option<String> = None;
|
||||
|
||||
if let Some(dunder) = DunderReplacement::from_method(attr) {
|
||||
match (call.arguments.args.as_slice(), dunder) {
|
||||
match (&*call.arguments.args, dunder) {
|
||||
([], DunderReplacement::Builtin(replacement, message)) => {
|
||||
if !checker.semantic().is_builtin(replacement) {
|
||||
return;
|
||||
|
||||
@@ -110,7 +110,7 @@ fn generate_keyword_fix(checker: &Checker, call: &ast::ExprCall) -> Fix {
|
||||
.generator()
|
||||
.expr(&Expr::StringLiteral(ast::ExprStringLiteral {
|
||||
value: ast::StringLiteralValue::single(ast::StringLiteral {
|
||||
value: "locale".to_string(),
|
||||
value: "locale".to_string().into_boxed_str(),
|
||||
unicode: false,
|
||||
range: TextRange::default(),
|
||||
}),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user