Compare commits
6 Commits
v0.0.258
...
blacklist_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c2d430430 | ||
|
|
b0242ccfa1 | ||
|
|
8ffc77d0ee | ||
|
|
adc677bc9c | ||
|
|
213e83aff2 | ||
|
|
4b91826a0b |
24
.github/workflows/ci.yaml
vendored
24
.github/workflows/ci.yaml
vendored
@@ -85,6 +85,7 @@ jobs:
|
||||
name: ruff
|
||||
path: target/debug/ruff
|
||||
|
||||
|
||||
cargo-test-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
name: "cargo test (wasm)"
|
||||
@@ -175,26 +176,3 @@ jobs:
|
||||
with:
|
||||
name: ecosystem-result
|
||||
path: ecosystem-result
|
||||
|
||||
cargo-udeps:
|
||||
name: "cargo udeps"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup toolchain install nightly
|
||||
- name: "Install cargo-udeps"
|
||||
uses: taiki-e/install-action@cargo-udeps
|
||||
- name: "Run cargo-udeps"
|
||||
run: |
|
||||
unused_dependencies=$(cargo +nightly udeps > unused.txt && cat unused.txt | cut -d $'\n' -f 2-)
|
||||
if [ -z "$unused_dependencies" ]; then
|
||||
echo "No unused dependencies found" > $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
else
|
||||
echo "Unused dependencies found" > $GITHUB_STEP_SUMMARY
|
||||
echo '```console' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$unused_dependencies" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -780,7 +780,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.258"
|
||||
version = "0.0.257"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.1.8",
|
||||
@@ -1982,7 +1982,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.258"
|
||||
version = "0.0.257"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bisection",
|
||||
@@ -2063,7 +2063,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.258"
|
||||
version = "0.0.257"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
|
||||
@@ -137,7 +137,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.258'
|
||||
rev: 'v0.0.257'
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -243,7 +243,7 @@ quality tools, including:
|
||||
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
|
||||
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
|
||||
- [flake8-django](https://pypi.org/project/flake8-django/)
|
||||
- [flake8-django](https://pypi.org/project/flake8-django/) ([#2817](https://github.com/charliermarsh/ruff/issues/2817))
|
||||
- [flake8-docstrings](https://pypi.org/project/flake8-docstrings/)
|
||||
- [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
|
||||
- [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.258"
|
||||
version = "0.0.257"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.258"
|
||||
version = "0.0.257"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -5,4 +5,3 @@ uses-*
|
||||
rewrite-*
|
||||
prefer-*
|
||||
consider-*
|
||||
use-*
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import pickle
|
||||
from telnetlib import Telnet
|
||||
|
||||
pickle.loads()
|
||||
|
||||
Telnet("localhost", 23)
|
||||
@@ -1,3 +0,0 @@
|
||||
import pickle
|
||||
|
||||
pickle.loads()
|
||||
@@ -1,113 +0,0 @@
|
||||
from django.db import models
|
||||
from django.db.models import Model
|
||||
|
||||
|
||||
class StrBeforeRandomField(models.Model):
|
||||
"""Model with `__str__` before a random property."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def __str__(self):
|
||||
return ""
|
||||
|
||||
random_property = "foo"
|
||||
|
||||
|
||||
class StrBeforeFieldModel(models.Model):
|
||||
"""Model with `__str__` before fields."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def __str__(self):
|
||||
return "foobar"
|
||||
|
||||
first_name = models.CharField(max_length=32)
|
||||
|
||||
|
||||
class ManagerBeforeField(models.Model):
|
||||
"""Model with manager before fields."""
|
||||
|
||||
objects = "manager"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def __str__(self):
|
||||
return "foobar"
|
||||
|
||||
first_name = models.CharField(max_length=32)
|
||||
|
||||
|
||||
class CustomMethodBeforeStr(models.Model):
|
||||
"""Model with a custom method before `__str__`."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def my_method(self):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return "foobar"
|
||||
|
||||
|
||||
class GetAbsoluteUrlBeforeSave(Model):
|
||||
"""Model with `get_absolute_url` method before `save` method.
|
||||
|
||||
Subclass this directly using the `Model` class.
|
||||
"""
|
||||
|
||||
def get_absolute_url(self):
|
||||
pass
|
||||
|
||||
def save(self):
|
||||
pass
|
||||
|
||||
|
||||
class ConstantsAreNotFields(models.Model):
|
||||
"""Model with an assignment to a constant after `__str__`."""
|
||||
|
||||
first_name = models.CharField(max_length=32)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def __str__(self):
|
||||
pass
|
||||
|
||||
MY_CONSTANT = id(1)
|
||||
|
||||
|
||||
class PerfectlyFine(models.Model):
|
||||
"""Model which has everything in perfect order."""
|
||||
|
||||
first_name = models.CharField(max_length=32)
|
||||
last_name = models.CharField(max_length=32)
|
||||
objects = "manager"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def __str__(self):
|
||||
return "Perfectly fine!"
|
||||
|
||||
def save(self, **kwargs):
|
||||
super(PerfectlyFine, self).save(**kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "http://%s" % self
|
||||
|
||||
def my_method(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def random_property(self):
|
||||
return "%s" % self
|
||||
@@ -2,9 +2,7 @@ from a import a1 # import_from
|
||||
from c import * # import_from_star
|
||||
import a # import
|
||||
import c.d
|
||||
from z import z1
|
||||
import b as b1 # import_as
|
||||
import z
|
||||
|
||||
from ..parent import *
|
||||
from .my import fn
|
||||
|
||||
@@ -13,8 +13,3 @@ result = {
|
||||
'key1': 'value',
|
||||
'key2': 'value',
|
||||
}
|
||||
|
||||
def foo() -> None:
|
||||
#: E231
|
||||
if (1,2):
|
||||
pass
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
"""Test: imports within `ModuleNotFoundError` and `ImportError` handlers."""
|
||||
"""Test: imports within `ModuleNotFoundError` handlers."""
|
||||
|
||||
|
||||
def module_not_found_error():
|
||||
def check_orjson():
|
||||
try:
|
||||
import orjson
|
||||
|
||||
return True
|
||||
except ModuleNotFoundError:
|
||||
return False
|
||||
|
||||
|
||||
def import_error():
|
||||
try:
|
||||
import orjson
|
||||
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
@@ -30,17 +30,8 @@ def f(x: "List[str]") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: r"List[str]") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: "List[str]") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: """List[str]""") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: "Li" "st[str]") -> None:
|
||||
list = "abc"
|
||||
|
||||
|
||||
def f(x: List[str]) -> None:
|
||||
...
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
input = [1, 2, 3]
|
||||
otherInput = [2, 3, 4]
|
||||
foo = [1, 2, 3, 4]
|
||||
|
||||
# OK
|
||||
zip(input, otherInput) # different inputs
|
||||
@@ -9,8 +8,6 @@ zip(input, input[2:]) # not successive
|
||||
zip(input[:-1], input[2:]) # not successive
|
||||
list(zip(input, otherInput)) # nested call
|
||||
zip(input, input[1::2]) # not successive
|
||||
zip(foo[:-1], foo[1:], foo, strict=False) # more than 2 inputs
|
||||
zip(foo[:-1], foo[1:], foo, strict=True) # more than 2 inputs
|
||||
|
||||
# Errors
|
||||
zip(input, input[1:])
|
||||
@@ -20,6 +17,3 @@ zip(input[1:], input[2:])
|
||||
zip(input[1:-1], input[2:])
|
||||
list(zip(input, input[1:]))
|
||||
list(zip(input[:-1], input[1:]))
|
||||
zip(foo[:-1], foo[1:], strict=True)
|
||||
zip(foo[:-1], foo[1:], strict=False)
|
||||
zip(foo[:-1], foo[1:], strict=bool(foo))
|
||||
|
||||
@@ -6,6 +6,7 @@ use log::error;
|
||||
use nohash_hasher::IntMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_common::cformat::{CFormatError, CFormatErrorType};
|
||||
use rustpython_parser as parser;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Comprehension, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext,
|
||||
ExprKind, KeywordData, Located, Location, Operator, Pattern, PatternKind, Stmt, StmtKind,
|
||||
@@ -14,17 +15,18 @@ use rustpython_parser::ast::{
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::context::Context;
|
||||
use ruff_python_ast::helpers::{binding_range, extract_handled_exceptions, to_module_path};
|
||||
use ruff_python_ast::helpers::{
|
||||
binding_range, extract_handled_exceptions, to_module_path, Exceptions,
|
||||
};
|
||||
use ruff_python_ast::operations::{extract_all_names, AllNamesFlags};
|
||||
use ruff_python_ast::relocate::relocate_expr;
|
||||
use ruff_python_ast::scope::{
|
||||
Binding, BindingId, BindingKind, ClassDef, Exceptions, ExecutionContext, FunctionDef, Lambda,
|
||||
Scope, ScopeId, ScopeKind, ScopeStack,
|
||||
Binding, BindingId, BindingKind, ClassDef, ExecutionContext, FunctionDef, Lambda, Scope,
|
||||
ScopeId, ScopeKind, ScopeStack,
|
||||
};
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
use ruff_python_ast::types::{Node, Range, RefEquality};
|
||||
use ruff_python_ast::typing::{
|
||||
match_annotated_subscript, parse_type_annotation, Callable, SubscriptKind,
|
||||
};
|
||||
use ruff_python_ast::typing::{match_annotated_subscript, Callable, SubscriptKind};
|
||||
use ruff_python_ast::visitor::{walk_excepthandler, walk_pattern, Visitor};
|
||||
use ruff_python_ast::{
|
||||
branch_detection, cast, helpers, operations, str, typing, visibility, visitor,
|
||||
@@ -196,7 +198,6 @@ where
|
||||
if !scope_index.is_global() {
|
||||
// Add the binding to the current scope.
|
||||
let context = self.ctx.execution_context();
|
||||
let exceptions = self.ctx.exceptions();
|
||||
let scope = &mut self.ctx.scopes[scope_index];
|
||||
let usage = Some((scope.id, Range::from(stmt)));
|
||||
for (name, range) in names.iter().zip(ranges.iter()) {
|
||||
@@ -208,7 +209,6 @@ where
|
||||
range: *range,
|
||||
source: Some(RefEquality(stmt)),
|
||||
context,
|
||||
exceptions,
|
||||
});
|
||||
scope.add(name, id);
|
||||
}
|
||||
@@ -226,7 +226,6 @@ where
|
||||
let ranges: Vec<Range> = helpers::find_names(stmt, self.locator).collect();
|
||||
if !scope_index.is_global() {
|
||||
let context = self.ctx.execution_context();
|
||||
let exceptions = self.ctx.exceptions();
|
||||
let scope = &mut self.ctx.scopes[scope_index];
|
||||
let usage = Some((scope.id, Range::from(stmt)));
|
||||
for (name, range) in names.iter().zip(ranges.iter()) {
|
||||
@@ -239,7 +238,6 @@ where
|
||||
range: *range,
|
||||
source: Some(RefEquality(stmt)),
|
||||
context,
|
||||
exceptions,
|
||||
});
|
||||
scope.add(name, id);
|
||||
}
|
||||
@@ -641,6 +639,7 @@ where
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
|
||||
let context = self.ctx.execution_context();
|
||||
self.add_binding(
|
||||
name,
|
||||
Binding {
|
||||
@@ -650,8 +649,7 @@ where
|
||||
typing_usage: None,
|
||||
range: Range::from(stmt),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
context,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -710,13 +708,6 @@ where
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::DjangoUnorderedBodyContentInModel)
|
||||
{
|
||||
flake8_django::rules::unordered_body_content_in_model(self, bases, body);
|
||||
}
|
||||
if self.settings.rules.enabled(Rule::GlobalStatement) {
|
||||
pylint::rules::global_statement(self, name);
|
||||
}
|
||||
@@ -836,6 +827,14 @@ where
|
||||
pyupgrade::rules::deprecated_mock_import(self, stmt);
|
||||
}
|
||||
|
||||
// If a module is imported within a `ModuleNotFoundError` body, treat that as a
|
||||
// synthetic usage.
|
||||
let is_handled = self
|
||||
.ctx
|
||||
.handled_exceptions
|
||||
.iter()
|
||||
.any(|exceptions| exceptions.contains(Exceptions::MODULE_NOT_FOUND_ERROR));
|
||||
|
||||
for alias in names {
|
||||
if alias.node.name == "__future__" {
|
||||
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||
@@ -850,7 +849,6 @@ where
|
||||
range: Range::from(alias),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -872,12 +870,15 @@ where
|
||||
Binding {
|
||||
kind: BindingKind::SubmoduleImportation(name, full_name),
|
||||
runtime_usage: None,
|
||||
synthetic_usage: None,
|
||||
synthetic_usage: if is_handled {
|
||||
Some((self.ctx.scope_id(), Range::from(alias)))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
typing_usage: None,
|
||||
range: Range::from(alias),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
@@ -899,7 +900,7 @@ where
|
||||
Binding {
|
||||
kind: BindingKind::Importation(name, full_name),
|
||||
runtime_usage: None,
|
||||
synthetic_usage: if is_explicit_reexport {
|
||||
synthetic_usage: if is_handled || is_explicit_reexport {
|
||||
Some((self.ctx.scope_id(), Range::from(alias)))
|
||||
} else {
|
||||
None
|
||||
@@ -908,7 +909,6 @@ where
|
||||
range: Range::from(alias),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1153,6 +1153,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// If a module is imported within a `ModuleNotFoundError` body, treat that as a
|
||||
// synthetic usage.
|
||||
let is_handled = self
|
||||
.ctx
|
||||
.handled_exceptions
|
||||
.iter()
|
||||
.any(|exceptions| exceptions.contains(Exceptions::MODULE_NOT_FOUND_ERROR));
|
||||
|
||||
for alias in names {
|
||||
if let Some("__future__") = module.as_deref() {
|
||||
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||
@@ -1167,7 +1175,6 @@ where
|
||||
range: Range::from(alias),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1193,12 +1200,15 @@ where
|
||||
Binding {
|
||||
kind: BindingKind::StarImportation(*level, module.clone()),
|
||||
runtime_usage: None,
|
||||
synthetic_usage: None,
|
||||
synthetic_usage: if is_handled {
|
||||
Some((self.ctx.scope_id(), Range::from(alias)))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
typing_usage: None,
|
||||
range: Range::from(stmt),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1266,16 +1276,16 @@ where
|
||||
Binding {
|
||||
kind: BindingKind::FromImportation(name, full_name),
|
||||
runtime_usage: None,
|
||||
synthetic_usage: if is_explicit_reexport {
|
||||
synthetic_usage: if is_handled || is_explicit_reexport {
|
||||
Some((self.ctx.scope_id(), Range::from(alias)))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
typing_usage: None,
|
||||
range: Range::from(alias),
|
||||
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1701,8 +1711,8 @@ where
|
||||
flake8_pytest_style::rules::assert_in_exception_handler(handlers),
|
||||
);
|
||||
}
|
||||
if self.settings.rules.enabled(Rule::SuppressibleException) {
|
||||
flake8_simplify::rules::suppressible_exception(
|
||||
if self.settings.rules.enabled(Rule::UseContextlibSuppress) {
|
||||
flake8_simplify::rules::use_contextlib_suppress(
|
||||
self, stmt, body, handlers, orelse, finalbody,
|
||||
);
|
||||
}
|
||||
@@ -1897,7 +1907,6 @@ where
|
||||
range: Range::from(stmt),
|
||||
source: Some(RefEquality(stmt)),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
});
|
||||
self.ctx.global_scope_mut().add(name, id);
|
||||
}
|
||||
@@ -1960,7 +1969,6 @@ where
|
||||
range: Range::from(*stmt),
|
||||
source: Some(RefEquality(stmt)),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
});
|
||||
self.ctx.global_scope_mut().add(name, id);
|
||||
}
|
||||
@@ -2110,7 +2118,6 @@ where
|
||||
range: Range::from(stmt),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -2128,8 +2135,7 @@ where
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
||||
if !self.ctx.in_deferred_type_definition
|
||||
&& self.ctx.in_deferred_string_type_definition.is_none()
|
||||
if !(self.ctx.in_deferred_type_definition || self.ctx.in_deferred_string_type_definition)
|
||||
&& self.ctx.in_type_definition
|
||||
&& self.ctx.annotations_future_enabled
|
||||
{
|
||||
@@ -2254,9 +2260,9 @@ where
|
||||
if self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::LoadBeforeGlobalDeclaration)
|
||||
.enabled(Rule::UsePriorToGlobalDeclaration)
|
||||
{
|
||||
pylint::rules::load_before_global_declaration(self, id, expr);
|
||||
pylint::rules::use_prior_to_global_declaration(self, id, expr);
|
||||
}
|
||||
}
|
||||
ExprKind::Attribute { attr, value, .. } => {
|
||||
@@ -2439,30 +2445,8 @@ where
|
||||
}
|
||||
|
||||
// flake8-bandit
|
||||
if self.settings.rules.any_enabled(&[
|
||||
Rule::SuspiciousPickleUsage,
|
||||
Rule::SuspiciousMarshalUsage,
|
||||
Rule::SuspiciousInsecureHashUsage,
|
||||
Rule::SuspiciousInsecureCipherUsage,
|
||||
Rule::SuspiciousInsecureCipherModeUsage,
|
||||
Rule::SuspiciousMktempUsage,
|
||||
Rule::SuspiciousEvalUsage,
|
||||
Rule::SuspiciousMarkSafeUsage,
|
||||
Rule::SuspiciousURLOpenUsage,
|
||||
Rule::SuspiciousNonCryptographicRandomUsage,
|
||||
Rule::SuspiciousXMLCElementTreeUsage,
|
||||
Rule::SuspiciousXMLElementTreeUsage,
|
||||
Rule::SuspiciousXMLExpatReaderUsage,
|
||||
Rule::SuspiciousXMLExpatBuilderUsage,
|
||||
Rule::SuspiciousXMLSaxUsage,
|
||||
Rule::SuspiciousXMLMiniDOMUsage,
|
||||
Rule::SuspiciousXMLPullDOMUsage,
|
||||
Rule::SuspiciousXMLETreeUsage,
|
||||
Rule::SuspiciousUnverifiedContextUsage,
|
||||
Rule::SuspiciousTelnetUsage,
|
||||
Rule::SuspiciousFTPLibUsage,
|
||||
]) {
|
||||
flake8_bandit::rules::suspicious_function_call(self, expr);
|
||||
if self.settings.rules.enabled(Rule::DeniedFunctionCall) {
|
||||
flake8_bandit::rules::denied_function_call(self, expr);
|
||||
}
|
||||
|
||||
// flake8-bugbear
|
||||
@@ -2873,30 +2857,30 @@ where
|
||||
|
||||
// flake8-use-pathlib
|
||||
if self.settings.rules.any_enabled(&[
|
||||
Rule::OsPathAbspath,
|
||||
Rule::OsChmod,
|
||||
Rule::OsMkdir,
|
||||
Rule::OsMakedirs,
|
||||
Rule::OsRename,
|
||||
Rule::PathlibAbspath,
|
||||
Rule::PathlibChmod,
|
||||
Rule::PathlibMkdir,
|
||||
Rule::PathlibMakedirs,
|
||||
Rule::PathlibRename,
|
||||
Rule::PathlibReplace,
|
||||
Rule::OsRmdir,
|
||||
Rule::OsRemove,
|
||||
Rule::OsUnlink,
|
||||
Rule::OsGetcwd,
|
||||
Rule::OsPathExists,
|
||||
Rule::OsPathExpanduser,
|
||||
Rule::OsPathIsdir,
|
||||
Rule::OsPathIsfile,
|
||||
Rule::OsPathIslink,
|
||||
Rule::OsReadlink,
|
||||
Rule::OsStat,
|
||||
Rule::OsPathIsabs,
|
||||
Rule::OsPathJoin,
|
||||
Rule::OsPathBasename,
|
||||
Rule::OsPathSamefile,
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
Rule::PyPath,
|
||||
Rule::PathlibRmdir,
|
||||
Rule::PathlibRemove,
|
||||
Rule::PathlibUnlink,
|
||||
Rule::PathlibGetcwd,
|
||||
Rule::PathlibExists,
|
||||
Rule::PathlibExpanduser,
|
||||
Rule::PathlibIsDir,
|
||||
Rule::PathlibIsFile,
|
||||
Rule::PathlibIsLink,
|
||||
Rule::PathlibReadlink,
|
||||
Rule::PathlibStat,
|
||||
Rule::PathlibIsAbs,
|
||||
Rule::PathlibJoin,
|
||||
Rule::PathlibBasename,
|
||||
Rule::PathlibSamefile,
|
||||
Rule::PathlibSplitext,
|
||||
Rule::PathlibOpen,
|
||||
Rule::PathlibPyPath,
|
||||
]) {
|
||||
flake8_use_pathlib::helpers::replaceable_by_pathlib(self, func);
|
||||
}
|
||||
@@ -3892,7 +3876,6 @@ where
|
||||
range: Range::from(arg),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3936,7 +3919,6 @@ where
|
||||
range: Range::from(pattern),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -4115,7 +4097,6 @@ impl<'a> Checker<'a> {
|
||||
typing_usage: None,
|
||||
source: None,
|
||||
context: ExecutionContext::Runtime,
|
||||
exceptions: Exceptions::empty(),
|
||||
});
|
||||
scope.add(builtin, id);
|
||||
}
|
||||
@@ -4148,7 +4129,7 @@ impl<'a> Checker<'a> {
|
||||
self.ctx.bindings[*index].mark_used(scope_id, Range::from(expr), context);
|
||||
|
||||
if self.ctx.bindings[*index].kind.is_annotation()
|
||||
&& self.ctx.in_deferred_string_type_definition.is_none()
|
||||
&& !self.ctx.in_deferred_string_type_definition
|
||||
&& !self.ctx.in_deferred_type_definition
|
||||
{
|
||||
continue;
|
||||
@@ -4340,7 +4321,6 @@ impl<'a> Checker<'a> {
|
||||
range: Range::from(expr),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -4361,7 +4341,6 @@ impl<'a> Checker<'a> {
|
||||
range: Range::from(expr),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -4378,7 +4357,6 @@ impl<'a> Checker<'a> {
|
||||
range: Range::from(expr),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -4444,7 +4422,6 @@ impl<'a> Checker<'a> {
|
||||
range: Range::from(expr),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -4461,7 +4438,6 @@ impl<'a> Checker<'a> {
|
||||
range: Range::from(expr),
|
||||
source: Some(self.ctx.current_stmt().clone()),
|
||||
context: self.ctx.execution_context(),
|
||||
exceptions: self.ctx.exceptions(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -4537,19 +4513,20 @@ impl<'a> Checker<'a> {
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
let mut stacks = Vec::with_capacity(self.deferred.string_type_definitions.len());
|
||||
let mut stacks = vec![];
|
||||
self.deferred.string_type_definitions.reverse();
|
||||
while let Some((range, value, (in_annotation, in_type_checking_block), deferral)) =
|
||||
while let Some((range, expression, (in_annotation, in_type_checking_block), deferral)) =
|
||||
self.deferred.string_type_definitions.pop()
|
||||
{
|
||||
if let Ok((expr, kind)) = parse_type_annotation(value, range, self.locator) {
|
||||
if let Ok(mut expr) = parser::parse_expression(expression, "<filename>") {
|
||||
if in_annotation && self.ctx.annotations_future_enabled {
|
||||
if self.settings.rules.enabled(Rule::QuotedAnnotation) {
|
||||
pyupgrade::rules::quoted_annotation(self, value, range);
|
||||
pyupgrade::rules::quoted_annotation(self, expression, range);
|
||||
}
|
||||
}
|
||||
relocate_expr(&mut expr, range);
|
||||
allocator.push(expr);
|
||||
stacks.push((kind, (in_annotation, in_type_checking_block), deferral));
|
||||
stacks.push(((in_annotation, in_type_checking_block), deferral));
|
||||
} else {
|
||||
if self
|
||||
.settings
|
||||
@@ -4558,14 +4535,14 @@ impl<'a> Checker<'a> {
|
||||
{
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::ForwardAnnotationSyntaxError {
|
||||
body: value.to_string(),
|
||||
body: expression.to_string(),
|
||||
},
|
||||
range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (expr, (kind, (in_annotation, in_type_checking_block), (scopes, parents))) in
|
||||
for (expr, ((in_annotation, in_type_checking_block), (scopes, parents))) in
|
||||
allocator.iter().zip(stacks)
|
||||
{
|
||||
self.ctx.scope_stack = scopes;
|
||||
@@ -4573,9 +4550,9 @@ impl<'a> Checker<'a> {
|
||||
self.ctx.in_annotation = in_annotation;
|
||||
self.ctx.in_type_checking_block = in_type_checking_block;
|
||||
self.ctx.in_type_definition = true;
|
||||
self.ctx.in_deferred_string_type_definition = Some(kind);
|
||||
self.ctx.in_deferred_string_type_definition = true;
|
||||
self.visit_expr(expr);
|
||||
self.ctx.in_deferred_string_type_definition = None;
|
||||
self.ctx.in_deferred_string_type_definition = false;
|
||||
self.ctx.in_type_definition = false;
|
||||
}
|
||||
}
|
||||
@@ -4919,11 +4896,8 @@ impl<'a> Checker<'a> {
|
||||
// Collect all unused imports by location. (Multiple unused imports at the same
|
||||
// location indicates an `import from`.)
|
||||
type UnusedImport<'a> = (&'a str, &'a Range);
|
||||
type BindingContext<'a, 'b> = (
|
||||
&'a RefEquality<'b, Stmt>,
|
||||
Option<&'a RefEquality<'b, Stmt>>,
|
||||
Exceptions,
|
||||
);
|
||||
type BindingContext<'a, 'b> =
|
||||
(&'a RefEquality<'b, Stmt>, Option<&'a RefEquality<'b, Stmt>>);
|
||||
|
||||
let mut unused: FxHashMap<BindingContext, Vec<UnusedImport>> = FxHashMap::default();
|
||||
let mut ignored: FxHashMap<BindingContext, Vec<UnusedImport>> =
|
||||
@@ -4945,7 +4919,6 @@ impl<'a> Checker<'a> {
|
||||
|
||||
let defined_by = binding.source.as_ref().unwrap();
|
||||
let defined_in = self.ctx.child_to_parent.get(defined_by);
|
||||
let exceptions = binding.exceptions;
|
||||
let child: &Stmt = defined_by.into();
|
||||
|
||||
let diagnostic_lineno = binding.range.location.row();
|
||||
@@ -4963,30 +4936,27 @@ impl<'a> Checker<'a> {
|
||||
})
|
||||
{
|
||||
ignored
|
||||
.entry((defined_by, defined_in, exceptions))
|
||||
.entry((defined_by, defined_in))
|
||||
.or_default()
|
||||
.push((full_name, &binding.range));
|
||||
} else {
|
||||
unused
|
||||
.entry((defined_by, defined_in, exceptions))
|
||||
.entry((defined_by, defined_in))
|
||||
.or_default()
|
||||
.push((full_name, &binding.range));
|
||||
}
|
||||
}
|
||||
|
||||
let in_init =
|
||||
let ignore_init =
|
||||
self.settings.ignore_init_module_imports && self.path.ends_with("__init__.py");
|
||||
for ((defined_by, defined_in, exceptions), unused_imports) in unused
|
||||
for ((defined_by, defined_in), unused_imports) in unused
|
||||
.into_iter()
|
||||
.sorted_by_key(|((defined_by, ..), ..)| defined_by.location)
|
||||
.sorted_by_key(|((defined_by, _), _)| defined_by.location)
|
||||
{
|
||||
let child: &Stmt = defined_by.into();
|
||||
let parent: Option<&Stmt> = defined_in.map(Into::into);
|
||||
let multiple = unused_imports.len() > 1;
|
||||
let in_except_handler = exceptions
|
||||
.intersects(Exceptions::MODULE_NOT_FOUND_ERROR | Exceptions::IMPORT_ERROR);
|
||||
|
||||
let fix = if !in_init && !in_except_handler && self.patch(Rule::UnusedImport) {
|
||||
let fix = if !ignore_init && self.patch(Rule::UnusedImport) {
|
||||
let deleted: Vec<&Stmt> = self.deletions.iter().map(Into::into).collect();
|
||||
match autofix::helpers::remove_unused_imports(
|
||||
unused_imports.iter().map(|(full_name, _)| *full_name),
|
||||
@@ -5012,17 +4982,12 @@ impl<'a> Checker<'a> {
|
||||
None
|
||||
};
|
||||
|
||||
let multiple = unused_imports.len() > 1;
|
||||
for (full_name, range) in unused_imports {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
pyflakes::rules::UnusedImport {
|
||||
name: full_name.to_string(),
|
||||
context: if in_except_handler {
|
||||
Some(pyflakes::rules::UnusedImportContext::ExceptHandler)
|
||||
} else if in_init {
|
||||
Some(pyflakes::rules::UnusedImportContext::Init)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
ignore_init,
|
||||
multiple,
|
||||
},
|
||||
*range,
|
||||
@@ -5038,25 +5003,17 @@ impl<'a> Checker<'a> {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
for ((defined_by, .., exceptions), unused_imports) in ignored
|
||||
for ((defined_by, ..), unused_imports) in ignored
|
||||
.into_iter()
|
||||
.sorted_by_key(|((defined_by, ..), ..)| defined_by.location)
|
||||
.sorted_by_key(|((defined_by, _), _)| defined_by.location)
|
||||
{
|
||||
let child: &Stmt = defined_by.into();
|
||||
let multiple = unused_imports.len() > 1;
|
||||
let in_except_handler = exceptions
|
||||
.intersects(Exceptions::MODULE_NOT_FOUND_ERROR | Exceptions::IMPORT_ERROR);
|
||||
for (full_name, range) in unused_imports {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
pyflakes::rules::UnusedImport {
|
||||
name: full_name.to_string(),
|
||||
context: if in_except_handler {
|
||||
Some(pyflakes::rules::UnusedImportContext::ExceptHandler)
|
||||
} else if in_init {
|
||||
Some(pyflakes::rules::UnusedImportContext::Init)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
ignore_init,
|
||||
multiple,
|
||||
},
|
||||
*range,
|
||||
|
||||
@@ -171,9 +171,7 @@ pub fn check_logical_lines(
|
||||
#[cfg(not(debug_assertions))]
|
||||
let should_fix = false;
|
||||
|
||||
for diagnostic in
|
||||
missing_whitespace(&line.text, start_loc.row(), should_fix, indent_level)
|
||||
{
|
||||
for diagnostic in missing_whitespace(&line.text, start_loc.row(), should_fix) {
|
||||
if settings.rules.enabled(diagnostic.kind.rule()) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! `NoQA` enforcement and validation.
|
||||
|
||||
use log::warn;
|
||||
use nohash_hasher::IntMap;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
@@ -9,7 +10,7 @@ use ruff_python_ast::types::Range;
|
||||
|
||||
use crate::codes::NoqaCode;
|
||||
use crate::noqa;
|
||||
use crate::noqa::{Directive, FileExemption};
|
||||
use crate::noqa::{extract_file_exemption, Directive, Exemption};
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
|
||||
@@ -25,47 +26,63 @@ pub fn check_noqa(
|
||||
) -> Vec<usize> {
|
||||
let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA);
|
||||
|
||||
let lines: Vec<&str> = contents.universal_newlines().collect();
|
||||
// Whether the file is exempted from all checks.
|
||||
let mut file_exempted = false;
|
||||
|
||||
// Identify any codes that are globally exempted (within the current file).
|
||||
let exemption = noqa::file_exemption(&lines, commented_lines);
|
||||
// Codes that are globally exempted (within the current file).
|
||||
let mut file_exemptions: Vec<NoqaCode> = vec![];
|
||||
|
||||
// Map from line number to `noqa` directive on that line, along with any codes
|
||||
// that were matched by the directive.
|
||||
let mut noqa_directives: IntMap<usize, (Directive, Vec<NoqaCode>)> = IntMap::default();
|
||||
|
||||
// Extract all `noqa` directives.
|
||||
if enforce_noqa {
|
||||
for lineno in commented_lines {
|
||||
// Indices of diagnostics that were ignored by a `noqa` directive.
|
||||
let mut ignored_diagnostics = vec![];
|
||||
|
||||
let lines: Vec<&str> = contents.universal_newlines().collect();
|
||||
for lineno in commented_lines {
|
||||
match extract_file_exemption(lines[lineno - 1]) {
|
||||
Exemption::All => {
|
||||
file_exempted = true;
|
||||
}
|
||||
Exemption::Codes(codes) => {
|
||||
file_exemptions.extend(codes.into_iter().filter_map(|code| {
|
||||
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code)) {
|
||||
Some(rule.noqa_code())
|
||||
} else {
|
||||
warn!("Invalid code provided to `# ruff: noqa`: {}", code);
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
Exemption::None => {}
|
||||
}
|
||||
|
||||
if enforce_noqa {
|
||||
noqa_directives
|
||||
.entry(lineno - 1)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[lineno - 1]), vec![]));
|
||||
}
|
||||
}
|
||||
|
||||
// Indices of diagnostics that were ignored by a `noqa` directive.
|
||||
let mut ignored_diagnostics = vec![];
|
||||
|
||||
// Remove any ignored diagnostics.
|
||||
for (index, diagnostic) in diagnostics.iter().enumerate() {
|
||||
if matches!(diagnostic.kind.rule(), Rule::BlanketNOQA) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match &exemption {
|
||||
FileExemption::All => {
|
||||
// If the file is exempted, ignore all diagnostics.
|
||||
// If the file is exempted, ignore all diagnostics.
|
||||
if file_exempted {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the diagnostic is ignored by a global exemption, ignore it.
|
||||
if !file_exemptions.is_empty() {
|
||||
if file_exemptions.contains(&diagnostic.kind.rule().noqa_code()) {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
FileExemption::Codes(codes) => {
|
||||
// If the diagnostic is ignored by a global exemption, ignore it.
|
||||
if codes.contains(&diagnostic.kind.rule().noqa_code()) {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
FileExemption::None => {}
|
||||
}
|
||||
|
||||
// Is the violation ignored by a `noqa` directive on the parent line?
|
||||
|
||||
@@ -168,7 +168,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Pylint, "E0101") => Rule::ReturnInInit,
|
||||
(Pylint, "E0116") => Rule::ContinueInFinally,
|
||||
(Pylint, "E0117") => Rule::NonlocalWithoutBinding,
|
||||
(Pylint, "E0118") => Rule::LoadBeforeGlobalDeclaration,
|
||||
(Pylint, "E0118") => Rule::UsePriorToGlobalDeclaration,
|
||||
(Pylint, "E0604") => Rule::InvalidAllObject,
|
||||
(Pylint, "E0605") => Rule::InvalidAllFormat,
|
||||
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
|
||||
@@ -327,7 +327,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Simplify, "101") => Rule::DuplicateIsinstanceCall,
|
||||
(Flake8Simplify, "102") => Rule::CollapsibleIf,
|
||||
(Flake8Simplify, "103") => Rule::NeedlessBool,
|
||||
(Flake8Simplify, "105") => Rule::SuppressibleException,
|
||||
(Flake8Simplify, "105") => Rule::UseContextlibSuppress,
|
||||
(Flake8Simplify, "107") => Rule::ReturnInTryExceptFinally,
|
||||
(Flake8Simplify, "108") => Rule::IfElseBlockInsteadOfIfExp,
|
||||
(Flake8Simplify, "109") => Rule::CompareWithTuple,
|
||||
@@ -463,6 +463,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Eradicate, "001") => Rule::CommentedOutCode,
|
||||
|
||||
// flake8-bandit
|
||||
(Flake8Bandit, "001") => Rule::DeniedFunctionCall,
|
||||
(Flake8Bandit, "101") => Rule::Assert,
|
||||
(Flake8Bandit, "102") => Rule::ExecBuiltin,
|
||||
(Flake8Bandit, "103") => Rule::BadFilePermissions,
|
||||
@@ -470,37 +471,16 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Bandit, "105") => Rule::HardcodedPasswordString,
|
||||
(Flake8Bandit, "106") => Rule::HardcodedPasswordFuncArg,
|
||||
(Flake8Bandit, "107") => Rule::HardcodedPasswordDefault,
|
||||
(Flake8Bandit, "608") => Rule::HardcodedSQLExpression,
|
||||
(Flake8Bandit, "108") => Rule::HardcodedTempFile,
|
||||
(Flake8Bandit, "110") => Rule::TryExceptPass,
|
||||
(Flake8Bandit, "112") => Rule::TryExceptContinue,
|
||||
(Flake8Bandit, "113") => Rule::RequestWithoutTimeout,
|
||||
(Flake8Bandit, "301") => Rule::SuspiciousPickleUsage,
|
||||
(Flake8Bandit, "302") => Rule::SuspiciousMarshalUsage,
|
||||
(Flake8Bandit, "303") => Rule::SuspiciousInsecureHashUsage,
|
||||
(Flake8Bandit, "304") => Rule::SuspiciousInsecureCipherUsage,
|
||||
(Flake8Bandit, "305") => Rule::SuspiciousInsecureCipherModeUsage,
|
||||
(Flake8Bandit, "306") => Rule::SuspiciousMktempUsage,
|
||||
(Flake8Bandit, "307") => Rule::SuspiciousEvalUsage,
|
||||
(Flake8Bandit, "308") => Rule::SuspiciousMarkSafeUsage,
|
||||
(Flake8Bandit, "310") => Rule::SuspiciousURLOpenUsage,
|
||||
(Flake8Bandit, "311") => Rule::SuspiciousNonCryptographicRandomUsage,
|
||||
(Flake8Bandit, "312") => Rule::SuspiciousTelnetUsage,
|
||||
(Flake8Bandit, "313") => Rule::SuspiciousXMLCElementTreeUsage,
|
||||
(Flake8Bandit, "314") => Rule::SuspiciousXMLElementTreeUsage,
|
||||
(Flake8Bandit, "315") => Rule::SuspiciousXMLExpatReaderUsage,
|
||||
(Flake8Bandit, "316") => Rule::SuspiciousXMLExpatBuilderUsage,
|
||||
(Flake8Bandit, "317") => Rule::SuspiciousXMLSaxUsage,
|
||||
(Flake8Bandit, "318") => Rule::SuspiciousXMLMiniDOMUsage,
|
||||
(Flake8Bandit, "319") => Rule::SuspiciousXMLPullDOMUsage,
|
||||
(Flake8Bandit, "320") => Rule::SuspiciousXMLETreeUsage,
|
||||
(Flake8Bandit, "321") => Rule::SuspiciousFTPLibUsage,
|
||||
(Flake8Bandit, "323") => Rule::SuspiciousUnverifiedContextUsage,
|
||||
(Flake8Bandit, "324") => Rule::HashlibInsecureHashFunction,
|
||||
(Flake8Bandit, "501") => Rule::RequestWithNoCertValidation,
|
||||
(Flake8Bandit, "506") => Rule::UnsafeYAMLLoad,
|
||||
(Flake8Bandit, "508") => Rule::SnmpInsecureVersion,
|
||||
(Flake8Bandit, "509") => Rule::SnmpWeakCryptography,
|
||||
(Flake8Bandit, "608") => Rule::HardcodedSQLExpression,
|
||||
(Flake8Bandit, "612") => Rule::LoggingConfigInsecureListen,
|
||||
(Flake8Bandit, "701") => Rule::Jinja2AutoescapeFalse,
|
||||
|
||||
@@ -638,31 +618,31 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Tryceratops, "401") => Rule::VerboseLogMessage,
|
||||
|
||||
// flake8-use-pathlib
|
||||
(Flake8UsePathlib, "100") => Rule::OsPathAbspath,
|
||||
(Flake8UsePathlib, "101") => Rule::OsChmod,
|
||||
(Flake8UsePathlib, "102") => Rule::OsMkdir,
|
||||
(Flake8UsePathlib, "103") => Rule::OsMakedirs,
|
||||
(Flake8UsePathlib, "104") => Rule::OsRename,
|
||||
(Flake8UsePathlib, "100") => Rule::PathlibAbspath,
|
||||
(Flake8UsePathlib, "101") => Rule::PathlibChmod,
|
||||
(Flake8UsePathlib, "102") => Rule::PathlibMkdir,
|
||||
(Flake8UsePathlib, "103") => Rule::PathlibMakedirs,
|
||||
(Flake8UsePathlib, "104") => Rule::PathlibRename,
|
||||
(Flake8UsePathlib, "105") => Rule::PathlibReplace,
|
||||
(Flake8UsePathlib, "106") => Rule::OsRmdir,
|
||||
(Flake8UsePathlib, "107") => Rule::OsRemove,
|
||||
(Flake8UsePathlib, "108") => Rule::OsUnlink,
|
||||
(Flake8UsePathlib, "109") => Rule::OsGetcwd,
|
||||
(Flake8UsePathlib, "110") => Rule::OsPathExists,
|
||||
(Flake8UsePathlib, "111") => Rule::OsPathExpanduser,
|
||||
(Flake8UsePathlib, "112") => Rule::OsPathIsdir,
|
||||
(Flake8UsePathlib, "113") => Rule::OsPathIsfile,
|
||||
(Flake8UsePathlib, "114") => Rule::OsPathIslink,
|
||||
(Flake8UsePathlib, "115") => Rule::OsReadlink,
|
||||
(Flake8UsePathlib, "116") => Rule::OsStat,
|
||||
(Flake8UsePathlib, "117") => Rule::OsPathIsabs,
|
||||
(Flake8UsePathlib, "118") => Rule::OsPathJoin,
|
||||
(Flake8UsePathlib, "119") => Rule::OsPathBasename,
|
||||
(Flake8UsePathlib, "120") => Rule::OsPathDirname,
|
||||
(Flake8UsePathlib, "121") => Rule::OsPathSamefile,
|
||||
(Flake8UsePathlib, "122") => Rule::OsPathSplitext,
|
||||
(Flake8UsePathlib, "123") => Rule::BuiltinOpen,
|
||||
(Flake8UsePathlib, "124") => Rule::PyPath,
|
||||
(Flake8UsePathlib, "106") => Rule::PathlibRmdir,
|
||||
(Flake8UsePathlib, "107") => Rule::PathlibRemove,
|
||||
(Flake8UsePathlib, "108") => Rule::PathlibUnlink,
|
||||
(Flake8UsePathlib, "109") => Rule::PathlibGetcwd,
|
||||
(Flake8UsePathlib, "110") => Rule::PathlibExists,
|
||||
(Flake8UsePathlib, "111") => Rule::PathlibExpanduser,
|
||||
(Flake8UsePathlib, "112") => Rule::PathlibIsDir,
|
||||
(Flake8UsePathlib, "113") => Rule::PathlibIsFile,
|
||||
(Flake8UsePathlib, "114") => Rule::PathlibIsLink,
|
||||
(Flake8UsePathlib, "115") => Rule::PathlibReadlink,
|
||||
(Flake8UsePathlib, "116") => Rule::PathlibStat,
|
||||
(Flake8UsePathlib, "117") => Rule::PathlibIsAbs,
|
||||
(Flake8UsePathlib, "118") => Rule::PathlibJoin,
|
||||
(Flake8UsePathlib, "119") => Rule::PathlibBasename,
|
||||
(Flake8UsePathlib, "120") => Rule::PathlibDirname,
|
||||
(Flake8UsePathlib, "121") => Rule::PathlibSamefile,
|
||||
(Flake8UsePathlib, "122") => Rule::PathlibSplitext,
|
||||
(Flake8UsePathlib, "123") => Rule::PathlibOpen,
|
||||
(Flake8UsePathlib, "124") => Rule::PathlibPyPath,
|
||||
|
||||
// flake8-logging-format
|
||||
(Flake8LoggingFormat, "001") => Rule::LoggingStringFormat,
|
||||
@@ -699,7 +679,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Django, "006") => Rule::DjangoExcludeWithModelForm,
|
||||
(Flake8Django, "007") => Rule::DjangoAllWithModelForm,
|
||||
(Flake8Django, "008") => Rule::DjangoModelWithoutDunderStr,
|
||||
(Flake8Django, "012") => Rule::DjangoUnorderedBodyContentInModel,
|
||||
(Flake8Django, "013") => Rule::DjangoNonLeadingReceiverDecorator,
|
||||
|
||||
_ => return None,
|
||||
|
||||
@@ -28,6 +28,49 @@ static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
});
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Exemption<'a> {
|
||||
None,
|
||||
All,
|
||||
Codes(Vec<&'a str>),
|
||||
}
|
||||
|
||||
/// Return `true` if a file is exempt from checking based on the contents of the
|
||||
/// given line.
|
||||
pub fn extract_file_exemption(line: &str) -> Exemption {
|
||||
let line = line.trim_start();
|
||||
|
||||
if line.starts_with("# flake8: noqa")
|
||||
|| line.starts_with("# flake8: NOQA")
|
||||
|| line.starts_with("# flake8: NoQA")
|
||||
{
|
||||
return Exemption::All;
|
||||
}
|
||||
|
||||
if let Some(remainder) = line
|
||||
.strip_prefix("# ruff: noqa")
|
||||
.or_else(|| line.strip_prefix("# ruff: NOQA"))
|
||||
.or_else(|| line.strip_prefix("# ruff: NoQA"))
|
||||
{
|
||||
if remainder.is_empty() {
|
||||
return Exemption::All;
|
||||
} else if let Some(codes) = remainder.strip_prefix(':') {
|
||||
let codes: Vec<&str> = SPLIT_COMMA_REGEX
|
||||
.split(codes.trim())
|
||||
.map(str::trim)
|
||||
.filter(|code| !code.is_empty())
|
||||
.collect();
|
||||
if codes.is_empty() {
|
||||
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
|
||||
}
|
||||
return Exemption::Codes(codes);
|
||||
}
|
||||
warn!("Unexpected suffix on `noqa` directive: \"{line}\"");
|
||||
}
|
||||
|
||||
Exemption::None
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Directive<'a> {
|
||||
None,
|
||||
@@ -76,47 +119,6 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
|
||||
}
|
||||
}
|
||||
|
||||
enum ParsedExemption<'a> {
|
||||
None,
|
||||
All,
|
||||
Codes(Vec<&'a str>),
|
||||
}
|
||||
|
||||
/// Return a [`ParsedExemption`] for a given comment line.
|
||||
fn parse_file_exemption(line: &str) -> ParsedExemption {
|
||||
let line = line.trim_start();
|
||||
|
||||
if line.starts_with("# flake8: noqa")
|
||||
|| line.starts_with("# flake8: NOQA")
|
||||
|| line.starts_with("# flake8: NoQA")
|
||||
{
|
||||
return ParsedExemption::All;
|
||||
}
|
||||
|
||||
if let Some(remainder) = line
|
||||
.strip_prefix("# ruff: noqa")
|
||||
.or_else(|| line.strip_prefix("# ruff: NOQA"))
|
||||
.or_else(|| line.strip_prefix("# ruff: NoQA"))
|
||||
{
|
||||
if remainder.is_empty() {
|
||||
return ParsedExemption::All;
|
||||
} else if let Some(codes) = remainder.strip_prefix(':') {
|
||||
let codes: Vec<&str> = SPLIT_COMMA_REGEX
|
||||
.split(codes.trim())
|
||||
.map(str::trim)
|
||||
.filter(|code| !code.is_empty())
|
||||
.collect();
|
||||
if codes.is_empty() {
|
||||
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
|
||||
}
|
||||
return ParsedExemption::Codes(codes);
|
||||
}
|
||||
warn!("Unexpected suffix on `noqa` directive: \"{line}\"");
|
||||
}
|
||||
|
||||
ParsedExemption::None
|
||||
}
|
||||
|
||||
/// Returns `true` if the string list of `codes` includes `code` (or an alias
|
||||
/// thereof).
|
||||
pub fn includes(needle: Rule, haystack: &[&str]) -> bool {
|
||||
@@ -145,43 +147,6 @@ pub fn rule_is_ignored(
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FileExemption {
|
||||
None,
|
||||
All,
|
||||
Codes(Vec<NoqaCode>),
|
||||
}
|
||||
|
||||
/// Extract the [`FileExemption`] for a given Python source file, enumerating any rules that are
|
||||
/// globally ignored within the file.
|
||||
pub fn file_exemption(lines: &[&str], commented_lines: &[usize]) -> FileExemption {
|
||||
let mut exempt_codes: Vec<NoqaCode> = vec![];
|
||||
|
||||
for lineno in commented_lines {
|
||||
match parse_file_exemption(lines[lineno - 1]) {
|
||||
ParsedExemption::All => {
|
||||
return FileExemption::All;
|
||||
}
|
||||
ParsedExemption::Codes(codes) => {
|
||||
exempt_codes.extend(codes.into_iter().filter_map(|code| {
|
||||
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code)) {
|
||||
Some(rule.noqa_code())
|
||||
} else {
|
||||
warn!("Invalid code provided to `# ruff: noqa`: {}", code);
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
ParsedExemption::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
if exempt_codes.is_empty() {
|
||||
FileExemption::None
|
||||
} else {
|
||||
FileExemption::Codes(exempt_codes)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_noqa(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
@@ -211,26 +176,44 @@ fn add_noqa_inner(
|
||||
// Map of line number to set of (non-ignored) diagnostic codes that are triggered on that line.
|
||||
let mut matches_by_line: FxHashMap<usize, RuleSet> = FxHashMap::default();
|
||||
|
||||
let lines: Vec<&str> = contents.universal_newlines().collect();
|
||||
|
||||
// Whether the file is exempted from all checks.
|
||||
let mut file_exempted = false;
|
||||
|
||||
// Codes that are globally exempted (within the current file).
|
||||
let exemption = file_exemption(&lines, commented_lines);
|
||||
let mut file_exemptions: Vec<NoqaCode> = vec![];
|
||||
|
||||
let lines: Vec<&str> = contents.universal_newlines().collect();
|
||||
for lineno in commented_lines {
|
||||
match extract_file_exemption(lines[lineno - 1]) {
|
||||
Exemption::All => {
|
||||
file_exempted = true;
|
||||
}
|
||||
Exemption::Codes(codes) => {
|
||||
file_exemptions.extend(codes.into_iter().filter_map(|code| {
|
||||
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code)) {
|
||||
Some(rule.noqa_code())
|
||||
} else {
|
||||
warn!("Invalid code provided to `# ruff: noqa`: {}", code);
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
Exemption::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark any non-ignored diagnostics.
|
||||
for diagnostic in diagnostics {
|
||||
match &exemption {
|
||||
FileExemption::All => {
|
||||
// If the file is exempted, don't add any noqa directives.
|
||||
// If the file is exempted, don't add any noqa directives.
|
||||
if file_exempted {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the diagnostic is ignored by a global exemption, don't add a noqa directive.
|
||||
if !file_exemptions.is_empty() {
|
||||
if file_exemptions.contains(&diagnostic.kind.rule().noqa_code()) {
|
||||
continue;
|
||||
}
|
||||
FileExemption::Codes(codes) => {
|
||||
// If the diagnostic is ignored by a global exemption, don't add a noqa directive.
|
||||
if codes.contains(&diagnostic.kind.rule().noqa_code()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
FileExemption::None => {}
|
||||
}
|
||||
|
||||
// Is the violation ignored by a `noqa` directive on the parent line?
|
||||
|
||||
@@ -167,7 +167,7 @@ ruff_macros::register_rules!(
|
||||
rules::pylint::rules::UselessImportAlias,
|
||||
rules::pylint::rules::UnnecessaryDirectLambdaCall,
|
||||
rules::pylint::rules::NonlocalWithoutBinding,
|
||||
rules::pylint::rules::LoadBeforeGlobalDeclaration,
|
||||
rules::pylint::rules::UsePriorToGlobalDeclaration,
|
||||
rules::pylint::rules::AwaitOutsideAsync,
|
||||
rules::pylint::rules::PropertyWithParameters,
|
||||
rules::pylint::rules::ReturnInInit,
|
||||
@@ -299,7 +299,7 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_simplify::rules::DuplicateIsinstanceCall,
|
||||
rules::flake8_simplify::rules::CollapsibleIf,
|
||||
rules::flake8_simplify::rules::NeedlessBool,
|
||||
rules::flake8_simplify::rules::SuppressibleException,
|
||||
rules::flake8_simplify::rules::UseContextlibSuppress,
|
||||
rules::flake8_simplify::rules::ReturnInTryExceptFinally,
|
||||
rules::flake8_simplify::rules::IfElseBlockInsteadOfIfExp,
|
||||
rules::flake8_simplify::rules::CompareWithTuple,
|
||||
@@ -429,45 +429,25 @@ ruff_macros::register_rules!(
|
||||
rules::eradicate::rules::CommentedOutCode,
|
||||
// flake8-bandit
|
||||
rules::flake8_bandit::rules::Assert,
|
||||
rules::flake8_bandit::rules::BadFilePermissions,
|
||||
rules::flake8_bandit::rules::DeniedFunctionCall,
|
||||
rules::flake8_bandit::rules::ExecBuiltin,
|
||||
rules::flake8_bandit::rules::BadFilePermissions,
|
||||
rules::flake8_bandit::rules::HardcodedBindAllInterfaces,
|
||||
rules::flake8_bandit::rules::HardcodedPasswordDefault,
|
||||
rules::flake8_bandit::rules::HardcodedPasswordFuncArg,
|
||||
rules::flake8_bandit::rules::HardcodedPasswordString,
|
||||
rules::flake8_bandit::rules::HardcodedPasswordFuncArg,
|
||||
rules::flake8_bandit::rules::HardcodedPasswordDefault,
|
||||
rules::flake8_bandit::rules::HardcodedSQLExpression,
|
||||
rules::flake8_bandit::rules::HardcodedTempFile,
|
||||
rules::flake8_bandit::rules::HashlibInsecureHashFunction,
|
||||
rules::flake8_bandit::rules::Jinja2AutoescapeFalse,
|
||||
rules::flake8_bandit::rules::LoggingConfigInsecureListen,
|
||||
rules::flake8_bandit::rules::RequestWithNoCertValidation,
|
||||
rules::flake8_bandit::rules::TryExceptPass,
|
||||
rules::flake8_bandit::rules::TryExceptContinue,
|
||||
rules::flake8_bandit::rules::RequestWithoutTimeout,
|
||||
rules::flake8_bandit::rules::HashlibInsecureHashFunction,
|
||||
rules::flake8_bandit::rules::RequestWithNoCertValidation,
|
||||
rules::flake8_bandit::rules::UnsafeYAMLLoad,
|
||||
rules::flake8_bandit::rules::SnmpInsecureVersion,
|
||||
rules::flake8_bandit::rules::SnmpWeakCryptography,
|
||||
rules::flake8_bandit::rules::SuspiciousEvalUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousFTPLibUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousInsecureCipherUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousInsecureCipherModeUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousInsecureHashUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousMarkSafeUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousMarshalUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousMktempUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousNonCryptographicRandomUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousPickleUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousTelnetUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousURLOpenUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousUnverifiedContextUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLCElementTreeUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLETreeUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLElementTreeUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLExpatBuilderUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLExpatReaderUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLMiniDOMUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLPullDOMUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLSaxUsage,
|
||||
rules::flake8_bandit::rules::TryExceptContinue,
|
||||
rules::flake8_bandit::rules::TryExceptPass,
|
||||
rules::flake8_bandit::rules::UnsafeYAMLLoad,
|
||||
rules::flake8_bandit::rules::LoggingConfigInsecureListen,
|
||||
rules::flake8_bandit::rules::Jinja2AutoescapeFalse,
|
||||
// flake8-boolean-trap
|
||||
rules::flake8_boolean_trap::rules::BooleanPositionalArgInFunctionDefinition,
|
||||
rules::flake8_boolean_trap::rules::BooleanDefaultValueInFunctionDefinition,
|
||||
@@ -587,31 +567,31 @@ ruff_macros::register_rules!(
|
||||
rules::tryceratops::rules::ErrorInsteadOfException,
|
||||
rules::tryceratops::rules::VerboseLogMessage,
|
||||
// flake8-use-pathlib
|
||||
rules::flake8_use_pathlib::violations::OsPathAbspath,
|
||||
rules::flake8_use_pathlib::violations::OsChmod,
|
||||
rules::flake8_use_pathlib::violations::OsMkdir,
|
||||
rules::flake8_use_pathlib::violations::OsMakedirs,
|
||||
rules::flake8_use_pathlib::violations::OsRename,
|
||||
rules::flake8_use_pathlib::violations::PathlibAbspath,
|
||||
rules::flake8_use_pathlib::violations::PathlibChmod,
|
||||
rules::flake8_use_pathlib::violations::PathlibMkdir,
|
||||
rules::flake8_use_pathlib::violations::PathlibMakedirs,
|
||||
rules::flake8_use_pathlib::violations::PathlibRename,
|
||||
rules::flake8_use_pathlib::violations::PathlibReplace,
|
||||
rules::flake8_use_pathlib::violations::OsRmdir,
|
||||
rules::flake8_use_pathlib::violations::OsRemove,
|
||||
rules::flake8_use_pathlib::violations::OsUnlink,
|
||||
rules::flake8_use_pathlib::violations::OsGetcwd,
|
||||
rules::flake8_use_pathlib::violations::OsPathExists,
|
||||
rules::flake8_use_pathlib::violations::OsPathExpanduser,
|
||||
rules::flake8_use_pathlib::violations::OsPathIsdir,
|
||||
rules::flake8_use_pathlib::violations::OsPathIsfile,
|
||||
rules::flake8_use_pathlib::violations::OsPathIslink,
|
||||
rules::flake8_use_pathlib::violations::OsReadlink,
|
||||
rules::flake8_use_pathlib::violations::OsStat,
|
||||
rules::flake8_use_pathlib::violations::OsPathIsabs,
|
||||
rules::flake8_use_pathlib::violations::OsPathJoin,
|
||||
rules::flake8_use_pathlib::violations::OsPathBasename,
|
||||
rules::flake8_use_pathlib::violations::OsPathDirname,
|
||||
rules::flake8_use_pathlib::violations::OsPathSamefile,
|
||||
rules::flake8_use_pathlib::violations::OsPathSplitext,
|
||||
rules::flake8_use_pathlib::violations::BuiltinOpen,
|
||||
rules::flake8_use_pathlib::violations::PyPath,
|
||||
rules::flake8_use_pathlib::violations::PathlibRmdir,
|
||||
rules::flake8_use_pathlib::violations::PathlibRemove,
|
||||
rules::flake8_use_pathlib::violations::PathlibUnlink,
|
||||
rules::flake8_use_pathlib::violations::PathlibGetcwd,
|
||||
rules::flake8_use_pathlib::violations::PathlibExists,
|
||||
rules::flake8_use_pathlib::violations::PathlibExpanduser,
|
||||
rules::flake8_use_pathlib::violations::PathlibIsDir,
|
||||
rules::flake8_use_pathlib::violations::PathlibIsFile,
|
||||
rules::flake8_use_pathlib::violations::PathlibIsLink,
|
||||
rules::flake8_use_pathlib::violations::PathlibReadlink,
|
||||
rules::flake8_use_pathlib::violations::PathlibStat,
|
||||
rules::flake8_use_pathlib::violations::PathlibIsAbs,
|
||||
rules::flake8_use_pathlib::violations::PathlibJoin,
|
||||
rules::flake8_use_pathlib::violations::PathlibBasename,
|
||||
rules::flake8_use_pathlib::violations::PathlibDirname,
|
||||
rules::flake8_use_pathlib::violations::PathlibSamefile,
|
||||
rules::flake8_use_pathlib::violations::PathlibSplitext,
|
||||
rules::flake8_use_pathlib::violations::PathlibOpen,
|
||||
rules::flake8_use_pathlib::violations::PathlibPyPath,
|
||||
// flake8-logging-format
|
||||
rules::flake8_logging_format::violations::LoggingStringFormat,
|
||||
rules::flake8_logging_format::violations::LoggingPercentFormat,
|
||||
@@ -642,7 +622,6 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_django::rules::DjangoExcludeWithModelForm,
|
||||
rules::flake8_django::rules::DjangoAllWithModelForm,
|
||||
rules::flake8_django::rules::DjangoModelWithoutDunderStr,
|
||||
rules::flake8_django::rules::DjangoUnorderedBodyContentInModel,
|
||||
rules::flake8_django::rules::DjangoNonLeadingReceiverDecorator,
|
||||
);
|
||||
|
||||
|
||||
@@ -12,30 +12,30 @@ mod tests {
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::flake8_bandit::settings::Severity;
|
||||
use crate::settings::Settings;
|
||||
use crate::test::test_path;
|
||||
|
||||
#[test_case(Rule::DeniedFunctionCall, Path::new("S001.py"); "S001")]
|
||||
#[test_case(Rule::Assert, Path::new("S101.py"); "S101")]
|
||||
#[test_case(Rule::BadFilePermissions, Path::new("S103.py"); "S103")]
|
||||
#[test_case(Rule::ExecBuiltin, Path::new("S102.py"); "S102")]
|
||||
#[test_case(Rule::BadFilePermissions, Path::new("S103.py"); "S103")]
|
||||
#[test_case(Rule::HardcodedBindAllInterfaces, Path::new("S104.py"); "S104")]
|
||||
#[test_case(Rule::HardcodedPasswordDefault, Path::new("S107.py"); "S107")]
|
||||
#[test_case(Rule::HardcodedPasswordFuncArg, Path::new("S106.py"); "S106")]
|
||||
#[test_case(Rule::HardcodedPasswordString, Path::new("S105.py"); "S105")]
|
||||
#[test_case(Rule::HardcodedPasswordFuncArg, Path::new("S106.py"); "S106")]
|
||||
#[test_case(Rule::HardcodedPasswordDefault, Path::new("S107.py"); "S107")]
|
||||
#[test_case(Rule::HardcodedSQLExpression, Path::new("S608.py"); "S608")]
|
||||
#[test_case(Rule::HardcodedTempFile, Path::new("S108.py"); "S108")]
|
||||
#[test_case(Rule::HashlibInsecureHashFunction, Path::new("S324.py"); "S324")]
|
||||
#[test_case(Rule::Jinja2AutoescapeFalse, Path::new("S701.py"); "S701")]
|
||||
#[test_case(Rule::LoggingConfigInsecureListen, Path::new("S612.py"); "S612")]
|
||||
#[test_case(Rule::RequestWithNoCertValidation, Path::new("S501.py"); "S501")]
|
||||
#[test_case(Rule::RequestWithoutTimeout, Path::new("S113.py"); "S113")]
|
||||
#[test_case(Rule::HashlibInsecureHashFunction, Path::new("S324.py"); "S324")]
|
||||
#[test_case(Rule::RequestWithNoCertValidation, Path::new("S501.py"); "S501")]
|
||||
#[test_case(Rule::UnsafeYAMLLoad, Path::new("S506.py"); "S506")]
|
||||
#[test_case(Rule::SnmpInsecureVersion, Path::new("S508.py"); "S508")]
|
||||
#[test_case(Rule::SnmpWeakCryptography, Path::new("S509.py"); "S509")]
|
||||
#[test_case(Rule::SuspiciousPickleUsage, Path::new("S301.py"); "S301")]
|
||||
#[test_case(Rule::SuspiciousTelnetUsage, Path::new("S312.py"); "S312")]
|
||||
#[test_case(Rule::TryExceptContinue, Path::new("S112.py"); "S112")]
|
||||
#[test_case(Rule::LoggingConfigInsecureListen, Path::new("S612.py"); "S612")]
|
||||
#[test_case(Rule::Jinja2AutoescapeFalse, Path::new("S701.py"); "S701")]
|
||||
#[test_case(Rule::TryExceptPass, Path::new("S110.py"); "S110")]
|
||||
#[test_case(Rule::UnsafeYAMLLoad, Path::new("S506.py"); "S506")]
|
||||
#[test_case(Rule::TryExceptContinue, Path::new("S112.py"); "S112")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
@@ -59,6 +59,7 @@ mod tests {
|
||||
"/foo".to_string(),
|
||||
],
|
||||
check_typed_exception: false,
|
||||
severity: Severity::Low,
|
||||
},
|
||||
..Settings::for_rule(Rule::HardcodedTempFile)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
//! Check for calls to suspicious functions, or calls into suspicious modules.
|
||||
//!
|
||||
//! See: <https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html>
|
||||
use rustpython_parser::ast::{Expr, ExprKind};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::Range;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_bandit::settings::Severity;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Reason {
|
||||
Pickle,
|
||||
Marshal,
|
||||
InsecureHash,
|
||||
InsecureCipher,
|
||||
Mktemp,
|
||||
Eval,
|
||||
MarkSafe,
|
||||
URLOpen,
|
||||
NonCryptographicRandom,
|
||||
UntrustedXML,
|
||||
UnverifiedSSL,
|
||||
Telnet,
|
||||
FTPLib,
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct DeniedFunctionCall {
|
||||
pub reason: Reason,
|
||||
}
|
||||
|
||||
impl Violation for DeniedFunctionCall {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let DeniedFunctionCall { reason } = self;
|
||||
match reason {
|
||||
Reason::Pickle => format!("`pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue"),
|
||||
Reason::Marshal => format!("Deserialization with the `marshal` module is possibly dangerous"),
|
||||
Reason::InsecureHash => format!("Use of insecure MD2, MD4, MD5, or SHA1 hash function"),
|
||||
Reason::InsecureCipher => format!("Use of insecure cipher or cipher mode, replace with a known secure cipher such as AES"),
|
||||
Reason::Mktemp => format!("Use of insecure and deprecated function (`mktemp`)"),
|
||||
Reason::Eval => format!("Use of possibly insecure function; consider using `ast.literal_eval`"),
|
||||
Reason::MarkSafe => format!("Use of `mark_safe` may expose cross-site scripting vulnerabilities"),
|
||||
Reason::URLOpen => format!("Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected."),
|
||||
Reason::NonCryptographicRandom => format!("Standard pseudo-random generators are not suitable for cryptographic purposes"),
|
||||
Reason::UntrustedXML => format!("Using various XLM methods to parse untrusted XML data is known to be vulnerable to XML attacks; use `defusedxml` equivalents"),
|
||||
Reason::UnverifiedSSL => format!("Python allows using an insecure context via the `_create_unverified_context` that reverts to the previous behavior that does not validate certificates or perform hostname checks"),
|
||||
Reason::Telnet => format!("Telnet-related functions are being called. Telnet is considered insecure. Use SSH or some other encrypted protocol"),
|
||||
Reason::FTPLib => format!("FTP-related functions are being called. FTP is considered insecure. Use SSH/SFTP/SCP or some other encrypted protocol"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SuspiciousMembers<'a> {
|
||||
members: &'a [&'a [&'a str]],
|
||||
reason: Reason,
|
||||
severity: Severity,
|
||||
}
|
||||
|
||||
impl<'a> SuspiciousMembers<'a> {
|
||||
pub const fn new(members: &'a [&'a [&'a str]], reason: Reason, severity: Severity) -> Self {
|
||||
Self {
|
||||
members,
|
||||
reason,
|
||||
severity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SuspiciousModule<'a> {
|
||||
name: &'a str,
|
||||
reason: Reason,
|
||||
severity: Severity,
|
||||
}
|
||||
|
||||
impl<'a> SuspiciousModule<'a> {
|
||||
pub const fn new(name: &'a str, reason: Reason, severity: Severity) -> Self {
|
||||
Self {
|
||||
name,
|
||||
reason,
|
||||
severity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SUSPICIOUS_MEMBERS: &[SuspiciousMembers] = &[
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["pickle", "loads"],
|
||||
&["pickle", "load"],
|
||||
&["pickle", "Unpickler"],
|
||||
&["dill", "loads"],
|
||||
&["dill", "load"],
|
||||
&["dill", "Unpickler"],
|
||||
&["shelve", "open"],
|
||||
&["shelve", "DbfilenameShelf"],
|
||||
&["jsonpickle", "decode"],
|
||||
&["jsonpickle", "unpickler", "decode"],
|
||||
&["pandas", "read_pickle"],
|
||||
],
|
||||
Reason::Pickle,
|
||||
Severity::Medium,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[&["marshal", "loads"], &["marshal", "load"]],
|
||||
Reason::Marshal,
|
||||
Severity::Medium,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["hashlib", "md5"],
|
||||
&["hashlib", "sha1"],
|
||||
&["Crypto", "Hash", "MD5", "new"],
|
||||
&["Crypto", "Hash", "MD4", "new"],
|
||||
&["Crypto", "Hash", "MD3", "new"],
|
||||
&["Crypto", "Hash", "MD2", "new"],
|
||||
&["Crypto", "Hash", "SHA", "new"],
|
||||
&["Cryptodome", "Hash", "MD5", "new"],
|
||||
&["Cryptodome", "Hash", "MD4", "new"],
|
||||
&["Cryptodome", "Hash", "MD3", "new"],
|
||||
&["Cryptodome", "Hash", "MD2", "new"],
|
||||
&["Cryptodome", "Hash", "SHA", "new"],
|
||||
&["cryptography", "hazmat", "primitives", "hashes", "MD5"],
|
||||
&["cryptography", "hazmat", "primitives", "hashes", "SHA1"],
|
||||
],
|
||||
Reason::InsecureHash,
|
||||
Severity::Medium,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["Crypto", "Cipher", "ARC2", "new"],
|
||||
&["Crypto", "Cipher", "ARC2", "new"],
|
||||
&["Crypto", "Cipher", "Blowfish", "new"],
|
||||
&["Crypto", "Cipher", "DES", "new"],
|
||||
&["Crypto", "Cipher", "XOR", "new"],
|
||||
&["Cryptodome", "Cipher", "ARC2", "new"],
|
||||
&["Cryptodome", "Cipher", "ARC2", "new"],
|
||||
&["Cryptodome", "Cipher", "Blowfish", "new"],
|
||||
&["Cryptodome", "Cipher", "DES", "new"],
|
||||
&["Cryptodome", "Cipher", "XOR", "new"],
|
||||
&[
|
||||
"cryptography",
|
||||
"hazmat",
|
||||
"primitives",
|
||||
"ciphers",
|
||||
"algorithms",
|
||||
"ARC4",
|
||||
],
|
||||
&[
|
||||
"cryptography",
|
||||
"hazmat",
|
||||
"primitives",
|
||||
"ciphers",
|
||||
"algorithms",
|
||||
"Blowfish",
|
||||
],
|
||||
&[
|
||||
"cryptography",
|
||||
"hazmat",
|
||||
"primitives",
|
||||
"ciphers",
|
||||
"algorithms",
|
||||
"IDEA",
|
||||
],
|
||||
&[
|
||||
"cryptography",
|
||||
"hazmat",
|
||||
"primitives",
|
||||
"ciphers",
|
||||
"modes",
|
||||
"ECB",
|
||||
],
|
||||
],
|
||||
Reason::InsecureCipher,
|
||||
Severity::High,
|
||||
),
|
||||
SuspiciousMembers::new(&[&["tempfile", "mktemp"]], Reason::Mktemp, Severity::Medium),
|
||||
SuspiciousMembers::new(&[&["eval"]], Reason::Eval, Severity::Medium),
|
||||
SuspiciousMembers::new(
|
||||
&[&["django", "utils", "safestring", "mark_safe"]],
|
||||
Reason::MarkSafe,
|
||||
Severity::Medium,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["urllib", "urlopen"],
|
||||
&["urllib", "request", "urlopen"],
|
||||
&["urllib", "urlretrieve"],
|
||||
&["urllib", "request", "urlretrieve"],
|
||||
&["urllib", "URLopener"],
|
||||
&["urllib", "request", "URLopener"],
|
||||
&["urllib", "FancyURLopener"],
|
||||
&["urllib", "request", "FancyURLopener"],
|
||||
&["urllib2", "urlopen"],
|
||||
&["urllib2", "Request"],
|
||||
&["six", "moves", "urllib", "request", "urlopen"],
|
||||
&["six", "moves", "urllib", "request", "urlretrieve"],
|
||||
&["six", "moves", "urllib", "request", "URLopener"],
|
||||
&["six", "moves", "urllib", "request", "FancyURLopener"],
|
||||
],
|
||||
Reason::URLOpen,
|
||||
Severity::Medium,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["random", "random"],
|
||||
&["random", "randrange"],
|
||||
&["random", "randint"],
|
||||
&["random", "choice"],
|
||||
&["random", "choices"],
|
||||
&["random", "uniform"],
|
||||
&["random", "triangular"],
|
||||
],
|
||||
Reason::NonCryptographicRandom,
|
||||
Severity::Low,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["xml", "etree", "cElementTree", "parse"],
|
||||
&["xml", "etree", "cElementTree", "iterparse"],
|
||||
&["xml", "etree", "cElementTree", "fromstring"],
|
||||
&["xml", "etree", "cElementTree", "XMLParser"],
|
||||
&["xml", "etree", "ElementTree", "parse"],
|
||||
&["xml", "etree", "ElementTree", "iterparse"],
|
||||
&["xml", "etree", "ElementTree", "fromstring"],
|
||||
&["xml", "etree", "ElementTree", "XMLParser"],
|
||||
&["xml", "sax", "expatreader", "create_parser"],
|
||||
&["xml", "dom", "expatbuilder", "parse"],
|
||||
&["xml", "dom", "expatbuilder", "parseString"],
|
||||
&["xml", "sax", "parse"],
|
||||
&["xml", "sax", "parseString"],
|
||||
&["xml", "sax", "make_parser"],
|
||||
&["xml", "dom", "minidom", "parse"],
|
||||
&["xml", "dom", "minidom", "parseString"],
|
||||
&["xml", "dom", "pulldom", "parse"],
|
||||
&["xml", "dom", "pulldom", "parseString"],
|
||||
&["lxml", "etree", "parse"],
|
||||
&["lxml", "etree", "fromstring"],
|
||||
&["lxml", "etree", "RestrictedElement"],
|
||||
&["lxml", "etree", "GlobalParserTLS"],
|
||||
&["lxml", "etree", "getDefaultParser"],
|
||||
&["lxml", "etree", "check_docinfo"],
|
||||
],
|
||||
Reason::UntrustedXML,
|
||||
Severity::High,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[&["ssl", "_create_unverified_context"]],
|
||||
Reason::UnverifiedSSL,
|
||||
Severity::Medium,
|
||||
),
|
||||
];
|
||||
|
||||
const SUSPICIOUS_MODULES: &[SuspiciousModule] = &[
|
||||
SuspiciousModule::new("telnetlib", Reason::Telnet, Severity::High),
|
||||
SuspiciousModule::new("ftplib", Reason::FTPLib, Severity::High),
|
||||
];
|
||||
|
||||
/// S001
|
||||
pub fn denied_function_call(checker: &mut Checker, expr: &Expr) {
|
||||
let ExprKind::Call { func, .. } = &expr.node else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(reason) = checker.ctx.resolve_call_path(func).and_then(|call_path| {
|
||||
for module in SUSPICIOUS_MEMBERS {
|
||||
if module.severity >= checker.settings.flake8_bandit.severity {
|
||||
for member in module.members {
|
||||
if call_path.as_slice() == *member {
|
||||
return Some(module.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for module in SUSPICIOUS_MODULES {
|
||||
if module.severity >= checker.settings.flake8_bandit.severity {
|
||||
if call_path.first() == Some(&module.name) {
|
||||
return Some(module.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let issue = DeniedFunctionCall { reason };
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(issue, Range::from(expr)));
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub use assert_used::{assert_used, Assert};
|
||||
pub use bad_file_permissions::{bad_file_permissions, BadFilePermissions};
|
||||
pub use denied_function_call::{denied_function_call, DeniedFunctionCall};
|
||||
pub use exec_used::{exec_used, ExecBuiltin};
|
||||
pub use hardcoded_bind_all_interfaces::{
|
||||
hardcoded_bind_all_interfaces, HardcodedBindAllInterfaces,
|
||||
@@ -24,22 +25,13 @@ pub use request_with_no_cert_validation::{
|
||||
pub use request_without_timeout::{request_without_timeout, RequestWithoutTimeout};
|
||||
pub use snmp_insecure_version::{snmp_insecure_version, SnmpInsecureVersion};
|
||||
pub use snmp_weak_cryptography::{snmp_weak_cryptography, SnmpWeakCryptography};
|
||||
pub use suspicious_function_call::{
|
||||
suspicious_function_call, SuspiciousEvalUsage, SuspiciousFTPLibUsage,
|
||||
SuspiciousInsecureCipherModeUsage, SuspiciousInsecureCipherUsage, SuspiciousInsecureHashUsage,
|
||||
SuspiciousMarkSafeUsage, SuspiciousMarshalUsage, SuspiciousMktempUsage,
|
||||
SuspiciousNonCryptographicRandomUsage, SuspiciousPickleUsage, SuspiciousTelnetUsage,
|
||||
SuspiciousURLOpenUsage, SuspiciousUnverifiedContextUsage, SuspiciousXMLCElementTreeUsage,
|
||||
SuspiciousXMLETreeUsage, SuspiciousXMLElementTreeUsage, SuspiciousXMLExpatBuilderUsage,
|
||||
SuspiciousXMLExpatReaderUsage, SuspiciousXMLMiniDOMUsage, SuspiciousXMLPullDOMUsage,
|
||||
SuspiciousXMLSaxUsage,
|
||||
};
|
||||
pub use try_except_continue::{try_except_continue, TryExceptContinue};
|
||||
pub use try_except_pass::{try_except_pass, TryExceptPass};
|
||||
pub use unsafe_yaml_load::{unsafe_yaml_load, UnsafeYAMLLoad};
|
||||
|
||||
mod assert_used;
|
||||
mod bad_file_permissions;
|
||||
mod denied_function_call;
|
||||
mod exec_used;
|
||||
mod hardcoded_bind_all_interfaces;
|
||||
mod hardcoded_password_default;
|
||||
@@ -54,7 +46,6 @@ mod request_with_no_cert_validation;
|
||||
mod request_without_timeout;
|
||||
mod snmp_insecure_version;
|
||||
mod snmp_weak_cryptography;
|
||||
mod suspicious_function_call;
|
||||
mod try_except_continue;
|
||||
mod try_except_pass;
|
||||
mod unsafe_yaml_load;
|
||||
|
||||
@@ -1,519 +0,0 @@
|
||||
//! Check for calls to suspicious functions, or calls into suspicious modules.
|
||||
//!
|
||||
//! See: <https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html>
|
||||
use rustpython_parser::ast::{Expr, ExprKind};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::Range;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousPickleUsage;
|
||||
|
||||
impl Violation for SuspiciousPickleUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousMarshalUsage;
|
||||
|
||||
impl Violation for SuspiciousMarshalUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Deserialization with the `marshal` module is possibly dangerous")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousInsecureHashUsage;
|
||||
|
||||
impl Violation for SuspiciousInsecureHashUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of insecure MD2, MD4, MD5, or SHA1 hash function")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousInsecureCipherUsage;
|
||||
|
||||
impl Violation for SuspiciousInsecureCipherUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of insecure cipher, replace with a known secure cipher such as AES")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousInsecureCipherModeUsage;
|
||||
|
||||
impl Violation for SuspiciousInsecureCipherModeUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of insecure cipher mode, replace with a known secure cipher such as AES")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousMktempUsage;
|
||||
|
||||
impl Violation for SuspiciousMktempUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of insecure and deprecated function (`mktemp`)")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousEvalUsage;
|
||||
|
||||
impl Violation for SuspiciousEvalUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of possibly insecure function; consider using `ast.literal_eval`")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousMarkSafeUsage;
|
||||
|
||||
impl Violation for SuspiciousMarkSafeUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of `mark_safe` may expose cross-site scripting vulnerabilities")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousURLOpenUsage;
|
||||
|
||||
impl Violation for SuspiciousURLOpenUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousNonCryptographicRandomUsage;
|
||||
|
||||
impl Violation for SuspiciousNonCryptographicRandomUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Standard pseudo-random generators are not suitable for cryptographic purposes")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousXMLCElementTreeUsage;
|
||||
|
||||
impl Violation for SuspiciousXMLCElementTreeUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousXMLElementTreeUsage;
|
||||
|
||||
impl Violation for SuspiciousXMLElementTreeUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousXMLExpatReaderUsage;
|
||||
|
||||
impl Violation for SuspiciousXMLExpatReaderUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousXMLExpatBuilderUsage;
|
||||
|
||||
impl Violation for SuspiciousXMLExpatBuilderUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousXMLSaxUsage;
|
||||
|
||||
impl Violation for SuspiciousXMLSaxUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousXMLMiniDOMUsage;
|
||||
|
||||
impl Violation for SuspiciousXMLMiniDOMUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousXMLPullDOMUsage;
|
||||
|
||||
impl Violation for SuspiciousXMLPullDOMUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousXMLETreeUsage;
|
||||
|
||||
impl Violation for SuspiciousXMLETreeUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousUnverifiedContextUsage;
|
||||
|
||||
impl Violation for SuspiciousUnverifiedContextUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Python allows using an insecure context via the `_create_unverified_context` that reverts to the previous behavior that does not validate certificates or perform hostname checks.")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousTelnetUsage;
|
||||
|
||||
impl Violation for SuspiciousTelnetUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Telnet-related functions are being called. Telnet is considered insecure. Use SSH or some other encrypted protocol.")
|
||||
}
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct SuspiciousFTPLibUsage;
|
||||
|
||||
impl Violation for SuspiciousFTPLibUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("FTP-related functions are being called. FTP is considered insecure. Use SSH/SFTP/SCP or some other encrypted protocol.")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Reason {
|
||||
Pickle,
|
||||
Marshal,
|
||||
InsecureHash,
|
||||
InsecureCipher,
|
||||
InsecureCipherMode,
|
||||
Mktemp,
|
||||
Eval,
|
||||
MarkSafe,
|
||||
URLOpen,
|
||||
NonCryptographicRandom,
|
||||
XMLCElementTree,
|
||||
XMLElementTree,
|
||||
XMLExpatReader,
|
||||
XMLExpatBuilder,
|
||||
XMLSax,
|
||||
XMLMiniDOM,
|
||||
XMLPullDOM,
|
||||
XMLETree,
|
||||
UnverifiedContext,
|
||||
Telnet,
|
||||
FTPLib,
|
||||
}
|
||||
|
||||
struct SuspiciousMembers<'a> {
|
||||
members: &'a [&'a [&'a str]],
|
||||
reason: Reason,
|
||||
}
|
||||
|
||||
impl<'a> SuspiciousMembers<'a> {
|
||||
pub const fn new(members: &'a [&'a [&'a str]], reason: Reason) -> Self {
|
||||
Self { members, reason }
|
||||
}
|
||||
}
|
||||
|
||||
struct SuspiciousModule<'a> {
|
||||
name: &'a str,
|
||||
reason: Reason,
|
||||
}
|
||||
|
||||
impl<'a> SuspiciousModule<'a> {
|
||||
pub const fn new(name: &'a str, reason: Reason) -> Self {
|
||||
Self { name, reason }
|
||||
}
|
||||
}
|
||||
|
||||
const SUSPICIOUS_MEMBERS: &[SuspiciousMembers] = &[
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["pickle", "loads"],
|
||||
&["pickle", "load"],
|
||||
&["pickle", "Unpickler"],
|
||||
&["dill", "loads"],
|
||||
&["dill", "load"],
|
||||
&["dill", "Unpickler"],
|
||||
&["shelve", "open"],
|
||||
&["shelve", "DbfilenameShelf"],
|
||||
&["jsonpickle", "decode"],
|
||||
&["jsonpickle", "unpickler", "decode"],
|
||||
&["pandas", "read_pickle"],
|
||||
],
|
||||
Reason::Pickle,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[&["marshal", "loads"], &["marshal", "load"]],
|
||||
Reason::Marshal,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["Crypto", "Hash", "MD5", "new"],
|
||||
&["Crypto", "Hash", "MD4", "new"],
|
||||
&["Crypto", "Hash", "MD3", "new"],
|
||||
&["Crypto", "Hash", "MD2", "new"],
|
||||
&["Crypto", "Hash", "SHA", "new"],
|
||||
&["Cryptodome", "Hash", "MD5", "new"],
|
||||
&["Cryptodome", "Hash", "MD4", "new"],
|
||||
&["Cryptodome", "Hash", "MD3", "new"],
|
||||
&["Cryptodome", "Hash", "MD2", "new"],
|
||||
&["Cryptodome", "Hash", "SHA", "new"],
|
||||
&["cryptography", "hazmat", "primitives", "hashes", "MD5"],
|
||||
&["cryptography", "hazmat", "primitives", "hashes", "SHA1"],
|
||||
],
|
||||
Reason::InsecureHash,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["Crypto", "Cipher", "ARC2", "new"],
|
||||
&["Crypto", "Cipher", "ARC2", "new"],
|
||||
&["Crypto", "Cipher", "Blowfish", "new"],
|
||||
&["Crypto", "Cipher", "DES", "new"],
|
||||
&["Crypto", "Cipher", "XOR", "new"],
|
||||
&["Cryptodome", "Cipher", "ARC2", "new"],
|
||||
&["Cryptodome", "Cipher", "ARC2", "new"],
|
||||
&["Cryptodome", "Cipher", "Blowfish", "new"],
|
||||
&["Cryptodome", "Cipher", "DES", "new"],
|
||||
&["Cryptodome", "Cipher", "XOR", "new"],
|
||||
&[
|
||||
"cryptography",
|
||||
"hazmat",
|
||||
"primitives",
|
||||
"ciphers",
|
||||
"algorithms",
|
||||
"ARC4",
|
||||
],
|
||||
&[
|
||||
"cryptography",
|
||||
"hazmat",
|
||||
"primitives",
|
||||
"ciphers",
|
||||
"algorithms",
|
||||
"Blowfish",
|
||||
],
|
||||
&[
|
||||
"cryptography",
|
||||
"hazmat",
|
||||
"primitives",
|
||||
"ciphers",
|
||||
"algorithms",
|
||||
"IDEA",
|
||||
],
|
||||
],
|
||||
Reason::InsecureCipher,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[&[
|
||||
"cryptography",
|
||||
"hazmat",
|
||||
"primitives",
|
||||
"ciphers",
|
||||
"modes",
|
||||
"ECB",
|
||||
]],
|
||||
Reason::InsecureCipherMode,
|
||||
),
|
||||
SuspiciousMembers::new(&[&["tempfile", "mktemp"]], Reason::Mktemp),
|
||||
SuspiciousMembers::new(&[&["eval"]], Reason::Eval),
|
||||
SuspiciousMembers::new(
|
||||
&[&["django", "utils", "safestring", "mark_safe"]],
|
||||
Reason::MarkSafe,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["urllib", "urlopen"],
|
||||
&["urllib", "request", "urlopen"],
|
||||
&["urllib", "urlretrieve"],
|
||||
&["urllib", "request", "urlretrieve"],
|
||||
&["urllib", "URLopener"],
|
||||
&["urllib", "request", "URLopener"],
|
||||
&["urllib", "FancyURLopener"],
|
||||
&["urllib", "request", "FancyURLopener"],
|
||||
&["urllib2", "urlopen"],
|
||||
&["urllib2", "Request"],
|
||||
&["six", "moves", "urllib", "request", "urlopen"],
|
||||
&["six", "moves", "urllib", "request", "urlretrieve"],
|
||||
&["six", "moves", "urllib", "request", "URLopener"],
|
||||
&["six", "moves", "urllib", "request", "FancyURLopener"],
|
||||
],
|
||||
Reason::URLOpen,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["random", "random"],
|
||||
&["random", "randrange"],
|
||||
&["random", "randint"],
|
||||
&["random", "choice"],
|
||||
&["random", "choices"],
|
||||
&["random", "uniform"],
|
||||
&["random", "triangular"],
|
||||
],
|
||||
Reason::NonCryptographicRandom,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[&["ssl", "_create_unverified_context"]],
|
||||
Reason::UnverifiedContext,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["xml", "etree", "cElementTree", "parse"],
|
||||
&["xml", "etree", "cElementTree", "iterparse"],
|
||||
&["xml", "etree", "cElementTree", "fromstring"],
|
||||
&["xml", "etree", "cElementTree", "XMLParser"],
|
||||
],
|
||||
Reason::XMLCElementTree,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["xml", "etree", "ElementTree", "parse"],
|
||||
&["xml", "etree", "ElementTree", "iterparse"],
|
||||
&["xml", "etree", "ElementTree", "fromstring"],
|
||||
&["xml", "etree", "ElementTree", "XMLParser"],
|
||||
],
|
||||
Reason::XMLElementTree,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[&["xml", "sax", "expatreader", "create_parser"]],
|
||||
Reason::XMLExpatReader,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["xml", "dom", "expatbuilder", "parse"],
|
||||
&["xml", "dom", "expatbuilder", "parseString"],
|
||||
],
|
||||
Reason::XMLExpatBuilder,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["xml", "sax", "parse"],
|
||||
&["xml", "sax", "parseString"],
|
||||
&["xml", "sax", "make_parser"],
|
||||
],
|
||||
Reason::XMLSax,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["xml", "dom", "minidom", "parse"],
|
||||
&["xml", "dom", "minidom", "parseString"],
|
||||
],
|
||||
Reason::XMLMiniDOM,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["xml", "dom", "pulldom", "parse"],
|
||||
&["xml", "dom", "pulldom", "parseString"],
|
||||
],
|
||||
Reason::XMLPullDOM,
|
||||
),
|
||||
SuspiciousMembers::new(
|
||||
&[
|
||||
&["lxml", "etree", "parse"],
|
||||
&["lxml", "etree", "fromstring"],
|
||||
&["lxml", "etree", "RestrictedElement"],
|
||||
&["lxml", "etree", "GlobalParserTLS"],
|
||||
&["lxml", "etree", "getDefaultParser"],
|
||||
&["lxml", "etree", "check_docinfo"],
|
||||
],
|
||||
Reason::XMLETree,
|
||||
),
|
||||
];
|
||||
|
||||
const SUSPICIOUS_MODULES: &[SuspiciousModule] = &[
|
||||
SuspiciousModule::new("telnetlib", Reason::Telnet),
|
||||
SuspiciousModule::new("ftplib", Reason::FTPLib),
|
||||
];
|
||||
|
||||
/// S001
|
||||
pub fn suspicious_function_call(checker: &mut Checker, expr: &Expr) {
|
||||
let ExprKind::Call { func, .. } = &expr.node else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(reason) = checker.ctx.resolve_call_path(func).and_then(|call_path| {
|
||||
for module in SUSPICIOUS_MEMBERS {
|
||||
for member in module.members {
|
||||
if call_path.as_slice() == *member {
|
||||
return Some(module.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
for module in SUSPICIOUS_MODULES {
|
||||
if call_path.first() == Some(&module.name) {
|
||||
return Some(module.reason);
|
||||
}
|
||||
}
|
||||
None
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let diagnostic_kind = match reason {
|
||||
Reason::Pickle => SuspiciousPickleUsage.into(),
|
||||
Reason::Marshal => SuspiciousMarshalUsage.into(),
|
||||
Reason::InsecureHash => SuspiciousInsecureHashUsage.into(),
|
||||
Reason::InsecureCipher => SuspiciousInsecureCipherUsage.into(),
|
||||
Reason::InsecureCipherMode => SuspiciousInsecureCipherModeUsage.into(),
|
||||
Reason::Mktemp => SuspiciousMktempUsage.into(),
|
||||
Reason::Eval => SuspiciousEvalUsage.into(),
|
||||
Reason::MarkSafe => SuspiciousMarkSafeUsage.into(),
|
||||
Reason::URLOpen => SuspiciousURLOpenUsage.into(),
|
||||
Reason::NonCryptographicRandom => SuspiciousNonCryptographicRandomUsage.into(),
|
||||
Reason::XMLCElementTree => SuspiciousXMLCElementTreeUsage.into(),
|
||||
Reason::XMLElementTree => SuspiciousXMLElementTreeUsage.into(),
|
||||
Reason::XMLExpatReader => SuspiciousXMLExpatReaderUsage.into(),
|
||||
Reason::XMLExpatBuilder => SuspiciousXMLExpatBuilderUsage.into(),
|
||||
Reason::XMLSax => SuspiciousXMLSaxUsage.into(),
|
||||
Reason::XMLMiniDOM => SuspiciousXMLMiniDOMUsage.into(),
|
||||
Reason::XMLPullDOM => SuspiciousXMLPullDOMUsage.into(),
|
||||
Reason::XMLETree => SuspiciousXMLETreeUsage.into(),
|
||||
Reason::UnverifiedContext => SuspiciousUnverifiedContextUsage.into(),
|
||||
Reason::Telnet => SuspiciousTelnetUsage.into(),
|
||||
Reason::FTPLib => SuspiciousFTPLibUsage.into(),
|
||||
};
|
||||
let diagnostic = Diagnostic::new::<DiagnosticKind>(diagnostic_kind, Range::from(expr));
|
||||
if checker.settings.rules.enabled(diagnostic.kind.rule()) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,16 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use ruff_macros::{CacheKey, ConfigurationOptions};
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema, Hash, CacheKey, PartialOrd,
|
||||
)]
|
||||
pub enum Severity {
|
||||
#[default]
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
fn default_tmp_dirs() -> Vec<String> {
|
||||
["/tmp", "/var/tmp", "/dev/shm"]
|
||||
.map(std::string::ToString::to_string)
|
||||
@@ -44,12 +54,18 @@ pub struct Options {
|
||||
/// exception types. By default, `try`-`except`-`pass` is only
|
||||
/// disallowed for `Exception` and `BaseException`.
|
||||
pub check_typed_exception: Option<bool>,
|
||||
#[option(default = "low", value_type = "str", example = "severity = \"high\"")]
|
||||
/// The minimum severity to enforce for denied function calls.
|
||||
///
|
||||
/// Valid values are `low`, `medium`, and `high`.
|
||||
pub severity: Option<Severity>,
|
||||
}
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub hardcoded_tmp_directory: Vec<String>,
|
||||
pub check_typed_exception: bool,
|
||||
pub severity: Severity,
|
||||
}
|
||||
|
||||
impl From<Options> for Settings {
|
||||
@@ -67,6 +83,7 @@ impl From<Options> for Settings {
|
||||
)
|
||||
.collect(),
|
||||
check_typed_exception: options.check_typed_exception.unwrap_or(false),
|
||||
severity: options.severity.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +94,7 @@ impl From<Settings> for Options {
|
||||
hardcoded_tmp_directory: Some(settings.hardcoded_tmp_directory),
|
||||
hardcoded_tmp_directory_extend: None,
|
||||
check_typed_exception: Some(settings.check_typed_exception),
|
||||
severity: Some(settings.severity),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +104,7 @@ impl Default for Settings {
|
||||
Self {
|
||||
hardcoded_tmp_directory: default_tmp_dirs(),
|
||||
check_typed_exception: false,
|
||||
severity: Severity::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: DeniedFunctionCall
|
||||
body: "`pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 14
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: DeniedFunctionCall
|
||||
body: Telnet-related functions are being called. Telnet is considered insecure. Use SSH or some other encrypted protocol
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 23
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: SuspiciousPickleUsage
|
||||
body: "`pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 14
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: SuspiciousTelnetUsage
|
||||
body: Telnet-related functions are being called. Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 23
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -18,7 +18,6 @@ mod tests {
|
||||
#[test_case(Rule::DjangoExcludeWithModelForm, Path::new("DJ006.py"); "DJ006")]
|
||||
#[test_case(Rule::DjangoAllWithModelForm, Path::new("DJ007.py"); "DJ007")]
|
||||
#[test_case(Rule::DjangoModelWithoutDunderStr, Path::new("DJ008.py"); "DJ008")]
|
||||
#[test_case(Rule::DjangoUnorderedBodyContentInModel, Path::new("DJ012.py"); "DJ012")]
|
||||
#[test_case(Rule::DjangoNonLeadingReceiverDecorator, Path::new("DJ013.py"); "DJ013")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
|
||||
@@ -49,7 +49,7 @@ impl Violation for DjangoAllWithModelForm {
|
||||
|
||||
/// DJ007
|
||||
pub fn all_with_model_form(checker: &Checker, bases: &[Expr], body: &[Stmt]) -> Option<Diagnostic> {
|
||||
if !bases.iter().any(|base| is_model_form(&checker.ctx, base)) {
|
||||
if !bases.iter().any(|base| is_model_form(checker, base)) {
|
||||
return None;
|
||||
}
|
||||
for element in body.iter() {
|
||||
|
||||
@@ -49,7 +49,7 @@ pub fn exclude_with_model_form(
|
||||
bases: &[Expr],
|
||||
body: &[Stmt],
|
||||
) -> Option<Diagnostic> {
|
||||
if !bases.iter().any(|base| is_model_form(&checker.ctx, base)) {
|
||||
if !bases.iter().any(|base| is_model_form(checker, base)) {
|
||||
return None;
|
||||
}
|
||||
for element in body.iter() {
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
use ruff_python_ast::context::Context;
|
||||
use rustpython_parser::ast::Expr;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// Return `true` if a Python class appears to be a Django model, based on its base classes.
|
||||
pub fn is_model(context: &Context, base: &Expr) -> bool {
|
||||
context.resolve_call_path(base).map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["django", "db", "models", "Model"]
|
||||
})
|
||||
pub fn is_model(checker: &Checker, base: &Expr) -> bool {
|
||||
checker
|
||||
.ctx
|
||||
.resolve_call_path(base)
|
||||
.map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["django", "db", "models", "Model"]
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if a Python class appears to be a Django model form, based on its base classes.
|
||||
pub fn is_model_form(context: &Context, base: &Expr) -> bool {
|
||||
context.resolve_call_path(base).map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["django", "forms", "ModelForm"]
|
||||
|| call_path.as_slice() == ["django", "forms", "models", "ModelForm"]
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the expression is constructor for a Django model field.
|
||||
pub fn is_model_field(context: &Context, expr: &Expr) -> bool {
|
||||
context.resolve_call_path(expr).map_or(false, |call_path| {
|
||||
call_path
|
||||
.as_slice()
|
||||
.starts_with(&["django", "db", "models"])
|
||||
})
|
||||
pub fn is_model_form(checker: &Checker, base: &Expr) -> bool {
|
||||
checker
|
||||
.ctx
|
||||
.resolve_call_path(base)
|
||||
.map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["django", "forms", "ModelForm"]
|
||||
|| call_path.as_slice() == ["django", "forms", "models", "ModelForm"]
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the name of the field type, if the expression is constructor for a Django model field.
|
||||
pub fn get_model_field_name<'a>(context: &'a Context, expr: &'a Expr) -> Option<&'a str> {
|
||||
context.resolve_call_path(expr).and_then(|call_path| {
|
||||
pub fn get_model_field_name<'a>(checker: &'a Checker, expr: &'a Expr) -> Option<&'a str> {
|
||||
checker.ctx.resolve_call_path(expr).and_then(|call_path| {
|
||||
let call_path = call_path.as_slice();
|
||||
if !call_path.starts_with(&["django", "db", "models"]) {
|
||||
return None;
|
||||
|
||||
@@ -8,9 +8,6 @@ pub use non_leading_receiver_decorator::{
|
||||
pub use nullable_model_string_field::{
|
||||
nullable_model_string_field, DjangoNullableModelStringField,
|
||||
};
|
||||
pub use unordered_body_content_in_model::{
|
||||
unordered_body_content_in_model, DjangoUnorderedBodyContentInModel,
|
||||
};
|
||||
|
||||
mod all_with_model_form;
|
||||
mod exclude_with_model_form;
|
||||
@@ -19,4 +16,3 @@ mod locals_in_render_function;
|
||||
mod model_without_dunder_str;
|
||||
mod non_leading_receiver_decorator;
|
||||
mod nullable_model_string_field;
|
||||
mod unordered_body_content_in_model;
|
||||
|
||||
@@ -86,7 +86,7 @@ fn checker_applies(checker: &Checker, bases: &[Expr], body: &[Stmt]) -> bool {
|
||||
if is_model_abstract(body) {
|
||||
continue;
|
||||
}
|
||||
if helpers::is_model(&checker.ctx, base) {
|
||||
if helpers::is_model(checker, base) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ fn is_nullable_field<'a>(checker: &'a Checker, value: &'a Expr) -> Option<&'a st
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(valid_field_name) = helpers::get_model_field_name(&checker.ctx, func) else {
|
||||
let Some(valid_field_name) = helpers::get_model_field_name(checker, func) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::Range;
|
||||
use rustpython_parser::ast::{Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the order of Model's inner classes, methods, and fields as per
|
||||
/// the [Django Style Guide].
|
||||
///
|
||||
/// ## Why is it bad?
|
||||
/// The [Django Style Guide] specifies that the order of Model inner classes,
|
||||
/// attributes and methods should be as follows:
|
||||
///
|
||||
/// 1. All database fields
|
||||
/// 2. Custom manager attributes
|
||||
/// 3. `class Meta`
|
||||
/// 4. `def __str__()`
|
||||
/// 5. `def save()`
|
||||
/// 6. `def get_absolute_url()`
|
||||
/// 7. Any custom methods
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// from django.db import models
|
||||
///
|
||||
///
|
||||
/// class StrBeforeFieldModel(models.Model):
|
||||
/// class Meta:
|
||||
/// verbose_name = "test"
|
||||
/// verbose_name_plural = "tests"
|
||||
///
|
||||
/// def __str__(self):
|
||||
/// return "foobar"
|
||||
///
|
||||
/// first_name = models.CharField(max_length=32)
|
||||
/// last_name = models.CharField(max_length=40)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from django.db import models
|
||||
///
|
||||
///
|
||||
/// class StrBeforeFieldModel(models.Model):
|
||||
/// first_name = models.CharField(max_length=32)
|
||||
/// last_name = models.CharField(max_length=40)
|
||||
///
|
||||
/// class Meta:
|
||||
/// verbose_name = "test"
|
||||
/// verbose_name_plural = "tests"
|
||||
///
|
||||
/// def __str__(self):
|
||||
/// return "foobar"
|
||||
/// ```
|
||||
///
|
||||
/// [Django Style Guide]: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#model-style
|
||||
#[violation]
|
||||
pub struct DjangoUnorderedBodyContentInModel {
|
||||
pub elem_type: ContentType,
|
||||
pub before: ContentType,
|
||||
}
|
||||
|
||||
impl Violation for DjangoUnorderedBodyContentInModel {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let DjangoUnorderedBodyContentInModel { elem_type, before } = self;
|
||||
format!("Order of model's inner classes, methods, and fields does not follow the Django Style Guide: {elem_type} should come before {before}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub enum ContentType {
|
||||
FieldDeclaration,
|
||||
ManagerDeclaration,
|
||||
MetaClass,
|
||||
StrMethod,
|
||||
SaveMethod,
|
||||
GetAbsoluteUrlMethod,
|
||||
CustomMethod,
|
||||
}
|
||||
|
||||
impl fmt::Display for ContentType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ContentType::FieldDeclaration => f.write_str("field declaration"),
|
||||
ContentType::ManagerDeclaration => f.write_str("manager declaration"),
|
||||
ContentType::MetaClass => f.write_str("`Meta` class"),
|
||||
ContentType::StrMethod => f.write_str("`__str__` method"),
|
||||
ContentType::SaveMethod => f.write_str("`save` method"),
|
||||
ContentType::GetAbsoluteUrlMethod => f.write_str("`get_absolute_url` method"),
|
||||
ContentType::CustomMethod => f.write_str("custom method"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_element_type(checker: &Checker, element: &StmtKind) -> Option<ContentType> {
|
||||
match element {
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
if let ExprKind::Call { func, .. } = &value.node {
|
||||
if helpers::is_model_field(&checker.ctx, func) {
|
||||
return Some(ContentType::FieldDeclaration);
|
||||
}
|
||||
}
|
||||
let Some(expr) = targets.first() else {
|
||||
return None;
|
||||
};
|
||||
let ExprKind::Name { id, .. } = &expr.node else {
|
||||
return None;
|
||||
};
|
||||
if id == "objects" {
|
||||
Some(ContentType::ManagerDeclaration)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
StmtKind::ClassDef { name, .. } => {
|
||||
if name == "Meta" {
|
||||
Some(ContentType::MetaClass)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
StmtKind::FunctionDef { name, .. } => match name.as_str() {
|
||||
"__str__" => Some(ContentType::StrMethod),
|
||||
"save" => Some(ContentType::SaveMethod),
|
||||
"get_absolute_url" => Some(ContentType::GetAbsoluteUrlMethod),
|
||||
_ => Some(ContentType::CustomMethod),
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// DJ012
|
||||
pub fn unordered_body_content_in_model(checker: &mut Checker, bases: &[Expr], body: &[Stmt]) {
|
||||
if !bases
|
||||
.iter()
|
||||
.any(|base| helpers::is_model(&checker.ctx, base))
|
||||
{
|
||||
return;
|
||||
}
|
||||
let mut elements_type_found = Vec::new();
|
||||
for element in body.iter() {
|
||||
let Some(current_element_type) = get_element_type(checker, &element.node) else {
|
||||
continue;
|
||||
};
|
||||
let Some(&element_type) = elements_type_found
|
||||
.iter()
|
||||
.find(|&&element_type| element_type > current_element_type) else {
|
||||
elements_type_found.push(current_element_type);
|
||||
continue;
|
||||
};
|
||||
let diagnostic = Diagnostic::new(
|
||||
DjangoUnorderedBodyContentInModel {
|
||||
elem_type: current_element_type,
|
||||
before: element_type,
|
||||
},
|
||||
Range::from(element),
|
||||
);
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_django/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: DjangoUnorderedBodyContentInModel
|
||||
body: "Order of model's inner classes, methods, and fields does not follow the Django Style Guide: field declaration should come before `Meta` class"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 28
|
||||
column: 4
|
||||
end_location:
|
||||
row: 28
|
||||
column: 48
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: DjangoUnorderedBodyContentInModel
|
||||
body: "Order of model's inner classes, methods, and fields does not follow the Django Style Guide: field declaration should come before manager declaration"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 43
|
||||
column: 4
|
||||
end_location:
|
||||
row: 43
|
||||
column: 48
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: DjangoUnorderedBodyContentInModel
|
||||
body: "Order of model's inner classes, methods, and fields does not follow the Django Style Guide: `__str__` method should come before custom method"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 56
|
||||
column: 4
|
||||
end_location:
|
||||
row: 57
|
||||
column: 23
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: DjangoUnorderedBodyContentInModel
|
||||
body: "Order of model's inner classes, methods, and fields does not follow the Django Style Guide: `save` method should come before `get_absolute_url` method"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 69
|
||||
column: 4
|
||||
end_location:
|
||||
row: 70
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -16,7 +16,7 @@ mod tests {
|
||||
#[test_case(Rule::DuplicateIsinstanceCall, Path::new("SIM101.py"); "SIM101")]
|
||||
#[test_case(Rule::CollapsibleIf, Path::new("SIM102.py"); "SIM102")]
|
||||
#[test_case(Rule::NeedlessBool, Path::new("SIM103.py"); "SIM103")]
|
||||
#[test_case(Rule::SuppressibleException, Path::new("SIM105.py"); "SIM105")]
|
||||
#[test_case(Rule::UseContextlibSuppress, Path::new("SIM105.py"); "SIM105")]
|
||||
#[test_case(Rule::ReturnInTryExceptFinally, Path::new("SIM107.py"); "SIM107")]
|
||||
#[test_case(Rule::IfElseBlockInsteadOfIfExp, Path::new("SIM108.py"); "SIM108")]
|
||||
#[test_case(Rule::CompareWithTuple, Path::new("SIM109.py"); "SIM109")]
|
||||
|
||||
@@ -24,7 +24,7 @@ pub use open_file_with_context_handler::{
|
||||
};
|
||||
pub use reimplemented_builtin::{convert_for_loop_to_any_all, ReimplementedBuiltin};
|
||||
pub use return_in_try_except_finally::{return_in_try_except_finally, ReturnInTryExceptFinally};
|
||||
pub use suppressible_exception::{suppressible_exception, SuppressibleException};
|
||||
pub use use_contextlib_suppress::{use_contextlib_suppress, UseContextlibSuppress};
|
||||
pub use yoda_conditions::{yoda_conditions, YodaConditions};
|
||||
|
||||
mod ast_bool_op;
|
||||
@@ -39,5 +39,5 @@ mod key_in_dict;
|
||||
mod open_file_with_context_handler;
|
||||
mod reimplemented_builtin;
|
||||
mod return_in_try_except_finally;
|
||||
mod suppressible_exception;
|
||||
mod use_contextlib_suppress;
|
||||
mod yoda_conditions;
|
||||
|
||||
@@ -9,19 +9,19 @@ use ruff_python_ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
#[violation]
|
||||
pub struct SuppressibleException {
|
||||
pub struct UseContextlibSuppress {
|
||||
pub exception: String,
|
||||
}
|
||||
|
||||
impl Violation for SuppressibleException {
|
||||
impl Violation for UseContextlibSuppress {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let SuppressibleException { exception } = self;
|
||||
let UseContextlibSuppress { exception } = self;
|
||||
format!("Use `contextlib.suppress({exception})` instead of try-except-pass")
|
||||
}
|
||||
}
|
||||
/// SIM105
|
||||
pub fn suppressible_exception(
|
||||
pub fn use_contextlib_suppress(
|
||||
checker: &mut Checker,
|
||||
stmt: &Stmt,
|
||||
body: &[Stmt],
|
||||
@@ -63,7 +63,7 @@ pub fn suppressible_exception(
|
||||
handler_names.join(", ")
|
||||
};
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
SuppressibleException { exception },
|
||||
UseContextlibSuppress { exception },
|
||||
Range::from(stmt),
|
||||
));
|
||||
}
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: SuppressibleException
|
||||
name: UseContextlibSuppress
|
||||
body: "Use `contextlib.suppress(ValueError)` instead of try-except-pass"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -16,7 +16,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: SuppressibleException
|
||||
name: UseContextlibSuppress
|
||||
body: "Use `contextlib.suppress(ValueError, OSError)` instead of try-except-pass"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -29,7 +29,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: SuppressibleException
|
||||
name: UseContextlibSuppress
|
||||
body: "Use `contextlib.suppress(Exception)` instead of try-except-pass"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -42,7 +42,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: SuppressibleException
|
||||
name: UseContextlibSuppress
|
||||
body: "Use `contextlib.suppress(a.Error, b.Error)` instead of try-except-pass"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
|
||||
@@ -6,10 +6,11 @@ use ruff_python_ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::rules::flake8_use_pathlib::violations::{
|
||||
BuiltinOpen, OsChmod, OsGetcwd, OsMakedirs, OsMkdir, OsPathAbspath, OsPathBasename,
|
||||
OsPathDirname, OsPathExists, OsPathExpanduser, OsPathIsabs, OsPathIsdir, OsPathIsfile,
|
||||
OsPathIslink, OsPathJoin, OsPathSamefile, OsPathSplitext, OsReadlink, OsRemove, OsRename,
|
||||
OsRmdir, OsStat, OsUnlink, PathlibReplace, PyPath,
|
||||
PathlibAbspath, PathlibBasename, PathlibChmod, PathlibDirname, PathlibExists,
|
||||
PathlibExpanduser, PathlibGetcwd, PathlibIsAbs, PathlibIsDir, PathlibIsFile, PathlibIsLink,
|
||||
PathlibJoin, PathlibMakedirs, PathlibMkdir, PathlibOpen, PathlibPyPath, PathlibReadlink,
|
||||
PathlibRemove, PathlibRename, PathlibReplace, PathlibRmdir, PathlibSamefile, PathlibSplitext,
|
||||
PathlibStat, PathlibUnlink,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
@@ -19,34 +20,34 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) {
|
||||
.ctx
|
||||
.resolve_call_path(expr)
|
||||
.and_then(|call_path| match call_path.as_slice() {
|
||||
["os", "path", "abspath"] => Some(OsPathAbspath.into()),
|
||||
["os", "chmod"] => Some(OsChmod.into()),
|
||||
["os", "mkdir"] => Some(OsMkdir.into()),
|
||||
["os", "makedirs"] => Some(OsMakedirs.into()),
|
||||
["os", "rename"] => Some(OsRename.into()),
|
||||
["os", "path", "abspath"] => Some(PathlibAbspath.into()),
|
||||
["os", "chmod"] => Some(PathlibChmod.into()),
|
||||
["os", "mkdir"] => Some(PathlibMkdir.into()),
|
||||
["os", "makedirs"] => Some(PathlibMakedirs.into()),
|
||||
["os", "rename"] => Some(PathlibRename.into()),
|
||||
["os", "replace"] => Some(PathlibReplace.into()),
|
||||
["os", "rmdir"] => Some(OsRmdir.into()),
|
||||
["os", "remove"] => Some(OsRemove.into()),
|
||||
["os", "unlink"] => Some(OsUnlink.into()),
|
||||
["os", "getcwd"] => Some(OsGetcwd.into()),
|
||||
["os", "getcwdb"] => Some(OsGetcwd.into()),
|
||||
["os", "path", "exists"] => Some(OsPathExists.into()),
|
||||
["os", "path", "expanduser"] => Some(OsPathExpanduser.into()),
|
||||
["os", "path", "isdir"] => Some(OsPathIsdir.into()),
|
||||
["os", "path", "isfile"] => Some(OsPathIsfile.into()),
|
||||
["os", "path", "islink"] => Some(OsPathIslink.into()),
|
||||
["os", "stat"] => Some(OsStat.into()),
|
||||
["os", "path", "isabs"] => Some(OsPathIsabs.into()),
|
||||
["os", "path", "join"] => Some(OsPathJoin.into()),
|
||||
["os", "path", "basename"] => Some(OsPathBasename.into()),
|
||||
["os", "path", "dirname"] => Some(OsPathDirname.into()),
|
||||
["os", "path", "samefile"] => Some(OsPathSamefile.into()),
|
||||
["os", "path", "splitext"] => Some(OsPathSplitext.into()),
|
||||
["", "open"] => Some(BuiltinOpen.into()),
|
||||
["py", "path", "local"] => Some(PyPath.into()),
|
||||
["os", "rmdir"] => Some(PathlibRmdir.into()),
|
||||
["os", "remove"] => Some(PathlibRemove.into()),
|
||||
["os", "unlink"] => Some(PathlibUnlink.into()),
|
||||
["os", "getcwd"] => Some(PathlibGetcwd.into()),
|
||||
["os", "getcwdb"] => Some(PathlibGetcwd.into()),
|
||||
["os", "path", "exists"] => Some(PathlibExists.into()),
|
||||
["os", "path", "expanduser"] => Some(PathlibExpanduser.into()),
|
||||
["os", "path", "isdir"] => Some(PathlibIsDir.into()),
|
||||
["os", "path", "isfile"] => Some(PathlibIsFile.into()),
|
||||
["os", "path", "islink"] => Some(PathlibIsLink.into()),
|
||||
["os", "stat"] => Some(PathlibStat.into()),
|
||||
["os", "path", "isabs"] => Some(PathlibIsAbs.into()),
|
||||
["os", "path", "join"] => Some(PathlibJoin.into()),
|
||||
["os", "path", "basename"] => Some(PathlibBasename.into()),
|
||||
["os", "path", "dirname"] => Some(PathlibDirname.into()),
|
||||
["os", "path", "samefile"] => Some(PathlibSamefile.into()),
|
||||
["os", "path", "splitext"] => Some(PathlibSplitext.into()),
|
||||
["", "open"] => Some(PathlibOpen.into()),
|
||||
["py", "path", "local"] => Some(PathlibPyPath.into()),
|
||||
// Python 3.9+
|
||||
["os", "readlink"] if checker.settings.target_version >= PythonVersion::Py39 => {
|
||||
Some(OsReadlink.into())
|
||||
Some(PathlibReadlink.into())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
|
||||
@@ -23,38 +23,38 @@ mod tests {
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_use_pathlib").join(path).as_path(),
|
||||
&settings::Settings::for_rules(vec![
|
||||
Rule::OsPathAbspath,
|
||||
Rule::OsChmod,
|
||||
Rule::OsMkdir,
|
||||
Rule::OsMakedirs,
|
||||
Rule::OsRename,
|
||||
Rule::PathlibAbspath,
|
||||
Rule::PathlibChmod,
|
||||
Rule::PathlibMkdir,
|
||||
Rule::PathlibMakedirs,
|
||||
Rule::PathlibRename,
|
||||
Rule::PathlibReplace,
|
||||
Rule::OsRmdir,
|
||||
Rule::OsRemove,
|
||||
Rule::OsUnlink,
|
||||
Rule::OsGetcwd,
|
||||
Rule::OsPathExists,
|
||||
Rule::OsPathExpanduser,
|
||||
Rule::OsPathIsdir,
|
||||
Rule::OsPathIsfile,
|
||||
Rule::OsPathIslink,
|
||||
Rule::OsReadlink,
|
||||
Rule::OsStat,
|
||||
Rule::OsPathIsabs,
|
||||
Rule::OsPathJoin,
|
||||
Rule::OsPathBasename,
|
||||
Rule::OsPathDirname,
|
||||
Rule::OsPathSamefile,
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
Rule::PathlibRmdir,
|
||||
Rule::PathlibRemove,
|
||||
Rule::PathlibUnlink,
|
||||
Rule::PathlibGetcwd,
|
||||
Rule::PathlibExists,
|
||||
Rule::PathlibExpanduser,
|
||||
Rule::PathlibIsDir,
|
||||
Rule::PathlibIsFile,
|
||||
Rule::PathlibIsLink,
|
||||
Rule::PathlibReadlink,
|
||||
Rule::PathlibStat,
|
||||
Rule::PathlibIsAbs,
|
||||
Rule::PathlibJoin,
|
||||
Rule::PathlibBasename,
|
||||
Rule::PathlibDirname,
|
||||
Rule::PathlibSamefile,
|
||||
Rule::PathlibSplitext,
|
||||
Rule::PathlibOpen,
|
||||
]),
|
||||
)?;
|
||||
insta::assert_yaml_snapshot!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::PyPath, Path::new("py_path_1.py"); "PTH024_1")]
|
||||
#[test_case(Rule::PyPath, Path::new("py_path_2.py"); "PTH024_2")]
|
||||
#[test_case(Rule::PathlibPyPath, Path::new("py_path_1.py"); "PTH024_1")]
|
||||
#[test_case(Rule::PathlibPyPath, Path::new("py_path_2.py"); "PTH024_2")]
|
||||
fn rules_pypath(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: PyPath
|
||||
name: PathlibPyPath
|
||||
body: "`py.path` is in maintenance mode, use `pathlib` instead"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: PyPath
|
||||
name: PathlibPyPath
|
||||
body: "`py.path` is in maintenance mode, use `pathlib` instead"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: OsPathAbspath
|
||||
name: PathlibAbspath
|
||||
body: "`os.path.abspath()` should be replaced by `Path.resolve()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -16,7 +16,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsChmod
|
||||
name: PathlibChmod
|
||||
body: "`os.chmod()` should be replaced by `Path.chmod()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -29,7 +29,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsMkdir
|
||||
name: PathlibMkdir
|
||||
body: "`os.mkdir()` should be replaced by `Path.mkdir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -42,7 +42,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsMakedirs
|
||||
name: PathlibMakedirs
|
||||
body: "`os.makedirs()` should be replaced by `Path.mkdir(parents=True)`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -55,7 +55,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRename
|
||||
name: PathlibRename
|
||||
body: "`os.rename()` should be replaced by `Path.rename()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -81,7 +81,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRmdir
|
||||
name: PathlibRmdir
|
||||
body: "`os.rmdir()` should be replaced by `Path.rmdir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -94,7 +94,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRemove
|
||||
name: PathlibRemove
|
||||
body: "`os.remove()` should be replaced by `Path.unlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -107,7 +107,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsUnlink
|
||||
name: PathlibUnlink
|
||||
body: "`os.unlink()` should be replaced by `Path.unlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -120,7 +120,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsGetcwd
|
||||
name: PathlibGetcwd
|
||||
body: "`os.getcwd()` should be replaced by `Path.cwd()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -133,7 +133,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathExists
|
||||
name: PathlibExists
|
||||
body: "`os.path.exists()` should be replaced by `Path.exists()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -146,7 +146,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathExpanduser
|
||||
name: PathlibExpanduser
|
||||
body: "`os.path.expanduser()` should be replaced by `Path.expanduser()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -159,7 +159,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsdir
|
||||
name: PathlibIsDir
|
||||
body: "`os.path.isdir()` should be replaced by `Path.is_dir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -172,7 +172,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsfile
|
||||
name: PathlibIsFile
|
||||
body: "`os.path.isfile()` should be replaced by `Path.is_file()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -185,7 +185,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIslink
|
||||
name: PathlibIsLink
|
||||
body: "`os.path.islink()` should be replaced by `Path.is_symlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -198,7 +198,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsReadlink
|
||||
name: PathlibReadlink
|
||||
body: "`os.readlink()` should be replaced by `Path.readlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -211,7 +211,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsStat
|
||||
name: PathlibStat
|
||||
body: "`os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -224,7 +224,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsabs
|
||||
name: PathlibIsAbs
|
||||
body: "`os.path.isabs()` should be replaced by `Path.is_absolute()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -237,7 +237,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathJoin
|
||||
name: PathlibJoin
|
||||
body: "`os.path.join()` should be replaced by `Path` with `/` operator"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -250,7 +250,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathBasename
|
||||
name: PathlibBasename
|
||||
body: "`os.path.basename()` should be replaced by `Path.name`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -263,7 +263,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathDirname
|
||||
name: PathlibDirname
|
||||
body: "`os.path.dirname()` should be replaced by `Path.parent`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -276,7 +276,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathSamefile
|
||||
name: PathlibSamefile
|
||||
body: "`os.path.samefile()` should be replaced by `Path.samefile()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -289,7 +289,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathSplitext
|
||||
name: PathlibSplitext
|
||||
body: "`os.path.splitext()` should be replaced by `Path.suffix`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -302,7 +302,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: BuiltinOpen
|
||||
name: PathlibOpen
|
||||
body: "`open()` should be replaced by `Path.open()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -315,7 +315,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: BuiltinOpen
|
||||
name: PathlibOpen
|
||||
body: "`open()` should be replaced by `Path.open()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -328,7 +328,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsGetcwd
|
||||
name: PathlibGetcwd
|
||||
body: "`os.getcwd()` should be replaced by `Path.cwd()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: OsPathAbspath
|
||||
name: PathlibAbspath
|
||||
body: "`os.path.abspath()` should be replaced by `Path.resolve()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -16,7 +16,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsChmod
|
||||
name: PathlibChmod
|
||||
body: "`os.chmod()` should be replaced by `Path.chmod()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -29,7 +29,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsMkdir
|
||||
name: PathlibMkdir
|
||||
body: "`os.mkdir()` should be replaced by `Path.mkdir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -42,7 +42,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsMakedirs
|
||||
name: PathlibMakedirs
|
||||
body: "`os.makedirs()` should be replaced by `Path.mkdir(parents=True)`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -55,7 +55,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRename
|
||||
name: PathlibRename
|
||||
body: "`os.rename()` should be replaced by `Path.rename()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -81,7 +81,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRmdir
|
||||
name: PathlibRmdir
|
||||
body: "`os.rmdir()` should be replaced by `Path.rmdir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -94,7 +94,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRemove
|
||||
name: PathlibRemove
|
||||
body: "`os.remove()` should be replaced by `Path.unlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -107,7 +107,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsUnlink
|
||||
name: PathlibUnlink
|
||||
body: "`os.unlink()` should be replaced by `Path.unlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -120,7 +120,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsGetcwd
|
||||
name: PathlibGetcwd
|
||||
body: "`os.getcwd()` should be replaced by `Path.cwd()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -133,7 +133,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathExists
|
||||
name: PathlibExists
|
||||
body: "`os.path.exists()` should be replaced by `Path.exists()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -146,7 +146,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathExpanduser
|
||||
name: PathlibExpanduser
|
||||
body: "`os.path.expanduser()` should be replaced by `Path.expanduser()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -159,7 +159,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsdir
|
||||
name: PathlibIsDir
|
||||
body: "`os.path.isdir()` should be replaced by `Path.is_dir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -172,7 +172,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsfile
|
||||
name: PathlibIsFile
|
||||
body: "`os.path.isfile()` should be replaced by `Path.is_file()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -185,7 +185,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIslink
|
||||
name: PathlibIsLink
|
||||
body: "`os.path.islink()` should be replaced by `Path.is_symlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -198,7 +198,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsReadlink
|
||||
name: PathlibReadlink
|
||||
body: "`os.readlink()` should be replaced by `Path.readlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -211,7 +211,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsStat
|
||||
name: PathlibStat
|
||||
body: "`os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -224,7 +224,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsabs
|
||||
name: PathlibIsAbs
|
||||
body: "`os.path.isabs()` should be replaced by `Path.is_absolute()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -237,7 +237,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathJoin
|
||||
name: PathlibJoin
|
||||
body: "`os.path.join()` should be replaced by `Path` with `/` operator"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -250,7 +250,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathBasename
|
||||
name: PathlibBasename
|
||||
body: "`os.path.basename()` should be replaced by `Path.name`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -263,7 +263,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathDirname
|
||||
name: PathlibDirname
|
||||
body: "`os.path.dirname()` should be replaced by `Path.parent`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -276,7 +276,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathSamefile
|
||||
name: PathlibSamefile
|
||||
body: "`os.path.samefile()` should be replaced by `Path.samefile()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -289,7 +289,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathSplitext
|
||||
name: PathlibSplitext
|
||||
body: "`os.path.splitext()` should be replaced by `Path.suffix`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: OsPathAbspath
|
||||
name: PathlibAbspath
|
||||
body: "`os.path.abspath()` should be replaced by `Path.resolve()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -16,7 +16,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsChmod
|
||||
name: PathlibChmod
|
||||
body: "`os.chmod()` should be replaced by `Path.chmod()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -29,7 +29,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsMkdir
|
||||
name: PathlibMkdir
|
||||
body: "`os.mkdir()` should be replaced by `Path.mkdir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -42,7 +42,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsMakedirs
|
||||
name: PathlibMakedirs
|
||||
body: "`os.makedirs()` should be replaced by `Path.mkdir(parents=True)`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -55,7 +55,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRename
|
||||
name: PathlibRename
|
||||
body: "`os.rename()` should be replaced by `Path.rename()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -81,7 +81,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRmdir
|
||||
name: PathlibRmdir
|
||||
body: "`os.rmdir()` should be replaced by `Path.rmdir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -94,7 +94,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRemove
|
||||
name: PathlibRemove
|
||||
body: "`os.remove()` should be replaced by `Path.unlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -107,7 +107,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsUnlink
|
||||
name: PathlibUnlink
|
||||
body: "`os.unlink()` should be replaced by `Path.unlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -120,7 +120,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsGetcwd
|
||||
name: PathlibGetcwd
|
||||
body: "`os.getcwd()` should be replaced by `Path.cwd()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -133,7 +133,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathExists
|
||||
name: PathlibExists
|
||||
body: "`os.path.exists()` should be replaced by `Path.exists()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -146,7 +146,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathExpanduser
|
||||
name: PathlibExpanduser
|
||||
body: "`os.path.expanduser()` should be replaced by `Path.expanduser()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -159,7 +159,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsdir
|
||||
name: PathlibIsDir
|
||||
body: "`os.path.isdir()` should be replaced by `Path.is_dir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -172,7 +172,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsfile
|
||||
name: PathlibIsFile
|
||||
body: "`os.path.isfile()` should be replaced by `Path.is_file()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -185,7 +185,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIslink
|
||||
name: PathlibIsLink
|
||||
body: "`os.path.islink()` should be replaced by `Path.is_symlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -198,7 +198,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsReadlink
|
||||
name: PathlibReadlink
|
||||
body: "`os.readlink()` should be replaced by `Path.readlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -211,7 +211,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsStat
|
||||
name: PathlibStat
|
||||
body: "`os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -224,7 +224,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsabs
|
||||
name: PathlibIsAbs
|
||||
body: "`os.path.isabs()` should be replaced by `Path.is_absolute()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -237,7 +237,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathJoin
|
||||
name: PathlibJoin
|
||||
body: "`os.path.join()` should be replaced by `Path` with `/` operator"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -250,7 +250,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathBasename
|
||||
name: PathlibBasename
|
||||
body: "`os.path.basename()` should be replaced by `Path.name`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -263,7 +263,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathDirname
|
||||
name: PathlibDirname
|
||||
body: "`os.path.dirname()` should be replaced by `Path.parent`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -276,7 +276,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathSamefile
|
||||
name: PathlibSamefile
|
||||
body: "`os.path.samefile()` should be replaced by `Path.samefile()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -289,7 +289,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathSplitext
|
||||
name: PathlibSplitext
|
||||
body: "`os.path.splitext()` should be replaced by `Path.suffix`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -302,7 +302,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: BuiltinOpen
|
||||
name: PathlibOpen
|
||||
body: "`open()` should be replaced by `Path.open()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -315,7 +315,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: BuiltinOpen
|
||||
name: PathlibOpen
|
||||
body: "`open()` should be replaced by `Path.open()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: OsPathAbspath
|
||||
name: PathlibAbspath
|
||||
body: "`os.path.abspath()` should be replaced by `Path.resolve()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -16,7 +16,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsChmod
|
||||
name: PathlibChmod
|
||||
body: "`os.chmod()` should be replaced by `Path.chmod()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -29,7 +29,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsMkdir
|
||||
name: PathlibMkdir
|
||||
body: "`os.mkdir()` should be replaced by `Path.mkdir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -42,7 +42,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsMakedirs
|
||||
name: PathlibMakedirs
|
||||
body: "`os.makedirs()` should be replaced by `Path.mkdir(parents=True)`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -55,7 +55,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRename
|
||||
name: PathlibRename
|
||||
body: "`os.rename()` should be replaced by `Path.rename()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -81,7 +81,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRmdir
|
||||
name: PathlibRmdir
|
||||
body: "`os.rmdir()` should be replaced by `Path.rmdir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -94,7 +94,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsRemove
|
||||
name: PathlibRemove
|
||||
body: "`os.remove()` should be replaced by `Path.unlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -107,7 +107,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsUnlink
|
||||
name: PathlibUnlink
|
||||
body: "`os.unlink()` should be replaced by `Path.unlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -120,7 +120,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsGetcwd
|
||||
name: PathlibGetcwd
|
||||
body: "`os.getcwd()` should be replaced by `Path.cwd()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -133,7 +133,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathExists
|
||||
name: PathlibExists
|
||||
body: "`os.path.exists()` should be replaced by `Path.exists()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -146,7 +146,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathExpanduser
|
||||
name: PathlibExpanduser
|
||||
body: "`os.path.expanduser()` should be replaced by `Path.expanduser()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -159,7 +159,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsdir
|
||||
name: PathlibIsDir
|
||||
body: "`os.path.isdir()` should be replaced by `Path.is_dir()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -172,7 +172,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsfile
|
||||
name: PathlibIsFile
|
||||
body: "`os.path.isfile()` should be replaced by `Path.is_file()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -185,7 +185,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIslink
|
||||
name: PathlibIsLink
|
||||
body: "`os.path.islink()` should be replaced by `Path.is_symlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -198,7 +198,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsReadlink
|
||||
name: PathlibReadlink
|
||||
body: "`os.readlink()` should be replaced by `Path.readlink()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -211,7 +211,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsStat
|
||||
name: PathlibStat
|
||||
body: "`os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -224,7 +224,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathIsabs
|
||||
name: PathlibIsAbs
|
||||
body: "`os.path.isabs()` should be replaced by `Path.is_absolute()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -237,7 +237,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathJoin
|
||||
name: PathlibJoin
|
||||
body: "`os.path.join()` should be replaced by `Path` with `/` operator"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -250,7 +250,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathBasename
|
||||
name: PathlibBasename
|
||||
body: "`os.path.basename()` should be replaced by `Path.name`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -263,7 +263,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathDirname
|
||||
name: PathlibDirname
|
||||
body: "`os.path.dirname()` should be replaced by `Path.parent`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -276,7 +276,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathSamefile
|
||||
name: PathlibSamefile
|
||||
body: "`os.path.samefile()` should be replaced by `Path.samefile()`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -289,7 +289,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: OsPathSplitext
|
||||
name: PathlibSplitext
|
||||
body: "`os.path.splitext()` should be replaced by `Path.suffix`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
|
||||
@@ -3,9 +3,9 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
// PTH100
|
||||
#[violation]
|
||||
pub struct OsPathAbspath;
|
||||
pub struct PathlibAbspath;
|
||||
|
||||
impl Violation for OsPathAbspath {
|
||||
impl Violation for PathlibAbspath {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.abspath()` should be replaced by `Path.resolve()`")
|
||||
@@ -14,9 +14,9 @@ impl Violation for OsPathAbspath {
|
||||
|
||||
// PTH101
|
||||
#[violation]
|
||||
pub struct OsChmod;
|
||||
pub struct PathlibChmod;
|
||||
|
||||
impl Violation for OsChmod {
|
||||
impl Violation for PathlibChmod {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.chmod()` should be replaced by `Path.chmod()`")
|
||||
@@ -25,9 +25,9 @@ impl Violation for OsChmod {
|
||||
|
||||
// PTH102
|
||||
#[violation]
|
||||
pub struct OsMakedirs;
|
||||
pub struct PathlibMakedirs;
|
||||
|
||||
impl Violation for OsMakedirs {
|
||||
impl Violation for PathlibMakedirs {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.makedirs()` should be replaced by `Path.mkdir(parents=True)`")
|
||||
@@ -36,9 +36,9 @@ impl Violation for OsMakedirs {
|
||||
|
||||
// PTH103
|
||||
#[violation]
|
||||
pub struct OsMkdir;
|
||||
pub struct PathlibMkdir;
|
||||
|
||||
impl Violation for OsMkdir {
|
||||
impl Violation for PathlibMkdir {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.mkdir()` should be replaced by `Path.mkdir()`")
|
||||
@@ -47,9 +47,9 @@ impl Violation for OsMkdir {
|
||||
|
||||
// PTH104
|
||||
#[violation]
|
||||
pub struct OsRename;
|
||||
pub struct PathlibRename;
|
||||
|
||||
impl Violation for OsRename {
|
||||
impl Violation for PathlibRename {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.rename()` should be replaced by `Path.rename()`")
|
||||
@@ -69,9 +69,9 @@ impl Violation for PathlibReplace {
|
||||
|
||||
// PTH106
|
||||
#[violation]
|
||||
pub struct OsRmdir;
|
||||
pub struct PathlibRmdir;
|
||||
|
||||
impl Violation for OsRmdir {
|
||||
impl Violation for PathlibRmdir {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.rmdir()` should be replaced by `Path.rmdir()`")
|
||||
@@ -80,9 +80,9 @@ impl Violation for OsRmdir {
|
||||
|
||||
// PTH107
|
||||
#[violation]
|
||||
pub struct OsRemove;
|
||||
pub struct PathlibRemove;
|
||||
|
||||
impl Violation for OsRemove {
|
||||
impl Violation for PathlibRemove {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.remove()` should be replaced by `Path.unlink()`")
|
||||
@@ -91,9 +91,9 @@ impl Violation for OsRemove {
|
||||
|
||||
// PTH108
|
||||
#[violation]
|
||||
pub struct OsUnlink;
|
||||
pub struct PathlibUnlink;
|
||||
|
||||
impl Violation for OsUnlink {
|
||||
impl Violation for PathlibUnlink {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.unlink()` should be replaced by `Path.unlink()`")
|
||||
@@ -102,9 +102,9 @@ impl Violation for OsUnlink {
|
||||
|
||||
// PTH109
|
||||
#[violation]
|
||||
pub struct OsGetcwd;
|
||||
pub struct PathlibGetcwd;
|
||||
|
||||
impl Violation for OsGetcwd {
|
||||
impl Violation for PathlibGetcwd {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.getcwd()` should be replaced by `Path.cwd()`")
|
||||
@@ -113,9 +113,9 @@ impl Violation for OsGetcwd {
|
||||
|
||||
// PTH110
|
||||
#[violation]
|
||||
pub struct OsPathExists;
|
||||
pub struct PathlibExists;
|
||||
|
||||
impl Violation for OsPathExists {
|
||||
impl Violation for PathlibExists {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.exists()` should be replaced by `Path.exists()`")
|
||||
@@ -124,9 +124,9 @@ impl Violation for OsPathExists {
|
||||
|
||||
// PTH111
|
||||
#[violation]
|
||||
pub struct OsPathExpanduser;
|
||||
pub struct PathlibExpanduser;
|
||||
|
||||
impl Violation for OsPathExpanduser {
|
||||
impl Violation for PathlibExpanduser {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.expanduser()` should be replaced by `Path.expanduser()`")
|
||||
@@ -135,9 +135,9 @@ impl Violation for OsPathExpanduser {
|
||||
|
||||
// PTH112
|
||||
#[violation]
|
||||
pub struct OsPathIsdir;
|
||||
pub struct PathlibIsDir;
|
||||
|
||||
impl Violation for OsPathIsdir {
|
||||
impl Violation for PathlibIsDir {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.isdir()` should be replaced by `Path.is_dir()`")
|
||||
@@ -146,9 +146,9 @@ impl Violation for OsPathIsdir {
|
||||
|
||||
// PTH113
|
||||
#[violation]
|
||||
pub struct OsPathIsfile;
|
||||
pub struct PathlibIsFile;
|
||||
|
||||
impl Violation for OsPathIsfile {
|
||||
impl Violation for PathlibIsFile {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.isfile()` should be replaced by `Path.is_file()`")
|
||||
@@ -157,9 +157,9 @@ impl Violation for OsPathIsfile {
|
||||
|
||||
// PTH114
|
||||
#[violation]
|
||||
pub struct OsPathIslink;
|
||||
pub struct PathlibIsLink;
|
||||
|
||||
impl Violation for OsPathIslink {
|
||||
impl Violation for PathlibIsLink {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.islink()` should be replaced by `Path.is_symlink()`")
|
||||
@@ -168,9 +168,9 @@ impl Violation for OsPathIslink {
|
||||
|
||||
// PTH115
|
||||
#[violation]
|
||||
pub struct OsReadlink;
|
||||
pub struct PathlibReadlink;
|
||||
|
||||
impl Violation for OsReadlink {
|
||||
impl Violation for PathlibReadlink {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.readlink()` should be replaced by `Path.readlink()`")
|
||||
@@ -179,9 +179,9 @@ impl Violation for OsReadlink {
|
||||
|
||||
// PTH116
|
||||
#[violation]
|
||||
pub struct OsStat;
|
||||
pub struct PathlibStat;
|
||||
|
||||
impl Violation for OsStat {
|
||||
impl Violation for PathlibStat {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
@@ -192,9 +192,9 @@ impl Violation for OsStat {
|
||||
|
||||
// PTH117
|
||||
#[violation]
|
||||
pub struct OsPathIsabs;
|
||||
pub struct PathlibIsAbs;
|
||||
|
||||
impl Violation for OsPathIsabs {
|
||||
impl Violation for PathlibIsAbs {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.isabs()` should be replaced by `Path.is_absolute()`")
|
||||
@@ -203,9 +203,9 @@ impl Violation for OsPathIsabs {
|
||||
|
||||
// PTH118
|
||||
#[violation]
|
||||
pub struct OsPathJoin;
|
||||
pub struct PathlibJoin;
|
||||
|
||||
impl Violation for OsPathJoin {
|
||||
impl Violation for PathlibJoin {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.join()` should be replaced by `Path` with `/` operator")
|
||||
@@ -214,9 +214,9 @@ impl Violation for OsPathJoin {
|
||||
|
||||
// PTH119
|
||||
#[violation]
|
||||
pub struct OsPathBasename;
|
||||
pub struct PathlibBasename;
|
||||
|
||||
impl Violation for OsPathBasename {
|
||||
impl Violation for PathlibBasename {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.basename()` should be replaced by `Path.name`")
|
||||
@@ -225,9 +225,9 @@ impl Violation for OsPathBasename {
|
||||
|
||||
// PTH120
|
||||
#[violation]
|
||||
pub struct OsPathDirname;
|
||||
pub struct PathlibDirname;
|
||||
|
||||
impl Violation for OsPathDirname {
|
||||
impl Violation for PathlibDirname {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.dirname()` should be replaced by `Path.parent`")
|
||||
@@ -236,9 +236,9 @@ impl Violation for OsPathDirname {
|
||||
|
||||
// PTH121
|
||||
#[violation]
|
||||
pub struct OsPathSamefile;
|
||||
pub struct PathlibSamefile;
|
||||
|
||||
impl Violation for OsPathSamefile {
|
||||
impl Violation for PathlibSamefile {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.samefile()` should be replaced by `Path.samefile()`")
|
||||
@@ -247,9 +247,9 @@ impl Violation for OsPathSamefile {
|
||||
|
||||
// PTH122
|
||||
#[violation]
|
||||
pub struct OsPathSplitext;
|
||||
pub struct PathlibSplitext;
|
||||
|
||||
impl Violation for OsPathSplitext {
|
||||
impl Violation for PathlibSplitext {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`os.path.splitext()` should be replaced by `Path.suffix`")
|
||||
@@ -258,9 +258,9 @@ impl Violation for OsPathSplitext {
|
||||
|
||||
// PTH123
|
||||
#[violation]
|
||||
pub struct BuiltinOpen;
|
||||
pub struct PathlibOpen;
|
||||
|
||||
impl Violation for BuiltinOpen {
|
||||
impl Violation for PathlibOpen {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`open()` should be replaced by `Path.open()`")
|
||||
@@ -269,9 +269,9 @@ impl Violation for BuiltinOpen {
|
||||
|
||||
// PTH124
|
||||
#[violation]
|
||||
pub struct PyPath;
|
||||
pub struct PathlibPyPath;
|
||||
|
||||
impl Violation for PyPath {
|
||||
impl Violation for PathlibPyPath {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`py.path` is in maintenance mode, use `pathlib` instead")
|
||||
|
||||
@@ -665,7 +665,6 @@ mod tests {
|
||||
&Settings {
|
||||
isort: super::settings::Settings {
|
||||
force_sort_within_sections: true,
|
||||
force_to_top: BTreeSet::from(["z".to_string()]),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
src: vec![test_resource_path("fixtures/isort")],
|
||||
|
||||
@@ -11,15 +11,15 @@ expression: diagnostics
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
row: 12
|
||||
column: 0
|
||||
fix:
|
||||
content: "import a # import\nimport b as b1 # import_as\nimport c.d\nimport z\nfrom a import a1 # import_from\nfrom c import * # import_from_star\nfrom z import z1\n\nfrom ...grandparent import fn3\nfrom ..parent import *\nfrom . import my\nfrom .my import fn\nfrom .my.nested import fn2\n"
|
||||
content: "import a # import\nimport b as b1 # import_as\nimport c.d\nfrom a import a1 # import_from\nfrom c import * # import_from_star\n\nfrom ...grandparent import fn3\nfrom ..parent import *\nfrom . import my\nfrom .my import fn\nfrom .my.nested import fn2\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
row: 12
|
||||
column: 0
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -11,15 +11,15 @@ expression: diagnostics
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
row: 12
|
||||
column: 0
|
||||
fix:
|
||||
content: "import z\nfrom z import z1\nimport a # import\nfrom a import a1 # import_from\nimport b as b1 # import_as\nfrom c import * # import_from_star\nimport c.d\n\nfrom ...grandparent import fn3\nfrom ..parent import *\nfrom . import my\nfrom .my import fn\nfrom .my.nested import fn2\n"
|
||||
content: "import a # import\nfrom a import a1 # import_from\nimport b as b1 # import_as\nfrom c import * # import_from_star\nimport c.d\n\nfrom ...grandparent import fn3\nfrom ..parent import *\nfrom . import my\nfrom .my import fn\nfrom .my.nested import fn2\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
row: 12
|
||||
column: 0
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -44,28 +44,29 @@ fn prefix(
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare two module names' by their `force-to-top`ness.
|
||||
fn cmp_force_to_top(name1: &str, name2: &str, force_to_top: &BTreeSet<String>) -> Ordering {
|
||||
let force_to_top1 = force_to_top.contains(name1);
|
||||
let force_to_top2 = force_to_top.contains(name2);
|
||||
force_to_top1.cmp(&force_to_top2).reverse()
|
||||
}
|
||||
|
||||
/// Compare two top-level modules.
|
||||
pub fn cmp_modules(
|
||||
alias1: &AliasData,
|
||||
alias2: &AliasData,
|
||||
force_to_top: &BTreeSet<String>,
|
||||
) -> Ordering {
|
||||
cmp_force_to_top(alias1.name, alias2.name, force_to_top)
|
||||
.then_with(|| natord::compare_ignore_case(alias1.name, alias2.name))
|
||||
.then_with(|| natord::compare(alias1.name, alias2.name))
|
||||
.then_with(|| match (alias1.asname, alias2.asname) {
|
||||
(None, None) => Ordering::Equal,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(Some(asname1), Some(asname2)) => natord::compare(asname1, asname2),
|
||||
})
|
||||
(match (
|
||||
force_to_top.contains(alias1.name),
|
||||
force_to_top.contains(alias2.name),
|
||||
) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(false, false) => Ordering::Equal,
|
||||
(true, false) => Ordering::Less,
|
||||
(false, true) => Ordering::Greater,
|
||||
})
|
||||
.then_with(|| natord::compare_ignore_case(alias1.name, alias2.name))
|
||||
.then_with(|| natord::compare(alias1.name, alias2.name))
|
||||
.then_with(|| match (alias1.asname, alias2.asname) {
|
||||
(None, None) => Ordering::Equal,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(Some(asname1), Some(asname2)) => natord::compare(asname1, asname2),
|
||||
})
|
||||
}
|
||||
|
||||
/// Compare two member imports within `StmtKind::ImportFrom` blocks.
|
||||
@@ -123,11 +124,15 @@ pub fn cmp_import_from(
|
||||
relative_imports_order,
|
||||
)
|
||||
.then_with(|| {
|
||||
cmp_force_to_top(
|
||||
&import_from1.module_name(),
|
||||
&import_from2.module_name(),
|
||||
force_to_top,
|
||||
)
|
||||
match (
|
||||
force_to_top.contains(&import_from1.module_name()),
|
||||
force_to_top.contains(&import_from2.module_name()),
|
||||
) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(false, false) => Ordering::Equal,
|
||||
(true, false) => Ordering::Less,
|
||||
(false, true) => Ordering::Greater,
|
||||
}
|
||||
})
|
||||
.then_with(|| match (&import_from1.module, import_from2.module) {
|
||||
(None, None) => Ordering::Equal,
|
||||
@@ -138,17 +143,6 @@ pub fn cmp_import_from(
|
||||
})
|
||||
}
|
||||
|
||||
/// Compare an import to an import-from.
|
||||
fn cmp_import_import_from(
|
||||
import: &AliasData,
|
||||
import_from: &ImportFromData,
|
||||
force_to_top: &BTreeSet<String>,
|
||||
) -> Ordering {
|
||||
cmp_force_to_top(import.name, &import_from.module_name(), force_to_top).then_with(|| {
|
||||
natord::compare_ignore_case(import.name, import_from.module.unwrap_or_default())
|
||||
})
|
||||
}
|
||||
|
||||
/// Compare two [`EitherImport`] enums which may be [`Import`] or [`ImportFrom`]
|
||||
/// structs.
|
||||
pub fn cmp_either_import(
|
||||
@@ -160,10 +154,10 @@ pub fn cmp_either_import(
|
||||
match (a, b) {
|
||||
(Import((alias1, _)), Import((alias2, _))) => cmp_modules(alias1, alias2, force_to_top),
|
||||
(ImportFrom((import_from, ..)), Import((alias, _))) => {
|
||||
cmp_import_import_from(alias, import_from, force_to_top).reverse()
|
||||
natord::compare_ignore_case(import_from.module.unwrap_or_default(), alias.name)
|
||||
}
|
||||
(Import((alias, _)), ImportFrom((import_from, ..))) => {
|
||||
cmp_import_import_from(alias, import_from, force_to_top)
|
||||
natord::compare_ignore_case(alias.name, import_from.module.unwrap_or_default())
|
||||
}
|
||||
(ImportFrom((import_from1, ..)), ImportFrom((import_from2, ..))) => cmp_import_from(
|
||||
import_from1,
|
||||
|
||||
@@ -33,12 +33,7 @@ impl AlwaysAutofixableViolation for MissingWhitespace {
|
||||
|
||||
/// E231
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn missing_whitespace(
|
||||
line: &str,
|
||||
row: usize,
|
||||
autofix: bool,
|
||||
indent_level: usize,
|
||||
) -> Vec<Diagnostic> {
|
||||
pub fn missing_whitespace(line: &str, row: usize, autofix: bool) -> Vec<Diagnostic> {
|
||||
let mut diagnostics = vec![];
|
||||
for (idx, char) in line.chars().enumerate() {
|
||||
if idx + 1 == line.len() {
|
||||
@@ -67,17 +62,11 @@ pub fn missing_whitespace(
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
kind,
|
||||
Range::new(
|
||||
Location::new(row, indent_level + idx),
|
||||
Location::new(row, indent_level + idx),
|
||||
),
|
||||
Range::new(Location::new(row, idx), Location::new(row, idx)),
|
||||
);
|
||||
|
||||
if autofix {
|
||||
diagnostic.amend(Fix::insertion(
|
||||
" ".to_string(),
|
||||
Location::new(row, indent_level + idx + 1),
|
||||
));
|
||||
diagnostic.amend(Fix::insertion(" ".to_string(), Location::new(row, idx + 1)));
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -86,11 +75,6 @@ pub fn missing_whitespace(
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn missing_whitespace(
|
||||
_line: &str,
|
||||
_row: usize,
|
||||
_autofix: bool,
|
||||
indent_level: usize,
|
||||
) -> Vec<Diagnostic> {
|
||||
pub fn missing_whitespace(_line: &str, _row: usize, _autofix: bool) -> Vec<Diagnostic> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
@@ -62,24 +62,4 @@ expression: diagnostics
|
||||
row: 6
|
||||
column: 10
|
||||
parent: ~
|
||||
- kind:
|
||||
name: MissingWhitespace
|
||||
body: "Missing whitespace after ','"
|
||||
suggestion: "Added missing whitespace after ','"
|
||||
fixable: true
|
||||
location:
|
||||
row: 19
|
||||
column: 9
|
||||
end_location:
|
||||
row: 19
|
||||
column: 9
|
||||
fix:
|
||||
content: " "
|
||||
location:
|
||||
row: 19
|
||||
column: 10
|
||||
end_location:
|
||||
row: 19
|
||||
column: 10
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,52 +9,46 @@ use ruff_python_stdlib::future::ALL_FEATURE_NAMES;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum UnusedImportContext {
|
||||
ExceptHandler,
|
||||
Init,
|
||||
}
|
||||
|
||||
#[violation]
|
||||
pub struct UnusedImport {
|
||||
pub name: String,
|
||||
pub context: Option<UnusedImportContext>,
|
||||
pub ignore_init: bool,
|
||||
pub multiple: bool,
|
||||
}
|
||||
|
||||
fn fmt_unused_import_autofix_msg(unused_import: &UnusedImport) -> String {
|
||||
let UnusedImport { name, multiple, .. } = unused_import;
|
||||
if *multiple {
|
||||
"Remove unused import".to_string()
|
||||
} else {
|
||||
format!("Remove unused import: `{name}`")
|
||||
}
|
||||
}
|
||||
impl Violation for UnusedImport {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnusedImport { name, context, .. } = self;
|
||||
match context {
|
||||
Some(UnusedImportContext::ExceptHandler) => {
|
||||
format!(
|
||||
"`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability"
|
||||
)
|
||||
}
|
||||
Some(UnusedImportContext::Init) => {
|
||||
format!(
|
||||
"`{name}` imported but unused; consider adding to `__all__` or using a redundant \
|
||||
alias"
|
||||
)
|
||||
}
|
||||
None => format!("`{name}` imported but unused"),
|
||||
let UnusedImport {
|
||||
name, ignore_init, ..
|
||||
} = self;
|
||||
if *ignore_init {
|
||||
format!(
|
||||
"`{name}` imported but unused; consider adding to `__all__` or using a redundant \
|
||||
alias"
|
||||
)
|
||||
} else {
|
||||
format!("`{name}` imported but unused")
|
||||
}
|
||||
}
|
||||
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
let UnusedImport { context, .. } = self;
|
||||
context
|
||||
.is_none()
|
||||
.then_some(|UnusedImport { name, multiple, .. }| {
|
||||
if *multiple {
|
||||
"Remove unused import".to_string()
|
||||
} else {
|
||||
format!("Remove unused import: `{name}`")
|
||||
}
|
||||
})
|
||||
let UnusedImport { ignore_init, .. } = self;
|
||||
if *ignore_init {
|
||||
None
|
||||
} else {
|
||||
Some(fmt_unused_import_autofix_msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[violation]
|
||||
|
||||
@@ -10,7 +10,7 @@ pub use if_tuple::{if_tuple, IfTuple};
|
||||
pub use imports::{
|
||||
future_feature_not_defined, FutureFeatureNotDefined, ImportShadowedByLoopVar, LateFutureImport,
|
||||
UndefinedLocalWithImportStar, UndefinedLocalWithImportStarUsage,
|
||||
UndefinedLocalWithNestedImportStarUsage, UnusedImport, UnusedImportContext,
|
||||
UndefinedLocalWithNestedImportStarUsage, UnusedImport,
|
||||
};
|
||||
pub use invalid_literal_comparisons::{invalid_literal_comparison, IsLiteral};
|
||||
pub use invalid_print_syntax::{invalid_print_syntax, InvalidPrintSyntax};
|
||||
|
||||
@@ -2,37 +2,5 @@
|
||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: UnusedImport
|
||||
body: "`orjson` imported but unused; consider using `importlib.util.find_spec` to test for availability"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 6
|
||||
column: 15
|
||||
end_location:
|
||||
row: 6
|
||||
column: 21
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: UnusedImport
|
||||
body: "`orjson` imported but unused"
|
||||
suggestion: "Remove unused import: `orjson`"
|
||||
fixable: true
|
||||
location:
|
||||
row: 15
|
||||
column: 15
|
||||
end_location:
|
||||
row: 15
|
||||
column: 21
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 16
|
||||
column: 0
|
||||
parent: ~
|
||||
[]
|
||||
|
||||
|
||||
@@ -61,10 +61,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 58
|
||||
column: 4
|
||||
column: 3
|
||||
end_location:
|
||||
row: 58
|
||||
column: 7
|
||||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -139,10 +139,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 115
|
||||
column: 9
|
||||
column: 8
|
||||
end_location:
|
||||
row: 115
|
||||
column: 22
|
||||
column: 23
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -152,10 +152,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 123
|
||||
column: 14
|
||||
column: 13
|
||||
end_location:
|
||||
row: 123
|
||||
column: 17
|
||||
column: 18
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -165,10 +165,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 123
|
||||
column: 21
|
||||
column: 20
|
||||
end_location:
|
||||
row: 123
|
||||
column: 24
|
||||
column: 25
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,49 +9,49 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 11
|
||||
column: 10
|
||||
column: 9
|
||||
end_location:
|
||||
row: 11
|
||||
column: 15
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: UndefinedName
|
||||
body: "Undefined name `Model`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 18
|
||||
column: 17
|
||||
end_location:
|
||||
row: 18
|
||||
column: 22
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: UndefinedName
|
||||
body: "Undefined name `Model`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 24
|
||||
column: 13
|
||||
end_location:
|
||||
row: 24
|
||||
column: 18
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: UndefinedName
|
||||
body: "Undefined name `Model`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 30
|
||||
column: 11
|
||||
end_location:
|
||||
row: 30
|
||||
column: 16
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: UndefinedName
|
||||
body: "Undefined name `Model`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 18
|
||||
column: 16
|
||||
end_location:
|
||||
row: 18
|
||||
column: 23
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: UndefinedName
|
||||
body: "Undefined name `Model`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 24
|
||||
column: 12
|
||||
end_location:
|
||||
row: 24
|
||||
column: 19
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: UndefinedName
|
||||
body: "Undefined name `Model`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 30
|
||||
column: 10
|
||||
end_location:
|
||||
row: 30
|
||||
column: 17
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 18
|
||||
column: 27
|
||||
column: 26
|
||||
end_location:
|
||||
row: 18
|
||||
column: 29
|
||||
column: 30
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -22,10 +22,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 23
|
||||
column: 13
|
||||
column: 12
|
||||
end_location:
|
||||
row: 23
|
||||
column: 16
|
||||
column: 17
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 20
|
||||
column: 27
|
||||
column: 26
|
||||
end_location:
|
||||
row: 20
|
||||
column: 29
|
||||
column: 30
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -22,10 +22,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 25
|
||||
column: 13
|
||||
column: 12
|
||||
end_location:
|
||||
row: 25
|
||||
column: 16
|
||||
column: 17
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 5
|
||||
column: 12
|
||||
column: 11
|
||||
end_location:
|
||||
row: 5
|
||||
column: 17
|
||||
column: 18
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 11
|
||||
column: 9
|
||||
column: 8
|
||||
end_location:
|
||||
row: 11
|
||||
column: 12
|
||||
column: 13
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -22,10 +22,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 11
|
||||
column: 16
|
||||
column: 15
|
||||
end_location:
|
||||
row: 11
|
||||
column: 21
|
||||
column: 22
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,25 +9,25 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 4
|
||||
column: 10
|
||||
column: 9
|
||||
end_location:
|
||||
row: 4
|
||||
column: 15
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: UndefinedName
|
||||
body: "Undefined name `Model`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 9
|
||||
column: 11
|
||||
end_location:
|
||||
row: 9
|
||||
column: 16
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: UndefinedName
|
||||
body: "Undefined name `Model`"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 9
|
||||
column: 10
|
||||
end_location:
|
||||
row: 9
|
||||
column: 17
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: UndefinedName
|
||||
body: "Undefined name `Model`"
|
||||
@@ -35,10 +35,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 14
|
||||
column: 15
|
||||
column: 14
|
||||
end_location:
|
||||
row: 14
|
||||
column: 20
|
||||
column: 21
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -48,10 +48,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 19
|
||||
column: 31
|
||||
column: 30
|
||||
end_location:
|
||||
row: 19
|
||||
column: 36
|
||||
column: 37
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -61,10 +61,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 24
|
||||
column: 19
|
||||
column: 18
|
||||
end_location:
|
||||
row: 24
|
||||
column: 24
|
||||
column: 25
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 5
|
||||
column: 30
|
||||
column: 29
|
||||
end_location:
|
||||
row: 5
|
||||
column: 40
|
||||
column: 41
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 11
|
||||
column: 21
|
||||
column: 20
|
||||
end_location:
|
||||
row: 11
|
||||
column: 30
|
||||
column: 31
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -22,10 +22,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 12
|
||||
column: 26
|
||||
column: 25
|
||||
end_location:
|
||||
row: 12
|
||||
column: 35
|
||||
column: 36
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -35,10 +35,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 13
|
||||
column: 21
|
||||
column: 20
|
||||
end_location:
|
||||
row: 13
|
||||
column: 30
|
||||
column: 31
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 6
|
||||
column: 35
|
||||
column: 34
|
||||
end_location:
|
||||
row: 6
|
||||
column: 37
|
||||
column: 38
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 7
|
||||
column: 14
|
||||
column: 13
|
||||
end_location:
|
||||
row: 7
|
||||
column: 19
|
||||
column: 20
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 26
|
||||
column: 16
|
||||
column: 15
|
||||
end_location:
|
||||
row: 26
|
||||
column: 19
|
||||
column: 20
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
@@ -22,10 +22,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 33
|
||||
column: 16
|
||||
column: 15
|
||||
end_location:
|
||||
row: 33
|
||||
column: 19
|
||||
column: 20
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 26
|
||||
column: 16
|
||||
column: 15
|
||||
end_location:
|
||||
row: 26
|
||||
column: 19
|
||||
column: 20
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ mod tests {
|
||||
#[test_case(Rule::TooManyReturnStatements, Path::new("too_many_return_statements.py"); "PLR0911")]
|
||||
#[test_case(Rule::TooManyStatements, Path::new("too_many_statements.py"); "PLR0915")]
|
||||
#[test_case(Rule::UnnecessaryDirectLambdaCall, Path::new("unnecessary_direct_lambda_call.py"); "PLC3002")]
|
||||
#[test_case(Rule::LoadBeforeGlobalDeclaration, Path::new("load_before_global_declaration.py"); "PLE0118")]
|
||||
#[test_case(Rule::UsePriorToGlobalDeclaration, Path::new("use_prior_to_global_declaration.py"); "PLE0118")]
|
||||
#[test_case(Rule::UselessElseOnLoop, Path::new("useless_else_on_loop.py"); "PLW0120")]
|
||||
#[test_case(Rule::UselessImportAlias, Path::new("import_aliasing.py"); "PLC0414")]
|
||||
#[test_case(Rule::UselessReturn, Path::new("useless_return.py"); "PLR1711")]
|
||||
|
||||
@@ -18,9 +18,6 @@ pub use invalid_string_characters::{
|
||||
invalid_string_characters, InvalidCharacterBackspace, InvalidCharacterEsc, InvalidCharacterNul,
|
||||
InvalidCharacterSub, InvalidCharacterZeroWidthSpace,
|
||||
};
|
||||
pub use load_before_global_declaration::{
|
||||
load_before_global_declaration, LoadBeforeGlobalDeclaration,
|
||||
};
|
||||
pub use logging::{logging_call, LoggingTooFewArgs, LoggingTooManyArgs};
|
||||
pub use magic_value_comparison::{magic_value_comparison, MagicValueComparison};
|
||||
pub use manual_import_from::{manual_from_import, ManualFromImport};
|
||||
@@ -37,6 +34,9 @@ pub use too_many_statements::{too_many_statements, TooManyStatements};
|
||||
pub use unnecessary_direct_lambda_call::{
|
||||
unnecessary_direct_lambda_call, UnnecessaryDirectLambdaCall,
|
||||
};
|
||||
pub use use_prior_to_global_declaration::{
|
||||
use_prior_to_global_declaration, UsePriorToGlobalDeclaration,
|
||||
};
|
||||
pub use useless_else_on_loop::{useless_else_on_loop, UselessElseOnLoop};
|
||||
pub use useless_import_alias::{useless_import_alias, UselessImportAlias};
|
||||
pub use useless_return::{useless_return, UselessReturn};
|
||||
@@ -59,7 +59,6 @@ mod invalid_all_object;
|
||||
mod invalid_envvar_default;
|
||||
mod invalid_envvar_value;
|
||||
mod invalid_string_characters;
|
||||
mod load_before_global_declaration;
|
||||
mod logging;
|
||||
mod magic_value_comparison;
|
||||
mod manual_import_from;
|
||||
@@ -74,6 +73,7 @@ mod too_many_branches;
|
||||
mod too_many_return_statements;
|
||||
mod too_many_statements;
|
||||
mod unnecessary_direct_lambda_call;
|
||||
mod use_prior_to_global_declaration;
|
||||
mod useless_else_on_loop;
|
||||
mod useless_import_alias;
|
||||
mod useless_return;
|
||||
|
||||
@@ -8,20 +8,20 @@ use ruff_python_ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
#[violation]
|
||||
pub struct LoadBeforeGlobalDeclaration {
|
||||
pub struct UsePriorToGlobalDeclaration {
|
||||
pub name: String,
|
||||
pub line: usize,
|
||||
}
|
||||
|
||||
impl Violation for LoadBeforeGlobalDeclaration {
|
||||
impl Violation for UsePriorToGlobalDeclaration {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let LoadBeforeGlobalDeclaration { name, line } = self;
|
||||
let UsePriorToGlobalDeclaration { name, line } = self;
|
||||
format!("Name `{name}` is used prior to global declaration on line {line}")
|
||||
}
|
||||
}
|
||||
/// PLE0118
|
||||
pub fn load_before_global_declaration(checker: &mut Checker, name: &str, expr: &Expr) {
|
||||
pub fn use_prior_to_global_declaration(checker: &mut Checker, name: &str, expr: &Expr) {
|
||||
let globals = match &checker.ctx.scope().kind {
|
||||
ScopeKind::Class(class_def) => &class_def.globals,
|
||||
ScopeKind::Function(function_def) => &function_def.globals,
|
||||
@@ -30,7 +30,7 @@ pub fn load_before_global_declaration(checker: &mut Checker, name: &str, expr: &
|
||||
if let Some(stmt) = globals.get(name) {
|
||||
if expr.location < stmt.location {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
LoadBeforeGlobalDeclaration {
|
||||
UsePriorToGlobalDeclaration {
|
||||
name: name.to_string(),
|
||||
line: stmt.location.row(),
|
||||
},
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/pylint/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 7"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -16,7 +16,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 17"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -29,7 +29,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 25"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -42,7 +42,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 35"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -55,7 +55,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 43"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -68,7 +68,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 53"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -81,7 +81,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 61"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -94,7 +94,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 71"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -107,7 +107,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 79"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -120,7 +120,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 89"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -133,7 +133,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 97"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -146,7 +146,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 107"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -159,7 +159,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: LoadBeforeGlobalDeclaration
|
||||
name: UsePriorToGlobalDeclaration
|
||||
body: "Name `x` is used prior to global declaration on line 114"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
@@ -3,7 +3,6 @@ use rustpython_parser::ast::Expr;
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::Range;
|
||||
use ruff_python_ast::typing::AnnotationKind;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
@@ -41,11 +40,7 @@ pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr) {
|
||||
.resolve_call_path(expr)
|
||||
.and_then(|call_path| call_path.last().copied())
|
||||
{
|
||||
let fixable = checker
|
||||
.ctx
|
||||
.in_deferred_string_type_definition
|
||||
.as_ref()
|
||||
.map_or(true, AnnotationKind::is_simple);
|
||||
let fixable = !checker.ctx.in_deferred_string_type_definition;
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NonPEP585Annotation {
|
||||
name: binding.to_string(),
|
||||
|
||||
@@ -4,7 +4,6 @@ use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::unparse_expr;
|
||||
use ruff_python_ast::types::Range;
|
||||
use ruff_python_ast::typing::AnnotationKind;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
@@ -100,11 +99,7 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
|
||||
};
|
||||
|
||||
// Avoid fixing forward references.
|
||||
let fixable = checker
|
||||
.ctx
|
||||
.in_deferred_string_type_definition
|
||||
.as_ref()
|
||||
.map_or(true, AnnotationKind::is_simple);
|
||||
let fixable = !checker.ctx.in_deferred_string_type_definition;
|
||||
|
||||
match typing_member {
|
||||
TypingMember::Optional => {
|
||||
|
||||
@@ -82,97 +82,30 @@ expression: diagnostics
|
||||
row: 25
|
||||
column: 14
|
||||
parent: ~
|
||||
- kind:
|
||||
name: NonPEP585Annotation
|
||||
body: "Use `list` instead of `List` for type annotations"
|
||||
suggestion: "Replace `List` with `list`"
|
||||
fixable: true
|
||||
location:
|
||||
row: 29
|
||||
column: 10
|
||||
end_location:
|
||||
row: 29
|
||||
column: 14
|
||||
fix:
|
||||
content: list
|
||||
location:
|
||||
row: 29
|
||||
column: 10
|
||||
end_location:
|
||||
row: 29
|
||||
column: 14
|
||||
parent: ~
|
||||
- kind:
|
||||
name: NonPEP585Annotation
|
||||
body: "Use `list` instead of `List` for type annotations"
|
||||
suggestion: "Replace `List` with `list`"
|
||||
fixable: true
|
||||
location:
|
||||
row: 33
|
||||
column: 11
|
||||
end_location:
|
||||
row: 33
|
||||
column: 15
|
||||
fix:
|
||||
content: list
|
||||
location:
|
||||
row: 33
|
||||
column: 11
|
||||
end_location:
|
||||
row: 33
|
||||
column: 15
|
||||
parent: ~
|
||||
- kind:
|
||||
name: NonPEP585Annotation
|
||||
body: "Use `list` instead of `List` for type annotations"
|
||||
suggestion: "Replace `List` with `list`"
|
||||
fixable: true
|
||||
location:
|
||||
row: 37
|
||||
column: 10
|
||||
end_location:
|
||||
row: 37
|
||||
column: 14
|
||||
fix:
|
||||
content: list
|
||||
location:
|
||||
row: 37
|
||||
column: 10
|
||||
end_location:
|
||||
row: 37
|
||||
column: 14
|
||||
parent: ~
|
||||
- kind:
|
||||
name: NonPEP585Annotation
|
||||
body: "Use `list` instead of `List` for type annotations"
|
||||
suggestion: "Replace `List` with `list`"
|
||||
fixable: true
|
||||
location:
|
||||
row: 41
|
||||
column: 12
|
||||
end_location:
|
||||
row: 41
|
||||
column: 16
|
||||
fix:
|
||||
content: list
|
||||
location:
|
||||
row: 41
|
||||
column: 12
|
||||
end_location:
|
||||
row: 41
|
||||
column: 16
|
||||
parent: ~
|
||||
- kind:
|
||||
name: NonPEP585Annotation
|
||||
body: "Use `list` instead of `List` for type annotations"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 45
|
||||
row: 29
|
||||
column: 9
|
||||
end_location:
|
||||
row: 45
|
||||
column: 23
|
||||
row: 29
|
||||
column: 20
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: NonPEP585Annotation
|
||||
body: "Use `list` instead of `List` for type annotations"
|
||||
suggestion: "Replace `List` with `list`"
|
||||
fixable: true
|
||||
location:
|
||||
row: 36
|
||||
column: 9
|
||||
end_location:
|
||||
row: 36
|
||||
column: 13
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -145,62 +145,41 @@ expression: diagnostics
|
||||
- kind:
|
||||
name: NonPEP604Annotation
|
||||
body: "Use `X | Y` for type annotations"
|
||||
suggestion: "Convert to `X | Y`"
|
||||
fixable: true
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 30
|
||||
column: 10
|
||||
column: 9
|
||||
end_location:
|
||||
row: 30
|
||||
column: 46
|
||||
fix:
|
||||
content: "str | int | Union[float, bytes]"
|
||||
location:
|
||||
row: 30
|
||||
column: 10
|
||||
end_location:
|
||||
row: 30
|
||||
column: 46
|
||||
column: 47
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: NonPEP604Annotation
|
||||
body: "Use `X | Y` for type annotations"
|
||||
suggestion: "Convert to `X | Y`"
|
||||
fixable: true
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 30
|
||||
column: 26
|
||||
column: 9
|
||||
end_location:
|
||||
row: 30
|
||||
column: 45
|
||||
fix:
|
||||
content: float | bytes
|
||||
location:
|
||||
row: 30
|
||||
column: 26
|
||||
end_location:
|
||||
row: 30
|
||||
column: 45
|
||||
column: 47
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: NonPEP604Annotation
|
||||
body: "Use `X | Y` for type annotations"
|
||||
suggestion: "Convert to `X | Y`"
|
||||
fixable: true
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 34
|
||||
column: 10
|
||||
column: 9
|
||||
end_location:
|
||||
row: 34
|
||||
column: 32
|
||||
fix:
|
||||
content: str | int
|
||||
location:
|
||||
row: 34
|
||||
column: 10
|
||||
end_location:
|
||||
row: 34
|
||||
column: 32
|
||||
column: 33
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: NonPEP604Annotation
|
||||
|
||||
@@ -93,18 +93,9 @@ pub fn pairwise_over_zipped(checker: &mut Checker, func: &Expr, args: &[Expr]) {
|
||||
return;
|
||||
};
|
||||
|
||||
// Require exactly two positional arguments.
|
||||
if args.len() != 2 {
|
||||
if !(args.len() > 1 && id == "zip" && checker.ctx.is_builtin(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Require the function to be the builtin `zip`.
|
||||
if id != "zip" {
|
||||
return;
|
||||
}
|
||||
if !checker.ctx.is_builtin(id) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Allow the first argument to be a `Name` or `Subscript`.
|
||||
let Some(first_arg_info) = ({
|
||||
|
||||
@@ -2,6 +2,45 @@
|
||||
source: crates/ruff/src/rules/ruff/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
name: PairwiseOverZipped
|
||||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: PairwiseOverZipped
|
||||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: PairwiseOverZipped
|
||||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 15
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: PairwiseOverZipped
|
||||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs"
|
||||
@@ -35,48 +74,9 @@ expression: diagnostics
|
||||
fixable: false
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
column: 5
|
||||
end_location:
|
||||
row: 18
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: PairwiseOverZipped
|
||||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 19
|
||||
column: 0
|
||||
end_location:
|
||||
row: 19
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: PairwiseOverZipped
|
||||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 20
|
||||
column: 0
|
||||
end_location:
|
||||
row: 20
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: PairwiseOverZipped
|
||||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 21
|
||||
column: 5
|
||||
end_location:
|
||||
row: 21
|
||||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
@@ -86,50 +86,11 @@ expression: diagnostics
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 22
|
||||
row: 19
|
||||
column: 5
|
||||
end_location:
|
||||
row: 22
|
||||
row: 19
|
||||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: PairwiseOverZipped
|
||||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 23
|
||||
column: 0
|
||||
end_location:
|
||||
row: 23
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: PairwiseOverZipped
|
||||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 24
|
||||
column: 0
|
||||
end_location:
|
||||
row: 24
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
name: PairwiseOverZipped
|
||||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs"
|
||||
suggestion: ~
|
||||
fixable: false
|
||||
location:
|
||||
row: 25
|
||||
column: 0
|
||||
end_location:
|
||||
row: 25
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.258"
|
||||
version = "0.0.257"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -8,13 +8,12 @@ use smallvec::smallvec;
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
use ruff_python_stdlib::typing::TYPING_EXTENSIONS;
|
||||
|
||||
use crate::helpers::{collect_call_path, from_relative_import};
|
||||
use crate::helpers::{collect_call_path, from_relative_import, Exceptions};
|
||||
use crate::scope::{
|
||||
Binding, BindingId, BindingKind, Bindings, Exceptions, ExecutionContext, Scope, ScopeId,
|
||||
ScopeKind, ScopeStack, Scopes,
|
||||
Binding, BindingId, BindingKind, Bindings, ExecutionContext, Scope, ScopeId, ScopeKind,
|
||||
ScopeStack, Scopes,
|
||||
};
|
||||
use crate::types::{CallPath, RefEquality};
|
||||
use crate::typing::AnnotationKind;
|
||||
use crate::visibility::{module_visibility, Modifier, VisibleScope};
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
@@ -42,7 +41,7 @@ pub struct Context<'a> {
|
||||
pub visible_scope: VisibleScope,
|
||||
pub in_annotation: bool,
|
||||
pub in_type_definition: bool,
|
||||
pub in_deferred_string_type_definition: Option<AnnotationKind>,
|
||||
pub in_deferred_string_type_definition: bool,
|
||||
pub in_deferred_type_definition: bool,
|
||||
pub in_exception_handler: bool,
|
||||
pub in_literal: bool,
|
||||
@@ -80,7 +79,7 @@ impl<'a> Context<'a> {
|
||||
},
|
||||
in_annotation: false,
|
||||
in_type_definition: false,
|
||||
in_deferred_string_type_definition: None,
|
||||
in_deferred_string_type_definition: false,
|
||||
in_deferred_type_definition: false,
|
||||
in_exception_handler: false,
|
||||
in_literal: false,
|
||||
@@ -309,24 +308,14 @@ impl<'a> Context<'a> {
|
||||
self.in_exception_handler
|
||||
}
|
||||
|
||||
/// Return the [`ExecutionContext`] of the current scope.
|
||||
pub const fn execution_context(&self) -> ExecutionContext {
|
||||
if self.in_type_checking_block
|
||||
|| self.in_annotation
|
||||
|| self.in_deferred_string_type_definition.is_some()
|
||||
|| self.in_deferred_string_type_definition
|
||||
{
|
||||
ExecutionContext::Typing
|
||||
} else {
|
||||
ExecutionContext::Runtime
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the union of all handled exceptions as an [`Exceptions`] bitflag.
|
||||
pub fn exceptions(&self) -> Exceptions {
|
||||
let mut exceptions = Exceptions::empty();
|
||||
for exception in &self.handled_exceptions {
|
||||
exceptions.insert(*exception);
|
||||
}
|
||||
exceptions
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::path::Path;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -576,6 +577,13 @@ pub fn has_non_none_keyword(keywords: &[Keyword], keyword: &str) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct Exceptions: u32 {
|
||||
const NAME_ERROR = 0b0000_0001;
|
||||
const MODULE_NOT_FOUND_ERROR = 0b0000_0010;
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the names of all handled exceptions.
|
||||
pub fn extract_handled_exceptions(handlers: &[Excepthandler]) -> Vec<&Expr> {
|
||||
let mut handled_exceptions = Vec::new();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::types::{Range, RefEquality};
|
||||
use bitflags::bitflags;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_parser::ast::{Arguments, Expr, Keyword, Stmt};
|
||||
use std::num::TryFromIntError;
|
||||
@@ -223,14 +222,6 @@ impl Default for ScopeStack {
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct Exceptions: u32 {
|
||||
const NAME_ERROR = 0b0000_0001;
|
||||
const MODULE_NOT_FOUND_ERROR = 0b0000_0010;
|
||||
const IMPORT_ERROR = 0b0000_0100;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Binding<'a> {
|
||||
pub kind: BindingKind<'a>,
|
||||
@@ -250,8 +241,6 @@ pub struct Binding<'a> {
|
||||
/// (e.g.) `__future__` imports, explicit re-exports, and other bindings
|
||||
/// that should be considered used even if they're never referenced.
|
||||
pub synthetic_usage: Option<(ScopeId, Range)>,
|
||||
/// The exceptions that were handled when the binding was defined.
|
||||
pub exceptions: Exceptions,
|
||||
}
|
||||
|
||||
impl<'a> Binding<'a> {
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use rustpython_parser as parser;
|
||||
use rustpython_parser::ast::{Expr, ExprKind, Location};
|
||||
use rustpython_parser::ast::{Expr, ExprKind};
|
||||
|
||||
use ruff_python_stdlib::typing::{PEP_585_BUILTINS_ELIGIBLE, PEP_593_SUBSCRIPTS, SUBSCRIPTS};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::relocate::relocate_expr;
|
||||
use crate::source_code::Locator;
|
||||
use crate::str;
|
||||
use crate::types::Range;
|
||||
|
||||
pub enum Callable {
|
||||
ForwardRef,
|
||||
@@ -72,48 +66,3 @@ pub fn is_pep585_builtin(expr: &Expr, context: &Context) -> bool {
|
||||
PEP_585_BUILTINS_ELIGIBLE.contains(&call_path.as_slice())
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(is_macro::Is)]
|
||||
pub enum AnnotationKind {
|
||||
/// The annotation is defined as part a simple string literal,
|
||||
/// e.g. `x: "List[int]" = []`. Annotations within simple literals
|
||||
/// can be accurately located. For example, we can underline specific
|
||||
/// expressions within the annotation and apply automatic fixes, which is
|
||||
/// not possible for complex string literals.
|
||||
Simple,
|
||||
/// The annotation is defined as part of a complex string literal, such as
|
||||
/// a literal containing an implicit concatenation or escaped characters,
|
||||
/// e.g. `x: "List" "[int]" = []`. These are comparatively rare, but valid.
|
||||
Complex,
|
||||
}
|
||||
|
||||
/// Parse a type annotation from a string.
|
||||
pub fn parse_type_annotation(
|
||||
value: &str,
|
||||
range: Range,
|
||||
locator: &Locator,
|
||||
) -> Result<(Expr, AnnotationKind)> {
|
||||
let expression = locator.slice(range);
|
||||
let body = str::raw_contents(expression);
|
||||
if body == value {
|
||||
// The annotation is considered "simple" if and only if the raw representation (e.g.,
|
||||
// `List[int]` within "List[int]") exactly matches the parsed representation. This
|
||||
// isn't the case, e.g., for implicit concatenations, or for annotations that contain
|
||||
// escaped quotes.
|
||||
let leading_quote = str::leading_quote(expression).unwrap();
|
||||
let expr = parser::parse_expression_located(
|
||||
body,
|
||||
"<filename>",
|
||||
Location::new(
|
||||
range.location.row(),
|
||||
range.location.column() + leading_quote.len(),
|
||||
),
|
||||
)?;
|
||||
Ok((expr, AnnotationKind::Simple))
|
||||
} else {
|
||||
// Otherwise, consider this a "complex" annotation.
|
||||
let mut expr = parser::parse_expression(value, "<filename>")?;
|
||||
relocate_expr(&mut expr, range);
|
||||
Ok((expr, AnnotationKind::Complex))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ natively, including:
|
||||
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
|
||||
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
|
||||
- [flake8-django](https://pypi.org/project/flake8-django/)
|
||||
- [flake8-django](https://pypi.org/project/flake8-django/) ([#2817](https://github.com/charliermarsh/ruff/issues/2817))
|
||||
- [flake8-docstrings](https://pypi.org/project/flake8-docstrings/)
|
||||
- [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
|
||||
- [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
|
||||
@@ -133,7 +133,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
|
||||
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
|
||||
- [flake8-django](https://pypi.org/project/flake8-django/)
|
||||
- [flake8-django](https://pypi.org/project/flake8-django/) ([#2817](https://github.com/charliermarsh/ruff/issues/2817))
|
||||
- [flake8-docstrings](https://pypi.org/project/flake8-docstrings/)
|
||||
- [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
|
||||
- [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
|
||||
|
||||
@@ -242,7 +242,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.258'
|
||||
rev: 'v0.0.257'
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
@@ -20,7 +20,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.258'
|
||||
rev: 'v0.0.257'
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -30,7 +30,7 @@ Or, to enable autofix:
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.258'
|
||||
rev: 'v0.0.257'
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.0.258"
|
||||
version = "0.0.257"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
authors = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
|
||||
maintainers = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
|
||||
|
||||
46
ruff.schema.json
generated
46
ruff.schema.json
generated
@@ -630,6 +630,17 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"severity": {
|
||||
"description": "The minimum severity to catch. Choose from `low`, `medium`, `high`,",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Severity"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -1575,7 +1586,6 @@
|
||||
"DJ007",
|
||||
"DJ008",
|
||||
"DJ01",
|
||||
"DJ012",
|
||||
"DJ013",
|
||||
"DTZ",
|
||||
"DTZ0",
|
||||
@@ -2032,6 +2042,9 @@
|
||||
"RUF10",
|
||||
"RUF100",
|
||||
"S",
|
||||
"S0",
|
||||
"S00",
|
||||
"S001",
|
||||
"S1",
|
||||
"S10",
|
||||
"S101",
|
||||
@@ -2047,30 +2060,7 @@
|
||||
"S112",
|
||||
"S113",
|
||||
"S3",
|
||||
"S30",
|
||||
"S301",
|
||||
"S302",
|
||||
"S303",
|
||||
"S304",
|
||||
"S305",
|
||||
"S306",
|
||||
"S307",
|
||||
"S308",
|
||||
"S31",
|
||||
"S310",
|
||||
"S311",
|
||||
"S312",
|
||||
"S313",
|
||||
"S314",
|
||||
"S315",
|
||||
"S316",
|
||||
"S317",
|
||||
"S318",
|
||||
"S319",
|
||||
"S32",
|
||||
"S320",
|
||||
"S321",
|
||||
"S323",
|
||||
"S324",
|
||||
"S5",
|
||||
"S50",
|
||||
@@ -2253,6 +2243,14 @@
|
||||
"azure"
|
||||
]
|
||||
},
|
||||
"Severity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Low",
|
||||
"Medium",
|
||||
"High"
|
||||
]
|
||||
},
|
||||
"Strictness": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user