Compare commits
16 Commits
v0.1.0
...
zanie/shar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f474920d87 | ||
|
|
a556319e7f | ||
|
|
a29aa78702 | ||
|
|
7a16680791 | ||
|
|
88c0106421 | ||
|
|
f60aa85471 | ||
|
|
d942a777d7 | ||
|
|
8a529925b3 | ||
|
|
dc6b4ad2b4 | ||
|
|
21ea290d6a | ||
|
|
5da0f9111e | ||
|
|
cb6d74c27b | ||
|
|
73049df3ed | ||
|
|
bf0e5788ef | ||
|
|
4113d65836 | ||
|
|
4c2c9bf7e0 |
18
.github/workflows/ci.yaml
vendored
18
.github/workflows/ci.yaml
vendored
@@ -77,6 +77,8 @@ jobs:
|
||||
rustup component add clippy
|
||||
rustup target add wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "ci"
|
||||
- name: "Clippy"
|
||||
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
- name: "Clippy (wasm)"
|
||||
@@ -97,6 +99,8 @@ jobs:
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "ci"
|
||||
- name: "Run tests (Ubuntu)"
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: cargo insta test --all --all-features --unreferenced reject
|
||||
@@ -146,6 +150,8 @@ jobs:
|
||||
cache-dependency-path: playground/package-lock.json
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "ci"
|
||||
- name: "Run wasm-pack"
|
||||
run: |
|
||||
cd crates/ruff_wasm
|
||||
@@ -159,6 +165,8 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "ci"
|
||||
- run: ./scripts/add_rule.py --name DoTheThing --prefix PL --code C0999 --linter pylint
|
||||
- run: cargo check
|
||||
- run: cargo fmt --all --check
|
||||
@@ -227,6 +235,8 @@ jobs:
|
||||
# Only pinned to make caching work, update freely
|
||||
run: rustup toolchain install nightly-2023-06-08
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "ci"
|
||||
- name: "Install cargo-udeps"
|
||||
uses: taiki-e/install-action@cargo-udeps
|
||||
- name: "Run cargo-udeps"
|
||||
@@ -242,6 +252,8 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "ci"
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
@@ -267,6 +279,8 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "ci"
|
||||
- name: "Install pre-commit"
|
||||
run: pip install pre-commit
|
||||
- name: "Cache pre-commit"
|
||||
@@ -300,6 +314,8 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "ci"
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: pip install -r docs/requirements-insiders.txt
|
||||
@@ -352,6 +368,8 @@ jobs:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "ci"
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build --features codspeed -p ruff_benchmark
|
||||
|
||||
@@ -170,7 +170,8 @@ At a high level, the steps involved in adding a new lint rule are as follows:
|
||||
statements, like imports) or `analyze/expression.rs` (if your rule is based on analyzing
|
||||
expressions, like function calls).
|
||||
|
||||
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`).
|
||||
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`). New rules
|
||||
should be added in `RuleGroup::Preview`.
|
||||
|
||||
1. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.
|
||||
|
||||
|
||||
13
Cargo.lock
generated
13
Cargo.lock
generated
@@ -3104,11 +3104,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
version = "0.1.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
@@ -3117,9 +3116,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.26"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3128,9 +3127,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.31"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
|
||||
@@ -46,7 +46,7 @@ syn = { version = "2.0.38" }
|
||||
test-case = { version = "3.2.1" }
|
||||
thiserror = { version = "1.0.49" }
|
||||
toml = { version = "0.7.8" }
|
||||
tracing = { version = "0.1.37" }
|
||||
tracing = { version = "0.1.39" }
|
||||
tracing-indicatif = { version = "0.3.4" }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
|
||||
@@ -54,7 +54,7 @@ impl<'a> Printer<'a> {
|
||||
|
||||
/// Prints the passed in element as well as all its content,
|
||||
/// starting at the specified indentation level
|
||||
#[tracing::instrument(name = "Printer::print", skip_all)]
|
||||
#[tracing::instrument(level = "debug", name = "Printer::print", skip_all)]
|
||||
pub fn print_with_indent(
|
||||
mut self,
|
||||
document: &'a Document,
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/pydocstyle/D300.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pydocstyle/D300.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
def with_backslash():
|
||||
"""Sum\\mary."""
|
||||
|
||||
|
||||
def ends_in_quote():
|
||||
'Sum\\mary."'
|
||||
|
||||
|
||||
def contains_quote():
|
||||
'Sum"\\mary.'
|
||||
@@ -68,6 +68,10 @@ class Apples:
|
||||
def _missing_(cls, value):
|
||||
pass
|
||||
|
||||
# Allow anonymous functions.
|
||||
def _(self):
|
||||
pass
|
||||
|
||||
|
||||
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
|
||||
...
|
||||
|
||||
@@ -6,6 +6,14 @@ for item in {1}:
|
||||
for item in {"apples", "lemons", "water"}: # flags in-line set literals
|
||||
print(f"I like {item}.")
|
||||
|
||||
for item in {1,}:
|
||||
print(f"I can count to {item}!")
|
||||
|
||||
for item in {
|
||||
"apples", "lemons", "water"
|
||||
}: # flags in-line set literals
|
||||
print(f"I like {item}.")
|
||||
|
||||
numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
|
||||
numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/pylint/literal_membership.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pylint/literal_membership.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Errors
|
||||
1 in [1, 2, 3]
|
||||
1 in (1, 2, 3)
|
||||
1 in (
|
||||
1, 2, 3
|
||||
)
|
||||
|
||||
# OK
|
||||
fruits = ["cherry", "grapes"]
|
||||
"cherry" in fruits
|
||||
71
crates/ruff_linter/resources/test/fixtures/pylint/misplaced_bare_raise.py
vendored
Normal file
71
crates/ruff_linter/resources/test/fixtures/pylint/misplaced_bare_raise.py
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# bare raise is an except block
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
if True:
|
||||
raise
|
||||
|
||||
# bare raise is in a finally block which is in an except block
|
||||
try:
|
||||
raise Exception
|
||||
except Exception:
|
||||
try:
|
||||
raise Exception
|
||||
finally:
|
||||
raise
|
||||
|
||||
# bare raise is in an __exit__ method
|
||||
class ContextManager:
|
||||
def __enter__(self):
|
||||
return self
|
||||
def __exit__(self, *args):
|
||||
raise
|
||||
|
||||
try:
|
||||
raise # [misplaced-bare-raise]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def f():
|
||||
try:
|
||||
raise # [misplaced-bare-raise]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def g():
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
def h():
|
||||
try:
|
||||
if True:
|
||||
def i():
|
||||
raise # [misplaced-bare-raise]
|
||||
except Exception:
|
||||
pass
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
def i():
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
class C:
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
raise # [misplaced-bare-raise]
|
||||
54
crates/ruff_linter/resources/test/fixtures/pylint/too_many_boolean_expressions.py
vendored
Normal file
54
crates/ruff_linter/resources/test/fixtures/pylint/too_many_boolean_expressions.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
if a:
|
||||
...
|
||||
elif (a and b):
|
||||
...
|
||||
elif (a and b) and c:
|
||||
...
|
||||
elif (a and b) and c and d:
|
||||
...
|
||||
elif (a and b) and c and d and e:
|
||||
...
|
||||
elif (a and b) and c and d and e and f:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
|
||||
...
|
||||
else:
|
||||
...
|
||||
44
crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py
vendored
Normal file
44
crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import io
|
||||
import sys
|
||||
import tempfile
|
||||
import io as hugo
|
||||
import codecs
|
||||
|
||||
# Errors.
|
||||
open("test.txt")
|
||||
io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
tempfile.NamedTemporaryFile("w")
|
||||
tempfile.TemporaryFile("w")
|
||||
codecs.open("test.txt")
|
||||
tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
||||
# Non-errors.
|
||||
open("test.txt", encoding="utf-8")
|
||||
open("test.bin", "wb")
|
||||
open("test.bin", mode="wb")
|
||||
open("test.txt", "r", -1, "utf-8")
|
||||
open("test.txt", mode=sys.argv[1])
|
||||
|
||||
def func(*args, **kwargs):
|
||||
open(*args)
|
||||
open("text.txt", **kwargs)
|
||||
|
||||
io.TextIOWrapper(io.FileIO("test.txt"), encoding="utf-8")
|
||||
io.TextIOWrapper(io.FileIO("test.txt"), "utf-8")
|
||||
tempfile.TemporaryFile("w", encoding="utf-8")
|
||||
tempfile.TemporaryFile("w", -1, "utf-8")
|
||||
tempfile.TemporaryFile("wb")
|
||||
tempfile.TemporaryFile()
|
||||
tempfile.NamedTemporaryFile("w", encoding="utf-8")
|
||||
tempfile.NamedTemporaryFile("w", -1, "utf-8")
|
||||
tempfile.NamedTemporaryFile("wb")
|
||||
tempfile.NamedTemporaryFile()
|
||||
codecs.open("test.txt", encoding="utf-8")
|
||||
codecs.open("test.bin", "wb")
|
||||
codecs.open("test.bin", mode="wb")
|
||||
codecs.open("test.txt", "r", -1, "utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "w", encoding="utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "w", -1, "utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "wb")
|
||||
tempfile.SpooledTemporaryFile(0, )
|
||||
@@ -786,6 +786,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::SubprocessRunWithoutCheck) {
|
||||
pylint::rules::subprocess_run_without_check(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnspecifiedEncoding) {
|
||||
pylint::rules::unspecified_encoding(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::PytestRaisesWithoutException,
|
||||
Rule::PytestRaisesTooBroad,
|
||||
@@ -1194,6 +1197,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ComparisonWithItself) {
|
||||
pylint::rules::comparison_with_itself(checker, left, ops, comparators);
|
||||
}
|
||||
if checker.enabled(Rule::LiteralMembership) {
|
||||
pylint::rules::literal_membership(checker, compare);
|
||||
}
|
||||
if checker.enabled(Rule::ComparisonOfConstant) {
|
||||
pylint::rules::comparison_of_constant(checker, left, ops, comparators);
|
||||
}
|
||||
|
||||
@@ -964,7 +964,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Raise(ast::StmtRaise { exc, .. }) => {
|
||||
Stmt::Raise(raise @ ast::StmtRaise { exc, .. }) => {
|
||||
if checker.enabled(Rule::RaiseNotImplemented) {
|
||||
if let Some(expr) = exc {
|
||||
pyflakes::rules::raise_not_implemented(checker, expr);
|
||||
@@ -1004,6 +1004,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_raise::rules::unnecessary_paren_on_raise_exception(checker, expr);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::MisplacedBareRaise) {
|
||||
pylint::rules::misplaced_bare_raise(checker, raise);
|
||||
}
|
||||
}
|
||||
Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => {
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
@@ -1067,6 +1070,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::CheckAndRemoveFromSet) {
|
||||
refurb::rules::check_and_remove_from_set(checker, if_);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyBooleanExpressions) {
|
||||
pylint::rules::too_many_boolean_expressions(checker, if_);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnrecognizedVersionInfoCheck,
|
||||
|
||||
@@ -524,6 +524,7 @@ where
|
||||
);
|
||||
self.semantic.push_definition(definition);
|
||||
self.semantic.push_scope(ScopeKind::Function(function_def));
|
||||
self.semantic.flags -= SemanticModelFlags::EXCEPTION_HANDLER;
|
||||
|
||||
self.deferred.functions.push(self.semantic.snapshot());
|
||||
|
||||
@@ -562,6 +563,7 @@ where
|
||||
);
|
||||
self.semantic.push_definition(definition);
|
||||
self.semantic.push_scope(ScopeKind::Class(class_def));
|
||||
self.semantic.flags -= SemanticModelFlags::EXCEPTION_HANDLER;
|
||||
|
||||
// Extract any global bindings from the class body.
|
||||
if let Some(globals) = Globals::from_body(body) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -87,6 +87,7 @@ mod tests {
|
||||
#[test_case(Rule::EscapeSequenceInDocstring, Path::new("D.py"))]
|
||||
#[test_case(Rule::EscapeSequenceInDocstring, Path::new("D301.py"))]
|
||||
#[test_case(Rule::TripleSingleQuotes, Path::new("D.py"))]
|
||||
#[test_case(Rule::TripleSingleQuotes, Path::new("D300.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_codegen::Quote;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -37,6 +37,8 @@ pub struct TripleSingleQuotes {
|
||||
}
|
||||
|
||||
impl Violation for TripleSingleQuotes {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let TripleSingleQuotes { expected_quote } = self;
|
||||
@@ -45,12 +47,25 @@ impl Violation for TripleSingleQuotes {
|
||||
Quote::Single => format!(r#"Use triple single quotes `'''`"#),
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let TripleSingleQuotes { expected_quote } = self;
|
||||
Some(match expected_quote {
|
||||
Quote::Double => format!("Convert to triple double quotes"),
|
||||
Quote::Single => format!("Convert to triple single quotes"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// D300
|
||||
pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
|
||||
let leading_quote = docstring.leading_quote();
|
||||
|
||||
let prefixes = docstring
|
||||
.leading_quote()
|
||||
.trim_end_matches(|c| c == '\'' || c == '"')
|
||||
.to_owned();
|
||||
|
||||
let expected_quote = if docstring.body().contains("\"\"\"") {
|
||||
Quote::Single
|
||||
} else {
|
||||
@@ -60,18 +75,34 @@ pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
|
||||
match expected_quote {
|
||||
Quote::Single => {
|
||||
if !leading_quote.ends_with("'''") {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TripleSingleQuotes { expected_quote },
|
||||
docstring.range(),
|
||||
));
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(TripleSingleQuotes { expected_quote }, docstring.range());
|
||||
|
||||
let body = docstring.body().as_str();
|
||||
if !body.ends_with('\'') {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("{prefixes}'''{body}'''"),
|
||||
docstring.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
Quote::Double => {
|
||||
if !leading_quote.ends_with("\"\"\"") {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TripleSingleQuotes { expected_quote },
|
||||
docstring.range(),
|
||||
));
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(TripleSingleQuotes { expected_quote }, docstring.range());
|
||||
|
||||
let body = docstring.body().as_str();
|
||||
if !body.ends_with('"') {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("{prefixes}\"\"\"{body}\"\"\""),
|
||||
docstring.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,102 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D.py:307:5: D300 Use triple double quotes `"""`
|
||||
D.py:307:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
306 | def triple_single_quotes_raw():
|
||||
307 | r'''Summary.'''
|
||||
| ^^^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:312:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
304 304 |
|
||||
305 305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
306 306 | def triple_single_quotes_raw():
|
||||
307 |- r'''Summary.'''
|
||||
307 |+ r"""Summary."""
|
||||
308 308 |
|
||||
309 309 |
|
||||
310 310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
|
||||
D.py:312:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
311 | def triple_single_quotes_raw_uppercase():
|
||||
312 | R'''Summary.'''
|
||||
| ^^^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:317:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
309 309 |
|
||||
310 310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
311 311 | def triple_single_quotes_raw_uppercase():
|
||||
312 |- R'''Summary.'''
|
||||
312 |+ R"""Summary."""
|
||||
313 313 |
|
||||
314 314 |
|
||||
315 315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
|
||||
D.py:317:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
316 | def single_quotes_raw():
|
||||
317 | r'Summary.'
|
||||
| ^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:322:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
314 314 |
|
||||
315 315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
316 316 | def single_quotes_raw():
|
||||
317 |- r'Summary.'
|
||||
317 |+ r"""Summary."""
|
||||
318 318 |
|
||||
319 319 |
|
||||
320 320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
|
||||
D.py:322:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
321 | def single_quotes_raw_uppercase():
|
||||
322 | R'Summary.'
|
||||
| ^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:328:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
319 319 |
|
||||
320 320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
321 321 | def single_quotes_raw_uppercase():
|
||||
322 |- R'Summary.'
|
||||
322 |+ R"""Summary."""
|
||||
323 323 |
|
||||
324 324 |
|
||||
325 325 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
|
||||
D.py:328:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
326 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
327 | def single_quotes_raw_uppercase_backslash():
|
||||
328 | R'Sum\mary.'
|
||||
| ^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:645:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
325 325 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
326 326 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
327 327 | def single_quotes_raw_uppercase_backslash():
|
||||
328 |- R'Sum\mary.'
|
||||
328 |+ R"""Sum\mary."""
|
||||
329 329 |
|
||||
330 330 |
|
||||
331 331 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
|
||||
D.py:645:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
644 | def single_line_docstring_with_an_escaped_backslash():
|
||||
645 | "\
|
||||
@@ -51,8 +106,21 @@ D.py:645:5: D300 Use triple double quotes `"""`
|
||||
647 |
|
||||
648 | class StatementOnSameLineAsDocstring:
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:649:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
642 642 |
|
||||
643 643 |
|
||||
644 644 | def single_line_docstring_with_an_escaped_backslash():
|
||||
645 |- "\
|
||||
646 |- "
|
||||
645 |+ """\
|
||||
646 |+ """
|
||||
647 647 |
|
||||
648 648 | class StatementOnSameLineAsDocstring:
|
||||
649 649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
|
||||
|
||||
D.py:649:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
648 | class StatementOnSameLineAsDocstring:
|
||||
649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
|
||||
@@ -60,15 +128,37 @@ D.py:649:5: D300 Use triple double quotes `"""`
|
||||
650 | def sort_services(self):
|
||||
651 | pass
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:654:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
646 646 | "
|
||||
647 647 |
|
||||
648 648 | class StatementOnSameLineAsDocstring:
|
||||
649 |- "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
|
||||
649 |+ """After this docstring there's another statement on the same line separated by a semicolon.""" ; priorities=1
|
||||
650 650 | def sort_services(self):
|
||||
651 651 | pass
|
||||
652 652 |
|
||||
|
||||
D.py:654:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
653 | class StatementOnSameLineAsDocstring:
|
||||
654 | "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:658:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
651 651 | pass
|
||||
652 652 |
|
||||
653 653 | class StatementOnSameLineAsDocstring:
|
||||
654 |- "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
|
||||
654 |+ """After this docstring there's another statement on the same line separated by a semicolon."""; priorities=1
|
||||
655 655 |
|
||||
656 656 |
|
||||
657 657 | class CommentAfterDocstring:
|
||||
|
||||
D.py:658:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
657 | class CommentAfterDocstring:
|
||||
658 | "After this docstring there's a comment." # priorities=1
|
||||
@@ -76,8 +166,19 @@ D.py:658:5: D300 Use triple double quotes `"""`
|
||||
659 | def sort_services(self):
|
||||
660 | pass
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:664:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
655 655 |
|
||||
656 656 |
|
||||
657 657 | class CommentAfterDocstring:
|
||||
658 |- "After this docstring there's a comment." # priorities=1
|
||||
658 |+ """After this docstring there's a comment.""" # priorities=1
|
||||
659 659 | def sort_services(self):
|
||||
660 660 | pass
|
||||
661 661 |
|
||||
|
||||
D.py:664:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
663 | def newline_after_closing_quote(self):
|
||||
664 | "We enforce a newline after the closing quote for a multi-line docstring \
|
||||
@@ -85,5 +186,15 @@ D.py:664:5: D300 Use triple double quotes `"""`
|
||||
665 | | but continuations shouldn't be considered multi-line"
|
||||
| |_________________________________________________________^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
ℹ Fix
|
||||
661 661 |
|
||||
662 662 |
|
||||
663 663 | def newline_after_closing_quote(self):
|
||||
664 |- "We enforce a newline after the closing quote for a multi-line docstring \
|
||||
665 |- but continuations shouldn't be considered multi-line"
|
||||
664 |+ """We enforce a newline after the closing quote for a multi-line docstring \
|
||||
665 |+ but continuations shouldn't be considered multi-line"""
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D300.py:6:5: D300 Use triple double quotes `"""`
|
||||
|
|
||||
5 | def ends_in_quote():
|
||||
6 | 'Sum\\mary."'
|
||||
| ^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D300.py:10:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
9 | def contains_quote():
|
||||
10 | 'Sum"\\mary.'
|
||||
| ^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
ℹ Fix
|
||||
7 7 |
|
||||
8 8 |
|
||||
9 9 | def contains_quote():
|
||||
10 |- 'Sum"\\mary.'
|
||||
10 |+ """Sum"\\mary."""
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
bom.py:1:1: D300 Use triple double quotes `"""`
|
||||
bom.py:1:1: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
1 | ''' SAM macro definitions '''
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
ℹ Fix
|
||||
1 |-''' SAM macro definitions '''
|
||||
1 |+""" SAM macro definitions """
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@ pub(super) fn type_param_name(arguments: &Arguments) -> Option<&str> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn in_dunder_init(semantic: &SemanticModel, settings: &LinterSettings) -> bool {
|
||||
pub(super) fn in_dunder_method(
|
||||
dunder_name: &str,
|
||||
semantic: &SemanticModel,
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
let scope = semantic.current_scope();
|
||||
let ScopeKind::Function(ast::StmtFunctionDef {
|
||||
name,
|
||||
@@ -32,7 +36,7 @@ pub(super) fn in_dunder_init(semantic: &SemanticModel, settings: &LinterSettings
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if name != "__init__" {
|
||||
if name != dunder_name {
|
||||
return false;
|
||||
}
|
||||
let Some(parent) = semantic.first_non_type_parent_scope(scope) else {
|
||||
|
||||
@@ -133,8 +133,11 @@ mod tests {
|
||||
Rule::SubprocessRunWithoutCheck,
|
||||
Path::new("subprocess_run_without_check.py")
|
||||
)]
|
||||
#[test_case(Rule::UnspecifiedEncoding, Path::new("unspecified_encoding.py"))]
|
||||
#[test_case(Rule::BadDunderMethodName, Path::new("bad_dunder_method_name.py"))]
|
||||
#[test_case(Rule::NoSelfUse, Path::new("no_self_use.py"))]
|
||||
#[test_case(Rule::MisplacedBareRaise, Path::new("misplaced_bare_raise.py"))]
|
||||
#[test_case(Rule::LiteralMembership, Path::new("literal_membership.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
@@ -228,6 +231,22 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_boolean_expressions() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("pylint/too_many_boolean_expressions.py"),
|
||||
&LinterSettings {
|
||||
pylint: pylint::settings::Settings {
|
||||
max_bool_expr: 5,
|
||||
..pylint::settings::Settings::default()
|
||||
},
|
||||
..LinterSettings::for_rule(Rule::TooManyBooleanExpressions)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_statements() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(crate) fn bad_dunder_method_name(checker: &mut Checker, class_body: &[Stmt])
|
||||
.iter()
|
||||
.filter_map(ruff_python_ast::Stmt::as_function_def_stmt)
|
||||
.filter(|method| {
|
||||
if is_known_dunder_method(&method.name) {
|
||||
if is_known_dunder_method(&method.name) || matches!(method.name.as_str(), "_") {
|
||||
return false;
|
||||
}
|
||||
method.name.starts_with('_') && method.name.ends_with('_')
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use ast::ExprContext;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -38,7 +36,7 @@ impl AlwaysFixableViolation for IterationOverSet {
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!("Use a sequence type instead of a `set` when iterating over values")
|
||||
format!("Convert to `tuple`")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,15 +52,14 @@ pub(crate) fn iteration_over_set(checker: &mut Checker, expr: &Expr) {
|
||||
|
||||
let mut diagnostic = Diagnostic::new(IterationOverSet, expr.range());
|
||||
|
||||
let tuple = checker.generator().expr(&Expr::Tuple(ast::ExprTuple {
|
||||
elts: elts.clone(),
|
||||
ctx: ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
}));
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("({tuple})"),
|
||||
expr.range(),
|
||||
)));
|
||||
let tuple = if let [elt] = elts.as_slice() {
|
||||
let elt = checker.locator().slice(elt);
|
||||
format!("({elt},)")
|
||||
} else {
|
||||
let set = checker.locator().slice(expr);
|
||||
format!("({})", &set[1..set.len() - 1])
|
||||
};
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(tuple, expr.range())));
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for membership tests on `list` and `tuple` literals.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// When testing for membership in a static sequence, prefer a `set` literal
|
||||
/// over a `list` or `tuple`, as Python optimizes `set` membership tests.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// 1 in [1, 2, 3]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// 1 in {1, 2, 3}
|
||||
/// ```
|
||||
/// ## References
|
||||
/// - [What’s New In Python 3.2](https://docs.python.org/3/whatsnew/3.2.html#optimizations)
|
||||
#[violation]
|
||||
pub struct LiteralMembership;
|
||||
|
||||
impl AlwaysFixableViolation for LiteralMembership {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use a `set` literal when testing for membership")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!("Convert to `set`")
|
||||
}
|
||||
}
|
||||
|
||||
/// PLR6201
|
||||
pub(crate) fn literal_membership(checker: &mut Checker, compare: &ast::ExprCompare) {
|
||||
let [op] = compare.ops.as_slice() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(op, CmpOp::In | CmpOp::NotIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
let [right] = compare.comparators.as_slice() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(right, Expr::List(_) | Expr::Tuple(_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(LiteralMembership, right.range());
|
||||
|
||||
let literal = checker.locator().slice(right);
|
||||
let set = format!("{{{}}}", &literal[1..literal.len() - 1]);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(set, right.range())));
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::pylint::helpers::in_dunder_method;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for bare `raise` statements outside of exception handlers.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// A bare `raise` statement without an exception object will re-raise the last
|
||||
/// exception that was active in the current scope, and is typically used
|
||||
/// within an exception handler to re-raise the caught exception.
|
||||
///
|
||||
/// If a bare `raise` is used outside of an exception handler, it will generate
|
||||
/// an error due to the lack of an active exception.
|
||||
///
|
||||
/// Note that a bare `raise` within a `finally` block will work in some cases
|
||||
/// (namely, when the exception is raised within the `try` block), but should
|
||||
/// be avoided as it can lead to confusing behavior.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import Any
|
||||
///
|
||||
///
|
||||
/// def is_some(obj: Any) -> bool:
|
||||
/// if obj is None:
|
||||
/// raise
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import Any
|
||||
///
|
||||
///
|
||||
/// def is_some(obj: Any) -> bool:
|
||||
/// if obj is None:
|
||||
/// raise ValueError("`obj` cannot be `None`")
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MisplacedBareRaise;
|
||||
|
||||
impl Violation for MisplacedBareRaise {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Bare `raise` statement is not inside an exception handler")
|
||||
}
|
||||
}
|
||||
|
||||
/// PLE0704
|
||||
pub(crate) fn misplaced_bare_raise(checker: &mut Checker, raise: &ast::StmtRaise) {
|
||||
if raise.exc.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if checker.semantic().in_exception_handler() {
|
||||
return;
|
||||
}
|
||||
|
||||
if in_dunder_method("__exit__", checker.semantic(), checker.settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(MisplacedBareRaise, raise.range()));
|
||||
}
|
||||
@@ -24,10 +24,12 @@ pub(crate) use invalid_envvar_value::*;
|
||||
pub(crate) use invalid_str_return::*;
|
||||
pub(crate) use invalid_string_characters::*;
|
||||
pub(crate) use iteration_over_set::*;
|
||||
pub(crate) use literal_membership::*;
|
||||
pub(crate) use load_before_global_declaration::*;
|
||||
pub(crate) use logging::*;
|
||||
pub(crate) use magic_value_comparison::*;
|
||||
pub(crate) use manual_import_from::*;
|
||||
pub(crate) use misplaced_bare_raise::*;
|
||||
pub(crate) use named_expr_without_context::*;
|
||||
pub(crate) use nested_min_max::*;
|
||||
pub(crate) use no_self_use::*;
|
||||
@@ -43,6 +45,7 @@ pub(crate) use subprocess_popen_preexec_fn::*;
|
||||
pub(crate) use subprocess_run_without_check::*;
|
||||
pub(crate) use sys_exit_alias::*;
|
||||
pub(crate) use too_many_arguments::*;
|
||||
pub(crate) use too_many_boolean_expressions::*;
|
||||
pub(crate) use too_many_branches::*;
|
||||
pub(crate) use too_many_public_methods::*;
|
||||
pub(crate) use too_many_return_statements::*;
|
||||
@@ -52,6 +55,7 @@ pub(crate) use type_name_incorrect_variance::*;
|
||||
pub(crate) use type_param_name_mismatch::*;
|
||||
pub(crate) use unexpected_special_method_signature::*;
|
||||
pub(crate) use unnecessary_direct_lambda_call::*;
|
||||
pub(crate) use unspecified_encoding::*;
|
||||
pub(crate) use useless_else_on_loop::*;
|
||||
pub(crate) use useless_import_alias::*;
|
||||
pub(crate) use useless_return::*;
|
||||
@@ -84,10 +88,12 @@ mod invalid_envvar_value;
|
||||
mod invalid_str_return;
|
||||
mod invalid_string_characters;
|
||||
mod iteration_over_set;
|
||||
mod literal_membership;
|
||||
mod load_before_global_declaration;
|
||||
mod logging;
|
||||
mod magic_value_comparison;
|
||||
mod manual_import_from;
|
||||
mod misplaced_bare_raise;
|
||||
mod named_expr_without_context;
|
||||
mod nested_min_max;
|
||||
mod no_self_use;
|
||||
@@ -103,6 +109,7 @@ mod subprocess_popen_preexec_fn;
|
||||
mod subprocess_run_without_check;
|
||||
mod sys_exit_alias;
|
||||
mod too_many_arguments;
|
||||
mod too_many_boolean_expressions;
|
||||
mod too_many_branches;
|
||||
mod too_many_public_methods;
|
||||
mod too_many_return_statements;
|
||||
@@ -112,6 +119,7 @@ mod type_name_incorrect_variance;
|
||||
mod type_param_name_mismatch;
|
||||
mod unexpected_special_method_signature;
|
||||
mod unnecessary_direct_lambda_call;
|
||||
mod unspecified_encoding;
|
||||
mod useless_else_on_loop;
|
||||
mod useless_import_alias;
|
||||
mod useless_return;
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_python_ast::helpers::is_const_none;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::pylint::helpers::in_dunder_init;
|
||||
use crate::rules::pylint::helpers::in_dunder_method;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `__init__` methods that return values.
|
||||
@@ -58,7 +58,7 @@ pub(crate) fn return_in_init(checker: &mut Checker, stmt: &Stmt) {
|
||||
}
|
||||
}
|
||||
|
||||
if in_dunder_init(checker.semantic(), checker.settings) {
|
||||
if in_dunder_method("__init__", checker.semantic(), checker.settings) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(ReturnInInit, stmt.range()));
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use ast::{Expr, StmtIf};
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for too many Boolean expressions in an `if` statement.
|
||||
///
|
||||
/// By default, this rule allows up to 5 expressions. This can be configured
|
||||
/// using the [`pylint.max-bool-expr`] option.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `if` statements with many Boolean expressions are harder to understand
|
||||
/// and maintain. Consider assigning the result of the Boolean expression,
|
||||
/// or any of its sub-expressions, to a variable.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// if a and b and c and d and e and f and g and h:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `pylint.max-bool-expr`
|
||||
#[violation]
|
||||
pub struct TooManyBooleanExpressions {
|
||||
expressions: usize,
|
||||
max_expressions: usize,
|
||||
}
|
||||
|
||||
impl Violation for TooManyBooleanExpressions {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let TooManyBooleanExpressions {
|
||||
expressions,
|
||||
max_expressions,
|
||||
} = self;
|
||||
format!("Too many Boolean expressions ({expressions} > {max_expressions})")
|
||||
}
|
||||
}
|
||||
|
||||
/// PLR0916
|
||||
pub(crate) fn too_many_boolean_expressions(checker: &mut Checker, stmt: &StmtIf) {
|
||||
if let Some(bool_op) = stmt.test.as_bool_op_expr() {
|
||||
let expressions = count_bools(bool_op);
|
||||
if expressions > checker.settings.pylint.max_bool_expr {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TooManyBooleanExpressions {
|
||||
expressions,
|
||||
max_expressions: checker.settings.pylint.max_bool_expr,
|
||||
},
|
||||
bool_op.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for elif in &stmt.elif_else_clauses {
|
||||
if let Some(bool_op) = elif.test.as_ref().and_then(Expr::as_bool_op_expr) {
|
||||
let expressions = count_bools(bool_op);
|
||||
if expressions > checker.settings.pylint.max_bool_expr {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TooManyBooleanExpressions {
|
||||
expressions,
|
||||
max_expressions: checker.settings.pylint.max_bool_expr,
|
||||
},
|
||||
bool_op.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Count the number of Boolean expressions in a `bool_op` expression.
|
||||
fn count_bools(bool_op: &ast::ExprBoolOp) -> usize {
|
||||
bool_op
|
||||
.values
|
||||
.iter()
|
||||
.map(|expr| {
|
||||
if let Expr::BoolOp(bool_op) = expr {
|
||||
count_bools(bool_op)
|
||||
} else {
|
||||
1
|
||||
}
|
||||
})
|
||||
.sum::<usize>()
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::call_path::{format_call_path, CallPath};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `open` and related calls without an explicit `encoding`
|
||||
/// argument.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using `open` in text mode without an explicit encoding can lead to
|
||||
/// non-portable code, with differing behavior across platforms.
|
||||
///
|
||||
/// Instead, consider using the `encoding` parameter to enforce a specific
|
||||
/// encoding.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// open("file.txt")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// open("file.txt", encoding="utf-8")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open)
|
||||
#[violation]
|
||||
pub struct UnspecifiedEncoding {
|
||||
function_name: String,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
impl Violation for UnspecifiedEncoding {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnspecifiedEncoding {
|
||||
function_name,
|
||||
mode,
|
||||
} = self;
|
||||
|
||||
match mode {
|
||||
Mode::Supported => {
|
||||
format!("`{function_name}` in text mode without explicit `encoding` argument")
|
||||
}
|
||||
Mode::Unsupported => {
|
||||
format!("`{function_name}` without explicit `encoding` argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PLW1514
|
||||
pub(crate) fn unspecified_encoding(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
let Some((function_name, mode)) = checker
|
||||
.semantic()
|
||||
.resolve_call_path(&call.func)
|
||||
.filter(|call_path| is_violation(call, call_path))
|
||||
.map(|call_path| {
|
||||
(
|
||||
format_call_path(call_path.as_slice()),
|
||||
Mode::from(&call_path),
|
||||
)
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UnspecifiedEncoding {
|
||||
function_name,
|
||||
mode,
|
||||
},
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
|
||||
/// Returns `true` if the given expression is a string literal containing a `b` character.
|
||||
fn is_binary_mode(expr: &ast::Expr) -> Option<bool> {
|
||||
Some(expr.as_constant_expr()?.value.as_str()?.value.contains('b'))
|
||||
}
|
||||
|
||||
/// Returns `true` if the given call lacks an explicit `encoding`.
|
||||
fn is_violation(call: &ast::ExprCall, call_path: &CallPath) -> bool {
|
||||
// If we have something like `*args`, which might contain the encoding argument, abort.
|
||||
if call
|
||||
.arguments
|
||||
.args
|
||||
.iter()
|
||||
.any(ruff_python_ast::Expr::is_starred_expr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// If we have something like `**kwargs`, which might contain the encoding argument, abort.
|
||||
if call
|
||||
.arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
match call_path.as_slice() {
|
||||
["" | "codecs" | "_io", "open"] => {
|
||||
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
|
||||
if is_binary_mode(mode_arg).unwrap_or(true) {
|
||||
// binary mode or unknown mode is no violation
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// else mode not specified, defaults to text mode
|
||||
call.arguments.find_argument("encoding", 3).is_none()
|
||||
}
|
||||
["tempfile", "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile"] => {
|
||||
let mode_pos = usize::from(call_path[1] == "SpooledTemporaryFile");
|
||||
if let Some(mode_arg) = call.arguments.find_argument("mode", mode_pos) {
|
||||
if is_binary_mode(mode_arg).unwrap_or(true) {
|
||||
// binary mode or unknown mode is no violation
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// defaults to binary mode
|
||||
return false;
|
||||
}
|
||||
call.arguments
|
||||
.find_argument("encoding", mode_pos + 2)
|
||||
.is_none()
|
||||
}
|
||||
["io" | "_io", "TextIOWrapper"] => call.arguments.find_argument("encoding", 1).is_none(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Mode {
|
||||
/// The call supports a `mode` argument.
|
||||
Supported,
|
||||
/// The call does not support a `mode` argument.
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
impl From<&CallPath<'_>> for Mode {
|
||||
fn from(value: &CallPath<'_>) -> Self {
|
||||
match value.as_slice() {
|
||||
["" | "codecs" | "_io", "open"] => Mode::Supported,
|
||||
["tempfile", "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile"] => {
|
||||
Mode::Supported
|
||||
}
|
||||
["io" | "_io", "TextIOWrapper"] => Mode::Unsupported,
|
||||
_ => Mode::Unsupported,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::pylint::helpers::in_dunder_init;
|
||||
use crate::rules::pylint::helpers::in_dunder_method;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `__init__` methods that are turned into generators by the
|
||||
@@ -40,7 +40,7 @@ impl Violation for YieldInInit {
|
||||
|
||||
/// PLE0100
|
||||
pub(crate) fn yield_in_init(checker: &mut Checker, expr: &Expr) {
|
||||
if in_dunder_init(checker.semantic(), checker.settings) {
|
||||
if in_dunder_method("__init__", checker.semantic(), checker.settings) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(YieldInInit, expr.range()));
|
||||
|
||||
@@ -40,6 +40,7 @@ pub struct Settings {
|
||||
pub allow_magic_value_types: Vec<ConstantType>,
|
||||
pub max_args: usize,
|
||||
pub max_returns: usize,
|
||||
pub max_bool_expr: usize,
|
||||
pub max_branches: usize,
|
||||
pub max_statements: usize,
|
||||
pub max_public_methods: usize,
|
||||
@@ -51,6 +52,7 @@ impl Default for Settings {
|
||||
allow_magic_value_types: vec![ConstantType::Str, ConstantType::Bytes],
|
||||
max_args: 5,
|
||||
max_returns: 6,
|
||||
max_bool_expr: 5,
|
||||
max_branches: 12,
|
||||
max_statements: 50,
|
||||
max_public_methods: 20,
|
||||
|
||||
@@ -9,7 +9,7 @@ iteration_over_set.py:3:13: PLC0208 [*] Use a sequence type instead of a `set` w
|
||||
| ^^^ PLC0208
|
||||
4 | print(f"I can count to {item}!")
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | # Errors
|
||||
@@ -28,7 +28,7 @@ iteration_over_set.py:6:13: PLC0208 [*] Use a sequence type instead of a `set` w
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0208
|
||||
7 | print(f"I like {item}.")
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
3 3 | for item in {1}:
|
||||
@@ -38,90 +38,136 @@ iteration_over_set.py:6:13: PLC0208 [*] Use a sequence type instead of a `set` w
|
||||
6 |+for item in ("apples", "lemons", "water"): # flags in-line set literals
|
||||
7 7 | print(f"I like {item}.")
|
||||
8 8 |
|
||||
9 9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
9 9 | for item in {1,}:
|
||||
|
||||
iteration_over_set.py:9:28: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:9:13: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
7 | print(f"I like {item}.")
|
||||
8 |
|
||||
9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
10 |
|
||||
11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
9 | for item in {1,}:
|
||||
| ^^^^ PLC0208
|
||||
10 | print(f"I can count to {item}!")
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
6 6 | for item in {"apples", "lemons", "water"}: # flags in-line set literals
|
||||
7 7 | print(f"I like {item}.")
|
||||
8 8 |
|
||||
9 |-numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
9 |+numbers_list = [i for i in (1, 2, 3)] # flags sets in list comprehensions
|
||||
10 10 |
|
||||
11 11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
12 12 |
|
||||
9 |-for item in {1,}:
|
||||
9 |+for item in (1,):
|
||||
10 10 | print(f"I can count to {item}!")
|
||||
11 11 |
|
||||
12 12 | for item in {
|
||||
|
||||
iteration_over_set.py:11:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:12:13: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
10 |
|
||||
11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
12 |
|
||||
13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
10 | print(f"I can count to {item}!")
|
||||
11 |
|
||||
12 | for item in {
|
||||
| _____________^
|
||||
13 | | "apples", "lemons", "water"
|
||||
14 | | }: # flags in-line set literals
|
||||
| |_^ PLC0208
|
||||
15 | print(f"I like {item}.")
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
8 8 |
|
||||
9 9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
10 10 |
|
||||
11 |-numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
11 |+numbers_set = {i for i in (1, 2, 3)} # flags sets in set comprehensions
|
||||
12 12 |
|
||||
13 13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
14 14 |
|
||||
|
||||
iteration_over_set.py:13:36: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
12 |
|
||||
13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
14 |
|
||||
15 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
|
||||
ℹ Fix
|
||||
10 10 |
|
||||
11 11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
12 12 |
|
||||
13 |-numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
13 |+numbers_dict = {str(i): i for i in (1, 2, 3)} # flags sets in dict comprehensions
|
||||
14 14 |
|
||||
15 15 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
9 9 | for item in {1,}:
|
||||
10 10 | print(f"I can count to {item}!")
|
||||
11 11 |
|
||||
12 |-for item in {
|
||||
12 |+for item in (
|
||||
13 13 | "apples", "lemons", "water"
|
||||
14 |-}: # flags in-line set literals
|
||||
14 |+): # flags in-line set literals
|
||||
15 15 | print(f"I like {item}.")
|
||||
16 16 |
|
||||
17 17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
|
||||
iteration_over_set.py:15:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:17:28: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
14 |
|
||||
15 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
15 | print(f"I like {item}.")
|
||||
16 |
|
||||
17 | # Non-errors
|
||||
17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
18 |
|
||||
19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
12 12 |
|
||||
13 13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
14 14 |
|
||||
15 |-numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
15 |+numbers_gen = (i for i in (1, 2, 3)) # flags sets in generator expressions
|
||||
14 14 | }: # flags in-line set literals
|
||||
15 15 | print(f"I like {item}.")
|
||||
16 16 |
|
||||
17 17 | # Non-errors
|
||||
17 |-numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
17 |+numbers_list = [i for i in (1, 2, 3)] # flags sets in list comprehensions
|
||||
18 18 |
|
||||
19 19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
20 20 |
|
||||
|
||||
iteration_over_set.py:19:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
18 |
|
||||
19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
20 |
|
||||
21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
|
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
16 16 |
|
||||
17 17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
18 18 |
|
||||
19 |-numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
19 |+numbers_set = {i for i in (1, 2, 3)} # flags sets in set comprehensions
|
||||
20 20 |
|
||||
21 21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
22 22 |
|
||||
|
||||
iteration_over_set.py:21:36: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
20 |
|
||||
21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
22 |
|
||||
23 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
|
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
18 18 |
|
||||
19 19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
20 20 |
|
||||
21 |-numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
21 |+numbers_dict = {str(i): i for i in (1, 2, 3)} # flags sets in dict comprehensions
|
||||
22 22 |
|
||||
23 23 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
24 24 |
|
||||
|
||||
iteration_over_set.py:23:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
22 |
|
||||
23 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
24 |
|
||||
25 | # Non-errors
|
||||
|
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
20 20 |
|
||||
21 21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
22 22 |
|
||||
23 |-numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
23 |+numbers_gen = (i for i in (1, 2, 3)) # flags sets in generator expressions
|
||||
24 24 |
|
||||
25 25 | # Non-errors
|
||||
26 26 |
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
misplaced_bare_raise.py:30:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
29 | try:
|
||||
30 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
31 | except Exception:
|
||||
32 | pass
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:36:9: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
34 | def f():
|
||||
35 | try:
|
||||
36 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
37 | except Exception:
|
||||
38 | pass
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:41:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
40 | def g():
|
||||
41 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
42 |
|
||||
43 | def h():
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:47:17: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
45 | if True:
|
||||
46 | def i():
|
||||
47 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
48 | except Exception:
|
||||
49 | pass
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:50:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
48 | except Exception:
|
||||
49 | pass
|
||||
50 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
51 |
|
||||
52 | raise # [misplaced-bare-raise]
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:52:1: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
50 | raise # [misplaced-bare-raise]
|
||||
51 |
|
||||
52 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
53 |
|
||||
54 | try:
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:58:9: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
56 | except:
|
||||
57 | def i():
|
||||
58 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
59 |
|
||||
60 | try:
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:64:9: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
62 | except:
|
||||
63 | class C:
|
||||
64 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
65 |
|
||||
66 | try:
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:71:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
69 | pass
|
||||
70 | finally:
|
||||
71 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
|
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
literal_membership.py:2:6: PLR6201 [*] Use a `set` literal when testing for membership
|
||||
|
|
||||
1 | # Errors
|
||||
2 | 1 in [1, 2, 3]
|
||||
| ^^^^^^^^^ PLR6201
|
||||
3 | 1 in (1, 2, 3)
|
||||
4 | 1 in (
|
||||
|
|
||||
= help: Convert to `set`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | # Errors
|
||||
2 |-1 in [1, 2, 3]
|
||||
2 |+1 in {1, 2, 3}
|
||||
3 3 | 1 in (1, 2, 3)
|
||||
4 4 | 1 in (
|
||||
5 5 | 1, 2, 3
|
||||
|
||||
literal_membership.py:3:6: PLR6201 [*] Use a `set` literal when testing for membership
|
||||
|
|
||||
1 | # Errors
|
||||
2 | 1 in [1, 2, 3]
|
||||
3 | 1 in (1, 2, 3)
|
||||
| ^^^^^^^^^ PLR6201
|
||||
4 | 1 in (
|
||||
5 | 1, 2, 3
|
||||
|
|
||||
= help: Convert to `set`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | # Errors
|
||||
2 2 | 1 in [1, 2, 3]
|
||||
3 |-1 in (1, 2, 3)
|
||||
3 |+1 in {1, 2, 3}
|
||||
4 4 | 1 in (
|
||||
5 5 | 1, 2, 3
|
||||
6 6 | )
|
||||
|
||||
literal_membership.py:4:6: PLR6201 [*] Use a `set` literal when testing for membership
|
||||
|
|
||||
2 | 1 in [1, 2, 3]
|
||||
3 | 1 in (1, 2, 3)
|
||||
4 | 1 in (
|
||||
| ______^
|
||||
5 | | 1, 2, 3
|
||||
6 | | )
|
||||
| |_^ PLR6201
|
||||
7 |
|
||||
8 | # OK
|
||||
|
|
||||
= help: Convert to `set`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | # Errors
|
||||
2 2 | 1 in [1, 2, 3]
|
||||
3 3 | 1 in (1, 2, 3)
|
||||
4 |-1 in (
|
||||
4 |+1 in {
|
||||
5 5 | 1, 2, 3
|
||||
6 |-)
|
||||
6 |+}
|
||||
7 7 |
|
||||
8 8 | # OK
|
||||
9 9 | fruits = ["cherry", "grapes"]
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
unspecified_encoding.py:8:1: PLW1514 `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
7 | # Errors.
|
||||
8 | open("test.txt")
|
||||
| ^^^^ PLW1514
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
|
|
||||
|
||||
unspecified_encoding.py:9:1: PLW1514 `io.TextIOWrapper` without explicit `encoding` argument
|
||||
|
|
||||
7 | # Errors.
|
||||
8 | open("test.txt")
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
| ^^^^^^^^^^^^^^^^ PLW1514
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
|
|
||||
|
||||
unspecified_encoding.py:10:1: PLW1514 `io.TextIOWrapper` without explicit `encoding` argument
|
||||
|
|
||||
8 | open("test.txt")
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
| ^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
|
|
||||
|
||||
unspecified_encoding.py:11:1: PLW1514 `tempfile.NamedTemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
|
|
||||
|
||||
unspecified_encoding.py:12:1: PLW1514 `tempfile.TemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
13 | codecs.open("test.txt")
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
|
||||
|
||||
unspecified_encoding.py:13:1: PLW1514 `codecs.open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
| ^^^^^^^^^^^ PLW1514
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
|
||||
|
||||
unspecified_encoding.py:14:1: PLW1514 `tempfile.SpooledTemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
15 |
|
||||
16 | # Non-errors.
|
||||
|
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
too_many_boolean_expressions.py:11:6: PLR0916 Too many Boolean expressions (6 > 5)
|
||||
|
|
||||
9 | elif (a and b) and c and d and e:
|
||||
10 | ...
|
||||
11 | elif (a and b) and c and d and e and f:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
12 | ...
|
||||
13 | elif (a and b) and c and d and e and f and g:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:13:6: PLR0916 Too many Boolean expressions (7 > 5)
|
||||
|
|
||||
11 | elif (a and b) and c and d and e and f:
|
||||
12 | ...
|
||||
13 | elif (a and b) and c and d and e and f and g:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
14 | ...
|
||||
15 | elif (a and b) and c and d and e and f and g and h:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:15:6: PLR0916 Too many Boolean expressions (8 > 5)
|
||||
|
|
||||
13 | elif (a and b) and c and d and e and f and g:
|
||||
14 | ...
|
||||
15 | elif (a and b) and c and d and e and f and g and h:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
16 | ...
|
||||
17 | elif (a and b) and c and d and e and f and g and h and i:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:17:6: PLR0916 Too many Boolean expressions (9 > 5)
|
||||
|
|
||||
15 | elif (a and b) and c and d and e and f and g and h:
|
||||
16 | ...
|
||||
17 | elif (a and b) and c and d and e and f and g and h and i:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
18 | ...
|
||||
19 | elif (a and b) and c and d and e and f and g and h and i and j:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:19:6: PLR0916 Too many Boolean expressions (10 > 5)
|
||||
|
|
||||
17 | elif (a and b) and c and d and e and f and g and h and i:
|
||||
18 | ...
|
||||
19 | elif (a and b) and c and d and e and f and g and h and i and j:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
20 | ...
|
||||
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:21:6: PLR0916 Too many Boolean expressions (11 > 5)
|
||||
|
|
||||
19 | elif (a and b) and c and d and e and f and g and h and i and j:
|
||||
20 | ...
|
||||
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
22 | ...
|
||||
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:23:6: PLR0916 Too many Boolean expressions (12 > 5)
|
||||
|
|
||||
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
|
||||
22 | ...
|
||||
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
24 | ...
|
||||
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:25:6: PLR0916 Too many Boolean expressions (13 > 5)
|
||||
|
|
||||
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
||||
24 | ...
|
||||
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
26 | ...
|
||||
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:27:6: PLR0916 Too many Boolean expressions (14 > 5)
|
||||
|
|
||||
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
||||
26 | ...
|
||||
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
28 | ...
|
||||
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:29:6: PLR0916 Too many Boolean expressions (15 > 5)
|
||||
|
|
||||
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
||||
28 | ...
|
||||
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
30 | ...
|
||||
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:31:6: PLR0916 Too many Boolean expressions (16 > 5)
|
||||
|
|
||||
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
||||
30 | ...
|
||||
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
32 | ...
|
||||
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:33:6: PLR0916 Too many Boolean expressions (17 > 5)
|
||||
|
|
||||
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
||||
32 | ...
|
||||
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
34 | ...
|
||||
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:35:6: PLR0916 Too many Boolean expressions (18 > 5)
|
||||
|
|
||||
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
||||
34 | ...
|
||||
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
36 | ...
|
||||
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:37:6: PLR0916 Too many Boolean expressions (19 > 5)
|
||||
|
|
||||
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
||||
36 | ...
|
||||
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
38 | ...
|
||||
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:39:6: PLR0916 Too many Boolean expressions (20 > 5)
|
||||
|
|
||||
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
||||
38 | ...
|
||||
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
40 | ...
|
||||
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:41:6: PLR0916 Too many Boolean expressions (21 > 5)
|
||||
|
|
||||
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
||||
40 | ...
|
||||
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
42 | ...
|
||||
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:43:6: PLR0916 Too many Boolean expressions (22 > 5)
|
||||
|
|
||||
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
||||
42 | ...
|
||||
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
44 | ...
|
||||
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:45:6: PLR0916 Too many Boolean expressions (23 > 5)
|
||||
|
|
||||
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
||||
44 | ...
|
||||
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
46 | ...
|
||||
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:47:6: PLR0916 Too many Boolean expressions (24 > 5)
|
||||
|
|
||||
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
||||
46 | ...
|
||||
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
48 | ...
|
||||
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:49:6: PLR0916 Too many Boolean expressions (25 > 5)
|
||||
|
|
||||
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
||||
48 | ...
|
||||
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
50 | ...
|
||||
51 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:51:6: PLR0916 Too many Boolean expressions (26 > 5)
|
||||
|
|
||||
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
||||
50 | ...
|
||||
51 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
52 | ...
|
||||
53 | else:
|
||||
|
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ pub(crate) fn map_codes(func: &ItemFn) -> syn::Result<TokenStream> {
|
||||
};
|
||||
|
||||
// Map from: linter (e.g., `Flake8Bugbear`) to rule code (e.g.,`"002"`) to rule data (e.g.,
|
||||
// `(Rule::UnaryPrefixIncrement, RuleGroup::Unspecified, vec![])`).
|
||||
// `(Rule::UnaryPrefixIncrement, RuleGroup::Stable, vec![])`).
|
||||
let mut linter_to_rules: BTreeMap<Ident, BTreeMap<String, Rule>> = BTreeMap::new();
|
||||
|
||||
for arm in arms {
|
||||
|
||||
@@ -2379,6 +2379,11 @@ pub struct PylintOptions {
|
||||
example = r"max-public-methods = 20"
|
||||
)]
|
||||
pub max_public_methods: Option<usize>,
|
||||
|
||||
/// Maximum number of Boolean expressions allowed within a single `if` statement
|
||||
/// (see: `PLR0916`).
|
||||
#[option(default = r"5", value_type = "int", example = r"max-bool-expr = 5")]
|
||||
pub max_bool_expr: Option<usize>,
|
||||
}
|
||||
|
||||
impl PylintOptions {
|
||||
@@ -2389,6 +2394,7 @@ impl PylintOptions {
|
||||
.allow_magic_value_types
|
||||
.unwrap_or(defaults.allow_magic_value_types),
|
||||
max_args: self.max_args.unwrap_or(defaults.max_args),
|
||||
max_bool_expr: self.max_bool_expr.unwrap_or(defaults.max_bool_expr),
|
||||
max_returns: self.max_returns.unwrap_or(defaults.max_returns),
|
||||
max_branches: self.max_branches.unwrap_or(defaults.max_branches),
|
||||
max_statements: self.max_statements.unwrap_or(defaults.max_statements),
|
||||
@@ -2503,7 +2509,7 @@ pub struct FormatOptions {
|
||||
/// ```
|
||||
///
|
||||
/// Ruff will change `a` to use single quotes when using `quote-style = "single"`. However,
|
||||
/// `a` will be unchanged, as converting to single quotes would require the inner `'` to be
|
||||
/// `b` will be unchanged, as converting to single quotes would require the inner `'` to be
|
||||
/// escaped, which leads to less readable code: `'It\'s monday morning'`.
|
||||
#[option(
|
||||
default = r#"double"#,
|
||||
|
||||
@@ -292,7 +292,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ruff
|
||||
# Include `--format=github` to enable automatic inline annotations.
|
||||
# Update output format to enable automatic inline annotations.
|
||||
- name: Run Ruff
|
||||
run: ruff check --format=github .
|
||||
run: ruff check --output-format=github .
|
||||
```
|
||||
|
||||
19
ruff.schema.json
generated
19
ruff.schema.json
generated
@@ -1239,7 +1239,7 @@
|
||||
]
|
||||
},
|
||||
"quote-style": {
|
||||
"description": "Whether to prefer single `'` or double `\"` quotes for strings. Defaults to double quotes.\n\nIn compliance with [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/), Ruff prefers double quotes for multiline strings and docstrings, regardless of the configured quote style.\n\nRuff may also deviate from this option if using the configured quotes would require escaping quote characters within the string. For example, given:\n\n```python a = \"a string without any quotes\" b = \"It's monday morning\" ```\n\nRuff will change `a` to use single quotes when using `quote-style = \"single\"`. However, `a` will be unchanged, as converting to single quotes would require the inner `'` to be escaped, which leads to less readable code: `'It\\'s monday morning'`.",
|
||||
"description": "Whether to prefer single `'` or double `\"` quotes for strings. Defaults to double quotes.\n\nIn compliance with [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/), Ruff prefers double quotes for multiline strings and docstrings, regardless of the configured quote style.\n\nRuff may also deviate from this option if using the configured quotes would require escaping quote characters within the string. For example, given:\n\n```python a = \"a string without any quotes\" b = \"It's monday morning\" ```\n\nRuff will change `a` to use single quotes when using `quote-style = \"single\"`. However, `b` will be unchanged, as converting to single quotes would require the inner `'` to be escaped, which leads to less readable code: `'It\\'s monday morning'`.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/QuoteStyle"
|
||||
@@ -2214,6 +2214,15 @@
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"max-bool-expr": {
|
||||
"description": "Maximum number of Boolean expressions allowed within a single `if` statement (see: `PLR0916`).",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"max-branches": {
|
||||
"description": "Maximum number of branches allowed for a function or method body (see: `PLR0912`).",
|
||||
"type": [
|
||||
@@ -2895,6 +2904,9 @@
|
||||
"PLE060",
|
||||
"PLE0604",
|
||||
"PLE0605",
|
||||
"PLE07",
|
||||
"PLE070",
|
||||
"PLE0704",
|
||||
"PLE1",
|
||||
"PLE11",
|
||||
"PLE114",
|
||||
@@ -2946,6 +2958,7 @@
|
||||
"PLR0912",
|
||||
"PLR0913",
|
||||
"PLR0915",
|
||||
"PLR0916",
|
||||
"PLR1",
|
||||
"PLR17",
|
||||
"PLR170",
|
||||
@@ -2965,6 +2978,9 @@
|
||||
"PLR550",
|
||||
"PLR5501",
|
||||
"PLR6",
|
||||
"PLR62",
|
||||
"PLR620",
|
||||
"PLR6201",
|
||||
"PLR63",
|
||||
"PLR630",
|
||||
"PLR6301",
|
||||
@@ -2994,6 +3010,7 @@
|
||||
"PLW1509",
|
||||
"PLW151",
|
||||
"PLW1510",
|
||||
"PLW1514",
|
||||
"PLW16",
|
||||
"PLW164",
|
||||
"PLW1641",
|
||||
|
||||
@@ -140,8 +140,7 @@ pub(crate) fn {rule_name_snake}(checker: &mut Checker) {{}}
|
||||
variant = pascal_case(linter)
|
||||
rule = f"""rules::{linter.split(" ")[0]}::rules::{name}"""
|
||||
lines.append(
|
||||
" " * 8
|
||||
+ f"""({variant}, "{code}") => (RuleGroup::Unspecified, {rule}),\n""",
|
||||
" " * 8 + f"""({variant}, "{code}") => (RuleGroup::Stable, {rule}),\n""",
|
||||
)
|
||||
lines.sort()
|
||||
text += "".join(lines)
|
||||
|
||||
@@ -68,6 +68,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
|
||||
"surrounding-whitespace",
|
||||
"tab-indentation",
|
||||
"too-few-spaces-before-inline-comment",
|
||||
"too-many-boolean-expressions",
|
||||
"trailing-comma-on-bare-tuple",
|
||||
"triple-single-quotes",
|
||||
"under-indentation",
|
||||
|
||||
Reference in New Issue
Block a user