Compare commits

...

7 Commits

Author SHA1 Message Date
Charlie Marsh
bf718fdf26 Bump Ruff version to 0.0.243 2023-02-06 21:22:54 -05:00
Steve Dignam
3b3466f6da Add flake8-pie single_starts_ends_with (#2616) 2023-02-06 21:22:32 -05:00
Charlie Marsh
f981f491aa Support ignore-names for all relevant pep8-naming rules (#2617) 2023-02-06 21:14:55 -05:00
Charlie Marsh
95fef43c4d Add some additional tests for relative imports 2023-02-06 21:13:23 -05:00
Charlie Marsh
097c679cf3 Support relative paths for typing-modules (#2615) 2023-02-06 19:51:37 -05:00
Charlie Marsh
3bca987665 Avoid removing quotes from runtime annotations (#2614) 2023-02-06 18:15:19 -05:00
Ville Skyttä
60ee1d2c17 fix(pep8-naming): typing.NamedTuple and typing.TypedDict treatment (#2611) 2023-02-06 17:11:37 -05:00
36 changed files with 1225 additions and 159 deletions

12
Cargo.lock generated
View File

@@ -747,7 +747,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.242"
version = "0.0.243"
dependencies = [
"anyhow",
"clap 4.1.4",
@@ -1896,7 +1896,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.242"
version = "0.0.243"
dependencies = [
"anyhow",
"bisection",
@@ -1953,7 +1953,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.242"
version = "0.0.243"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1989,7 +1989,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.242"
version = "0.0.243"
dependencies = [
"anyhow",
"clap 4.1.4",
@@ -2010,7 +2010,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.242"
version = "0.0.243"
dependencies = [
"once_cell",
"proc-macro2",
@@ -2021,7 +2021,7 @@ dependencies = [
[[package]]
name = "ruff_python"
version = "0.0.242"
version = "0.0.243"
dependencies = [
"once_cell",
"regex",

View File

@@ -230,7 +230,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.242'
rev: 'v0.0.243'
hooks:
- id: ruff
```
@@ -1113,6 +1113,7 @@ For more, see [flake8-pie](https://pypi.org/project/flake8-pie/) on PyPI.
| PIE800 | no-unnecessary-spread | Unnecessary spread `**` | |
| PIE804 | no-unnecessary-dict-kwargs | Unnecessary `dict` kwargs | |
| PIE807 | prefer-list-builtin | Prefer `list` over useless lambda | 🛠 |
| PIE810 | single-starts-ends-with | Call `{attr}` once with a `tuple` | |
### flake8-print (T20)

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.242"
version = "0.0.243"
edition = "2021"
[dependencies]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.242"
version = "0.0.243"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -39,8 +39,8 @@ num-traits = "0.2.15"
once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { version = "1.6.0" }
ruff_macros = { version = "0.0.242", path = "../ruff_macros" }
ruff_python = { version = "0.0.242", path = "../ruff_python" }
ruff_macros = { version = "0.0.243", path = "../ruff_macros" }
ruff_python = { version = "0.0.243", path = "../ruff_python" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }

View File

@@ -0,0 +1,17 @@
# error
obj.startswith("foo") or obj.startswith("bar")
# error
obj.endswith("foo") or obj.endswith("bar")
# error
obj.startswith(foo) or obj.startswith(bar)
# error
obj.startswith(foo) or obj.startswith("foo")
# ok
obj.startswith(("foo", "bar"))
# ok
obj.endswith(("foo", "bar"))
# ok
obj.startswith("foo") or obj.endswith("bar")
# ok
obj.startswith("foo") or abc.startswith("bar")

View File

@@ -5,3 +5,7 @@ def func(_, a, A):
class Class:
def method(self, _, a, A):
return _, a, A
def func(_, setUp):
return _, setUp

View File

@@ -2,6 +2,7 @@ import collections
from collections import namedtuple
from typing import TypeVar
from typing import NewType
from typing import NamedTuple, TypedDict
GLOBAL: str = "foo"
@@ -20,6 +21,10 @@ def assign():
T = TypeVar("T")
UserId = NewType("UserId", int)
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
Point2D = TypedDict('Point2D', {'in': int, 'x-y': int})
def aug_assign(rank, world_size):
global CURRENT_PORT

View File

@@ -1,5 +1,6 @@
import collections
from collections import namedtuple
from typing import NamedTuple, TypedDict
class C:
@@ -10,3 +11,5 @@ class C:
mixed_Case = 0
myObj1 = collections.namedtuple("MyObj1", ["a", "b"])
myObj2 = namedtuple("MyObj2", ["a", "b"])
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
Point2D = TypedDict('Point2D', {'in': int, 'x-y': int})

View File

@@ -1,5 +1,6 @@
import collections
from collections import namedtuple
from typing import NamedTuple, TypedDict
lower = 0
CONSTANT = 0
@@ -8,3 +9,5 @@ _mixedCase = 0
mixed_Case = 0
myObj1 = collections.namedtuple("MyObj1", ["a", "b"])
myObj2 = namedtuple("MyObj2", ["a", "b"])
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
Point2D = TypedDict('Point2D', {'in': int, 'x-y': int})

View File

@@ -0,0 +1,26 @@
def f():
from typing import Literal
# OK
x: Literal["foo"]
def f():
from .typical import Literal
# OK
x: Literal["foo"]
def f():
from . import typical
# OK
x: typical.Literal["foo"]
def f():
from .atypical import Literal
# F821
x: Literal["foo"]

View File

@@ -0,0 +1,33 @@
def f():
from typing import Literal
# OK
x: Literal["foo"]
def f():
from ..typical import Literal
# OK
x: Literal["foo"]
def f():
from .. import typical
# OK
x: typical.Literal["foo"]
def f():
from .typical import Literal
# F821
x: Literal["foo"]
def f():
from .atypical import Literal
# F821
x: Literal["foo"]

View File

@@ -66,12 +66,6 @@ x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
x: NamedTuple(typename="X", fields=[("foo", "int")])
x = TypeVar("x", "str", "int")
x = cast("str", x)
X = List["MyClass"]
X: MyCallable("X")
@@ -106,3 +100,9 @@ def foo(*, inplace):
x: Annotated[1:2] = ...
x = TypeVar("x", "str", "int")
x = cast("str", x)
X = List["MyClass"]

View File

@@ -1,3 +1,5 @@
use std::path::Path;
use itertools::Itertools;
use log::error;
use once_cell::sync::Lazy;
@@ -10,7 +12,7 @@ use rustpython_ast::{
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use rustpython_parser::token::StringKind;
use smallvec::smallvec;
use smallvec::{smallvec, SmallVec};
use crate::ast::types::{Binding, BindingKind, CallPath, Range};
use crate::ast::visitor;
@@ -657,6 +659,38 @@ pub fn to_call_path(target: &str) -> CallPath {
}
}
/// Create a module path from a (package, path) pair.
///
/// For example, if the package is `foo/bar` and the path is `foo/bar/baz.py`, the call path is
/// `["baz"]`.
pub fn to_module_path(package: &Path, path: &Path) -> Option<Vec<String>> {
path.strip_prefix(package.parent()?)
.ok()?
.iter()
.map(Path::new)
.map(std::path::Path::file_stem)
.map(|path| path.and_then(|path| path.to_os_string().into_string().ok()))
.collect::<Option<Vec<String>>>()
}
/// Create a call path from a relative import.
pub fn from_relative_import<'a>(module: &'a [String], name: &'a str) -> CallPath<'a> {
let mut call_path: CallPath = SmallVec::with_capacity(module.len() + 1);
// Start with the module path.
call_path.extend(module.iter().map(String::as_str));
// Remove segments based on the number of dots.
for _ in 0..name.chars().take_while(|c| *c == '.').count() {
call_path.pop();
}
// Add the remaining segments.
call_path.extend(name.trim_start_matches('.').split('.'));
call_path
}
/// A [`Visitor`] that collects all return statements in a function or method.
#[derive(Default)]
pub struct ReturnStatementVisitor<'a> {

View File

@@ -19,7 +19,9 @@ use smallvec::smallvec;
use ruff_python::builtins::{BUILTINS, MAGIC_GLOBALS};
use ruff_python::typing::TYPING_EXTENSIONS;
use crate::ast::helpers::{binding_range, collect_call_path, extract_handler_names};
use crate::ast::helpers::{
binding_range, collect_call_path, extract_handler_names, from_relative_import, to_module_path,
};
use crate::ast::operations::{extract_all_names, AllNamesFlags};
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
@@ -55,6 +57,7 @@ type DeferralContext<'a> = (Vec<usize>, Vec<RefEquality<'a, Stmt>>);
pub struct Checker<'a> {
// Input data.
pub(crate) path: &'a Path,
module_path: Option<Vec<String>>,
package: Option<&'a Path>,
autofix: flags::Autofix,
noqa: flags::Noqa,
@@ -119,6 +122,7 @@ impl<'a> Checker<'a> {
noqa: flags::Noqa,
path: &'a Path,
package: Option<&'a Path>,
module_path: Option<Vec<String>>,
locator: &'a Locator,
style: &'a Stylist,
indexer: &'a Indexer,
@@ -130,6 +134,7 @@ impl<'a> Checker<'a> {
noqa,
path,
package,
module_path,
locator,
stylist: style,
indexer,
@@ -234,32 +239,36 @@ impl<'a> Checker<'a> {
if let Some(head) = call_path.first() {
if let Some(binding) = self.find_binding(head) {
match &binding.kind {
BindingKind::Importation(.., name) => {
// Ignore relative imports.
if name.starts_with('.') {
return None;
}
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
return Some(source_path);
}
BindingKind::SubmoduleImportation(name, ..) => {
// Ignore relative imports.
if name.starts_with('.') {
return None;
}
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
return Some(source_path);
BindingKind::Importation(.., name)
| BindingKind::SubmoduleImportation(name, ..) => {
return if name.starts_with('.') {
if let Some(module) = &self.module_path {
let mut source_path = from_relative_import(module, name);
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
} else {
None
}
} else {
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
};
}
BindingKind::FromImportation(.., name) => {
// Ignore relative imports.
if name.starts_with('.') {
return None;
}
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
return Some(source_path);
return if name.starts_with('.') {
if let Some(module) = &self.module_path {
let mut source_path = from_relative_import(module, name);
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
} else {
None
}
} else {
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
};
}
BindingKind::Builtin => {
let mut source_path: CallPath = smallvec![];
@@ -3178,6 +3187,9 @@ where
{
pylint::rules::merge_isinstance(self, expr, op, values);
}
if self.settings.rules.enabled(&Rule::SingleStartsEndsWith) {
flake8_pie::rules::single_starts_ends_with(self, values, op);
}
if self.settings.rules.enabled(&Rule::DuplicateIsinstanceCall) {
flake8_simplify::rules::duplicate_isinstance_call(self, expr);
}
@@ -3702,8 +3714,11 @@ where
}
if self.settings.rules.enabled(&Rule::InvalidArgumentName) {
if let Some(diagnostic) = pep8_naming::rules::invalid_argument_name(&arg.node.arg, arg)
{
if let Some(diagnostic) = pep8_naming::rules::invalid_argument_name(
&arg.node.arg,
arg,
&self.settings.pep8_naming.ignore_names,
) {
self.diagnostics.push(diagnostic);
}
}
@@ -4391,7 +4406,7 @@ impl<'a> Checker<'a> {
self.deferred_string_type_definitions.pop()
{
if let Ok(mut expr) = parser::parse_expression(expression, "<filename>") {
if self.annotations_future_enabled {
if in_annotation && self.annotations_future_enabled {
if self.settings.rules.enabled(&Rule::QuotedAnnotation) {
pyupgrade::rules::quoted_annotation(self, expression, range);
}
@@ -5285,6 +5300,7 @@ pub fn check_ast(
noqa,
path,
package,
package.and_then(|package| to_module_path(package, path)),
locator,
stylist,
indexer,

View File

@@ -453,6 +453,7 @@ ruff_macros::define_rule_mapping!(
PIE800 => rules::flake8_pie::rules::NoUnnecessarySpread,
PIE804 => rules::flake8_pie::rules::NoUnnecessaryDictKwargs,
PIE807 => rules::flake8_pie::rules::PreferListBuiltin,
PIE810 => rules::flake8_pie::rules::SingleStartsEndsWith,
// flake8-commas
COM812 => rules::flake8_commas::rules::TrailingCommaMissing,
COM818 => rules::flake8_commas::rules::TrailingCommaOnBareTupleProhibited,

View File

@@ -14,6 +14,7 @@ mod tests {
#[test_case(Rule::DupeClassFieldDefinitions, Path::new("PIE794.py"); "PIE794")]
#[test_case(Rule::NoUnnecessaryDictKwargs, Path::new("PIE804.py"); "PIE804")]
#[test_case(Rule::SingleStartsEndsWith, Path::new("PIE810.py"); "PIE810")]
#[test_case(Rule::NoUnnecessaryPass, Path::new("PIE790.py"); "PIE790")]
#[test_case(Rule::NoUnnecessarySpread, Path::new("PIE800.py"); "PIE800")]
#[test_case(Rule::PreferListBuiltin, Path::new("PIE807.py"); "PIE807")]

View File

@@ -1,7 +1,9 @@
use log::error;
use ruff_macros::derive_message_formats;
use rustc_hash::FxHashSet;
use rustpython_ast::{Constant, Expr, ExprKind, Keyword, Stmt, StmtKind};
use rustpython_ast::{Boolop, Constant, Expr, ExprKind, Keyword, Stmt, StmtKind};
use ruff_macros::derive_message_formats;
use ruff_python::identifiers::is_identifier;
use crate::ast::comparable::ComparableExpr;
use crate::ast::helpers::{match_trailing_comment, unparse_expr};
@@ -13,7 +15,6 @@ use crate::fix::Fix;
use crate::message::Location;
use crate::registry::Diagnostic;
use crate::violation::{AlwaysAutofixableViolation, Violation};
use ruff_python::identifiers::is_identifier;
define_violation!(
pub struct NoUnnecessaryPass;
@@ -68,6 +69,19 @@ impl Violation for NoUnnecessarySpread {
}
}
define_violation!(
pub struct SingleStartsEndsWith {
pub attr: String,
}
);
impl Violation for SingleStartsEndsWith {
#[derive_message_formats]
fn message(&self) -> String {
let SingleStartsEndsWith { attr } = self;
format!("Call `{attr}` once with a `tuple`")
}
}
define_violation!(
pub struct NoUnnecessaryDictKwargs;
);
@@ -302,6 +316,44 @@ pub fn no_unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs: &[
}
}
/// PIE810
pub fn single_starts_ends_with(checker: &mut Checker, values: &[Expr], node: &Boolop) {
if *node != Boolop::Or {
return;
}
// Given `foo.startswith`, insert ("foo", "startswith") into the set.
let mut seen = FxHashSet::default();
for expr in values {
if let ExprKind::Call {
func,
args,
keywords,
..
} = &expr.node
{
if !(args.len() == 1 && keywords.is_empty()) {
continue;
}
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if attr != "startswith" && attr != "endswith" {
continue;
}
if let ExprKind::Name { id, .. } = &value.node {
if !seen.insert((id, attr)) {
checker.diagnostics.push(Diagnostic::new(
SingleStartsEndsWith {
attr: attr.to_string(),
},
Range::from_located(value),
));
}
}
}
}
}
}
/// PIE807
pub fn prefer_list_builtin(checker: &mut Checker, expr: &Expr) {
let ExprKind::Lambda { args, body } = &expr.node else {

View File

@@ -0,0 +1,49 @@
---
source: crates/ruff/src/rules/flake8_pie/mod.rs
expression: diagnostics
---
- kind:
SingleStartsEndsWith:
attr: startswith
location:
row: 2
column: 25
end_location:
row: 2
column: 28
fix: ~
parent: ~
- kind:
SingleStartsEndsWith:
attr: endswith
location:
row: 4
column: 23
end_location:
row: 4
column: 26
fix: ~
parent: ~
- kind:
SingleStartsEndsWith:
attr: startswith
location:
row: 6
column: 23
end_location:
row: 6
column: 26
fix: ~
parent: ~
- kind:
SingleStartsEndsWith:
attr: startswith
location:
row: 8
column: 23
end_location:
row: 8
column: 26
fix: ~
parent: ~

View File

@@ -28,6 +28,16 @@ pub fn is_namedtuple_assignment(checker: &Checker, stmt: &Stmt) -> bool {
};
checker.resolve_call_path(value).map_or(false, |call_path| {
call_path.as_slice() == ["collections", "namedtuple"]
|| call_path.as_slice() == ["typing", "NamedTuple"]
})
}
pub fn is_typeddict_assignment(checker: &Checker, stmt: &Stmt) -> bool {
let StmtKind::Assign { value, .. } = &stmt.node else {
return false;
};
checker.resolve_call_path(value).map_or(false, |call_path| {
call_path.as_slice() == ["typing", "TypedDict"]
})
}

View File

@@ -224,7 +224,10 @@ pub fn invalid_function_name(
ignore_names: &[String],
locator: &Locator,
) -> Option<Diagnostic> {
if name.to_lowercase() != name && !ignore_names.iter().any(|ignore_name| ignore_name == name) {
if ignore_names.iter().any(|ignore_name| ignore_name == name) {
return None;
}
if name.to_lowercase() != name {
return Some(Diagnostic::new(
InvalidFunctionName {
name: name.to_string(),
@@ -236,7 +239,10 @@ pub fn invalid_function_name(
}
/// N803
pub fn invalid_argument_name(name: &str, arg: &Arg) -> Option<Diagnostic> {
pub fn invalid_argument_name(name: &str, arg: &Arg, ignore_names: &[String]) -> Option<Diagnostic> {
if ignore_names.iter().any(|ignore_name| ignore_name == name) {
return None;
}
if name.to_lowercase() != name {
return Some(Diagnostic::new(
InvalidArgumentName {
@@ -269,15 +275,17 @@ pub fn invalid_first_argument_name_for_class_method(
) {
return None;
}
if let Some(arg) = args.posonlyargs.first() {
if arg.node.arg != "cls" {
return Some(Diagnostic::new(
InvalidFirstArgumentNameForClassMethod,
Range::from_located(arg),
));
}
} else if let Some(arg) = args.args.first() {
if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) {
if arg.node.arg != "cls" {
if checker
.settings
.pep8_naming
.ignore_names
.iter()
.any(|ignore_name| ignore_name == name)
{
return None;
}
return Some(Diagnostic::new(
InvalidFirstArgumentNameForClassMethod,
Range::from_located(arg),
@@ -312,6 +320,15 @@ pub fn invalid_first_argument_name_for_method(
if arg.node.arg == "self" {
return None;
}
if checker
.settings
.pep8_naming
.ignore_names
.iter()
.any(|ignore_name| ignore_name == name)
{
return None;
}
Some(Diagnostic::new(
InvalidFirstArgumentNameForMethod,
Range::from_located(arg),
@@ -325,8 +342,19 @@ pub fn non_lowercase_variable_in_function(
stmt: &Stmt,
name: &str,
) {
if checker
.settings
.pep8_naming
.ignore_names
.iter()
.any(|ignore_name| ignore_name == name)
{
return;
}
if name.to_lowercase() != name
&& !helpers::is_namedtuple_assignment(checker, stmt)
&& !helpers::is_typeddict_assignment(checker, stmt)
&& !helpers::is_type_var_assignment(checker, stmt)
{
checker.diagnostics.push(Diagnostic::new(
@@ -449,6 +477,15 @@ pub fn mixed_case_variable_in_class_scope(
stmt: &Stmt,
name: &str,
) {
if checker
.settings
.pep8_naming
.ignore_names
.iter()
.any(|ignore_name| ignore_name == name)
{
return;
}
if helpers::is_mixed_case(name) && !helpers::is_namedtuple_assignment(checker, stmt) {
checker.diagnostics.push(Diagnostic::new(
MixedCaseVariableInClassScope {
@@ -466,6 +503,15 @@ pub fn mixed_case_variable_in_global_scope(
stmt: &Stmt,
name: &str,
) {
if checker
.settings
.pep8_naming
.ignore_names
.iter()
.any(|ignore_name| ignore_name == name)
{
return;
}
if helpers::is_mixed_case(name) && !helpers::is_namedtuple_assignment(checker, stmt) {
checker.diagnostics.push(Diagnostic::new(
MixedCaseVariableInGlobalScope {

View File

@@ -1,15 +1,15 @@
---
source: src/rules/pep8_naming/mod.rs
source: crates/ruff/src/rules/pep8_naming/mod.rs
expression: diagnostics
---
- kind:
NonLowercaseVariableInFunction:
name: Camel
location:
row: 13
row: 14
column: 4
end_location:
row: 13
row: 14
column: 9
fix: ~
parent: ~
@@ -17,10 +17,10 @@ expression: diagnostics
NonLowercaseVariableInFunction:
name: CONSTANT
location:
row: 14
row: 15
column: 4
end_location:
row: 14
row: 15
column: 12
fix: ~
parent: ~

View File

@@ -1,15 +1,15 @@
---
source: src/rules/pep8_naming/mod.rs
source: crates/ruff/src/rules/pep8_naming/mod.rs
expression: diagnostics
---
- kind:
MixedCaseVariableInClassScope:
name: mixedCase
location:
row: 8
row: 9
column: 4
end_location:
row: 8
row: 9
column: 13
fix: ~
parent: ~
@@ -17,10 +17,10 @@ expression: diagnostics
MixedCaseVariableInClassScope:
name: _mixedCase
location:
row: 9
row: 10
column: 4
end_location:
row: 9
row: 10
column: 14
fix: ~
parent: ~
@@ -28,10 +28,10 @@ expression: diagnostics
MixedCaseVariableInClassScope:
name: mixed_Case
location:
row: 10
row: 11
column: 4
end_location:
row: 10
row: 11
column: 14
fix: ~
parent: ~

View File

@@ -1,15 +1,15 @@
---
source: src/rules/pep8_naming/mod.rs
source: crates/ruff/src/rules/pep8_naming/mod.rs
expression: diagnostics
---
- kind:
MixedCaseVariableInGlobalScope:
name: mixedCase
location:
row: 6
row: 7
column: 0
end_location:
row: 6
row: 7
column: 9
fix: ~
parent: ~
@@ -17,10 +17,10 @@ expression: diagnostics
MixedCaseVariableInGlobalScope:
name: _mixedCase
location:
row: 7
row: 8
column: 0
end_location:
row: 7
row: 8
column: 10
fix: ~
parent: ~
@@ -28,10 +28,10 @@ expression: diagnostics
MixedCaseVariableInGlobalScope:
name: mixed_Case
location:
row: 8
row: 9
column: 0
end_location:
row: 8
row: 9
column: 10
fix: ~
parent: ~

View File

@@ -207,6 +207,32 @@ mod tests {
Ok(())
}
#[test]
fn relative_typing_module() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/project/foo/bar.py"),
&settings::Settings {
typing_modules: vec!["foo.typical".to_string()],
..settings::Settings::for_rules(vec![Rule::UndefinedName])
},
)?;
assert_yaml_snapshot!(diagnostics);
Ok(())
}
#[test]
fn nested_relative_typing_module() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/project/foo/bop/baz.py"),
&settings::Settings {
typing_modules: vec!["foo.typical".to_string()],
..settings::Settings::for_rules(vec![Rule::UndefinedName])
},
)?;
assert_yaml_snapshot!(diagnostics);
Ok(())
}
/// A re-implementation of the Pyflakes test runner.
/// Note that all tests marked with `#[ignore]` should be considered TODOs.
fn flakes(contents: &str, expected: &[Rule]) {

View File

@@ -0,0 +1,27 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
expression: diagnostics
---
- kind:
UndefinedName:
name: foo
location:
row: 26
column: 15
end_location:
row: 26
column: 20
fix: ~
parent: ~
- kind:
UndefinedName:
name: foo
location:
row: 33
column: 15
end_location:
row: 33
column: 20
fix: ~
parent: ~

View File

@@ -0,0 +1,16 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
expression: diagnostics
---
- kind:
UndefinedName:
name: foo
location:
row: 26
column: 15
end_location:
row: 26
column: 20
fix: ~
parent: ~

View File

@@ -1,5 +1,5 @@
---
source: src/rules/pyupgrade/mod.rs
source: crates/ruff/src/rules/pyupgrade/mod.rs
expression: diagnostics
---
- kind:
@@ -510,90 +510,18 @@ expression: diagnostics
QuotedAnnotation: ~
location:
row: 69
column: 17
end_location:
row: 69
column: 22
fix:
content:
- str
location:
row: 69
column: 17
end_location:
row: 69
column: 22
parent: ~
- kind:
QuotedAnnotation: ~
location:
row: 69
column: 24
end_location:
row: 69
column: 29
fix:
content:
- int
location:
row: 69
column: 24
end_location:
row: 69
column: 29
parent: ~
- kind:
QuotedAnnotation: ~
location:
row: 71
column: 9
end_location:
row: 71
column: 14
fix:
content:
- str
location:
row: 71
column: 9
end_location:
row: 71
column: 14
parent: ~
- kind:
QuotedAnnotation: ~
location:
row: 73
column: 9
end_location:
row: 73
column: 18
fix:
content:
- MyClass
location:
row: 73
column: 9
end_location:
row: 73
column: 18
parent: ~
- kind:
QuotedAnnotation: ~
location:
row: 75
column: 14
end_location:
row: 75
row: 69
column: 17
fix:
content:
- X
location:
row: 75
row: 69
column: 14
end_location:
row: 75
row: 69
column: 17
parent: ~

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.0.242"
version = "0.0.243"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.242"
version = "0.0.243"
edition = "2021"
[dependencies]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.242"
version = "0.0.243"
edition = "2021"
[lib]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_python"
version = "0.0.242"
version = "0.0.243"
edition = "2021"
[lib]

View File

@@ -7,7 +7,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.242"
version = "0.0.243"
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" }]

View File

@@ -1650,6 +1650,8 @@
"PIE800",
"PIE804",
"PIE807",
"PIE81",
"PIE810",
"PL",
"PLC",
"PLC0",

766
src/registry.rs Normal file
View File

@@ -0,0 +1,766 @@
//! Registry of [`Rule`] to [`DiagnosticKind`] mappings.
use ruff_macros::RuleNamespace;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, EnumIter};
use crate::ast::types::Range;
use crate::fix::Fix;
use crate::rule_selector::{prefix_to_selector, RuleSelector};
use crate::violation::Violation;
use crate::{rules, violations};
ruff_macros::define_rule_mapping!(
// pycodestyle errors
E101 => rules::pycodestyle::rules::MixedSpacesAndTabs,
E401 => violations::MultipleImportsOnOneLine,
E402 => violations::ModuleImportNotAtTopOfFile,
E501 => rules::pycodestyle::rules::LineTooLong,
E711 => rules::pycodestyle::rules::NoneComparison,
E712 => rules::pycodestyle::rules::TrueFalseComparison,
E713 => rules::pycodestyle::rules::NotInTest,
E714 => rules::pycodestyle::rules::NotIsTest,
E721 => rules::pycodestyle::rules::TypeComparison,
E722 => rules::pycodestyle::rules::DoNotUseBareExcept,
E731 => rules::pycodestyle::rules::DoNotAssignLambda,
E741 => rules::pycodestyle::rules::AmbiguousVariableName,
E742 => rules::pycodestyle::rules::AmbiguousClassName,
E743 => rules::pycodestyle::rules::AmbiguousFunctionName,
E902 => violations::IOError,
E999 => violations::SyntaxError,
// pycodestyle warnings
W292 => rules::pycodestyle::rules::NoNewLineAtEndOfFile,
W505 => rules::pycodestyle::rules::DocLineTooLong,
W605 => rules::pycodestyle::rules::InvalidEscapeSequence,
// pyflakes
F401 => violations::UnusedImport,
F402 => violations::ImportShadowedByLoopVar,
F403 => violations::ImportStarUsed,
F404 => violations::LateFutureImport,
F405 => violations::ImportStarUsage,
F406 => violations::ImportStarNotPermitted,
F407 => violations::FutureFeatureNotDefined,
F501 => violations::PercentFormatInvalidFormat,
F502 => violations::PercentFormatExpectedMapping,
F503 => violations::PercentFormatExpectedSequence,
F504 => violations::PercentFormatExtraNamedArguments,
F505 => violations::PercentFormatMissingArgument,
F506 => violations::PercentFormatMixedPositionalAndNamed,
F507 => violations::PercentFormatPositionalCountMismatch,
F508 => violations::PercentFormatStarRequiresSequence,
F509 => violations::PercentFormatUnsupportedFormatCharacter,
F521 => violations::StringDotFormatInvalidFormat,
F522 => violations::StringDotFormatExtraNamedArguments,
F523 => violations::StringDotFormatExtraPositionalArguments,
F524 => violations::StringDotFormatMissingArguments,
F525 => violations::StringDotFormatMixingAutomatic,
F541 => violations::FStringMissingPlaceholders,
F601 => violations::MultiValueRepeatedKeyLiteral,
F602 => violations::MultiValueRepeatedKeyVariable,
F621 => violations::ExpressionsInStarAssignment,
F622 => violations::TwoStarredExpressions,
F631 => violations::AssertTuple,
F632 => violations::IsLiteral,
F633 => violations::InvalidPrintSyntax,
F634 => violations::IfTuple,
F701 => violations::BreakOutsideLoop,
F702 => violations::ContinueOutsideLoop,
F704 => violations::YieldOutsideFunction,
F706 => violations::ReturnOutsideFunction,
F707 => violations::DefaultExceptNotLast,
F722 => violations::ForwardAnnotationSyntaxError,
F811 => violations::RedefinedWhileUnused,
F821 => violations::UndefinedName,
F822 => violations::UndefinedExport,
F823 => violations::UndefinedLocal,
F841 => violations::UnusedVariable,
F842 => violations::UnusedAnnotation,
F901 => violations::RaiseNotImplemented,
// pylint
PLC0414 => violations::UselessImportAlias,
PLC3002 => violations::UnnecessaryDirectLambdaCall,
PLE0117 => violations::NonlocalWithoutBinding,
PLE0118 => violations::UsedPriorGlobalDeclaration,
PLE1142 => violations::AwaitOutsideAsync,
PLR0206 => violations::PropertyWithParameters,
PLR0402 => violations::ConsiderUsingFromImport,
PLR0133 => violations::ConstantComparison,
PLR1701 => violations::ConsiderMergingIsinstance,
PLR1722 => violations::UseSysExit,
PLR2004 => violations::MagicValueComparison,
PLW0120 => violations::UselessElseOnLoop,
PLW0602 => violations::GlobalVariableNotAssigned,
// flake8-builtins
A001 => violations::BuiltinVariableShadowing,
A002 => violations::BuiltinArgumentShadowing,
A003 => violations::BuiltinAttributeShadowing,
// flake8-bugbear
B002 => violations::UnaryPrefixIncrement,
B003 => violations::AssignmentToOsEnviron,
B004 => violations::UnreliableCallableCheck,
B005 => violations::StripWithMultiCharacters,
B006 => violations::MutableArgumentDefault,
B007 => violations::UnusedLoopControlVariable,
B008 => violations::FunctionCallArgumentDefault,
B009 => violations::GetAttrWithConstant,
B010 => violations::SetAttrWithConstant,
B011 => violations::DoNotAssertFalse,
B012 => violations::JumpStatementInFinally,
B013 => violations::RedundantTupleInExceptionHandler,
B014 => violations::DuplicateHandlerException,
B015 => violations::UselessComparison,
B016 => violations::CannotRaiseLiteral,
B017 => violations::NoAssertRaisesException,
B018 => violations::UselessExpression,
B019 => violations::CachedInstanceMethod,
B020 => violations::LoopVariableOverridesIterator,
B021 => violations::FStringDocstring,
B022 => violations::UselessContextlibSuppress,
B023 => violations::FunctionUsesLoopVariable,
B024 => violations::AbstractBaseClassWithoutAbstractMethod,
B025 => violations::DuplicateTryBlockException,
B026 => violations::StarArgUnpackingAfterKeywordArg,
B027 => violations::EmptyMethodWithoutAbstractDecorator,
B904 => violations::RaiseWithoutFromInsideExcept,
B905 => violations::ZipWithoutExplicitStrict,
// flake8-blind-except
BLE001 => violations::BlindExcept,
// flake8-comprehensions
C400 => violations::UnnecessaryGeneratorList,
C401 => violations::UnnecessaryGeneratorSet,
C402 => violations::UnnecessaryGeneratorDict,
C403 => violations::UnnecessaryListComprehensionSet,
C404 => violations::UnnecessaryListComprehensionDict,
C405 => violations::UnnecessaryLiteralSet,
C406 => violations::UnnecessaryLiteralDict,
C408 => violations::UnnecessaryCollectionCall,
C409 => violations::UnnecessaryLiteralWithinTupleCall,
C410 => violations::UnnecessaryLiteralWithinListCall,
C411 => violations::UnnecessaryListCall,
C413 => violations::UnnecessaryCallAroundSorted,
C414 => violations::UnnecessaryDoubleCastOrProcess,
C415 => violations::UnnecessarySubscriptReversal,
C416 => violations::UnnecessaryComprehension,
C417 => violations::UnnecessaryMap,
// flake8-debugger
T100 => violations::Debugger,
// mccabe
C901 => violations::FunctionIsTooComplex,
// flake8-tidy-imports
TID251 => rules::flake8_tidy_imports::banned_api::BannedApi,
TID252 => rules::flake8_tidy_imports::relative_imports::RelativeImports,
// flake8-return
RET501 => violations::UnnecessaryReturnNone,
RET502 => violations::ImplicitReturnValue,
RET503 => violations::ImplicitReturn,
RET504 => violations::UnnecessaryAssign,
RET505 => violations::SuperfluousElseReturn,
RET506 => violations::SuperfluousElseRaise,
RET507 => violations::SuperfluousElseContinue,
RET508 => violations::SuperfluousElseBreak,
// flake8-implicit-str-concat
ISC001 => violations::SingleLineImplicitStringConcatenation,
ISC002 => violations::MultiLineImplicitStringConcatenation,
ISC003 => violations::ExplicitStringConcatenation,
// flake8-print
T201 => violations::PrintFound,
T203 => violations::PPrintFound,
// flake8-quotes
Q000 => violations::BadQuotesInlineString,
Q001 => violations::BadQuotesMultilineString,
Q002 => violations::BadQuotesDocstring,
Q003 => violations::AvoidQuoteEscape,
// flake8-annotations
ANN001 => violations::MissingTypeFunctionArgument,
ANN002 => violations::MissingTypeArgs,
ANN003 => violations::MissingTypeKwargs,
ANN101 => violations::MissingTypeSelf,
ANN102 => violations::MissingTypeCls,
ANN201 => violations::MissingReturnTypePublicFunction,
ANN202 => violations::MissingReturnTypePrivateFunction,
ANN204 => violations::MissingReturnTypeSpecialMethod,
ANN205 => violations::MissingReturnTypeStaticMethod,
ANN206 => violations::MissingReturnTypeClassMethod,
ANN401 => violations::DynamicallyTypedExpression,
// flake8-2020
YTT101 => violations::SysVersionSlice3Referenced,
YTT102 => violations::SysVersion2Referenced,
YTT103 => violations::SysVersionCmpStr3,
YTT201 => violations::SysVersionInfo0Eq3Referenced,
YTT202 => violations::SixPY3Referenced,
YTT203 => violations::SysVersionInfo1CmpInt,
YTT204 => violations::SysVersionInfoMinorCmpInt,
YTT301 => violations::SysVersion0Referenced,
YTT302 => violations::SysVersionCmpStr10,
YTT303 => violations::SysVersionSlice1Referenced,
// flake8-simplify
SIM115 => violations::OpenFileWithContextHandler,
SIM101 => violations::DuplicateIsinstanceCall,
SIM102 => violations::NestedIfStatements,
SIM103 => violations::ReturnBoolConditionDirectly,
SIM105 => violations::UseContextlibSuppress,
SIM107 => violations::ReturnInTryExceptFinally,
SIM108 => violations::UseTernaryOperator,
SIM109 => violations::CompareWithTuple,
SIM110 => violations::ConvertLoopToAny,
SIM111 => violations::ConvertLoopToAll,
SIM112 => violations::UseCapitalEnvironmentVariables,
SIM117 => violations::MultipleWithStatements,
SIM118 => violations::KeyInDict,
SIM201 => violations::NegateEqualOp,
SIM202 => violations::NegateNotEqualOp,
SIM208 => violations::DoubleNegation,
SIM210 => violations::IfExprWithTrueFalse,
SIM211 => violations::IfExprWithFalseTrue,
SIM212 => violations::IfExprWithTwistedArms,
SIM220 => violations::AAndNotA,
SIM221 => violations::AOrNotA,
SIM222 => violations::OrTrue,
SIM223 => violations::AndFalse,
SIM300 => violations::YodaConditions,
SIM401 => violations::DictGetWithDefault,
// pyupgrade
UP001 => violations::UselessMetaclassType,
UP003 => violations::TypeOfPrimitive,
UP004 => violations::UselessObjectInheritance,
UP005 => violations::DeprecatedUnittestAlias,
UP006 => violations::UsePEP585Annotation,
UP007 => violations::UsePEP604Annotation,
UP008 => violations::SuperCallWithParameters,
UP009 => violations::PEP3120UnnecessaryCodingComment,
UP010 => violations::UnnecessaryFutureImport,
UP011 => violations::LRUCacheWithoutParameters,
UP012 => violations::UnnecessaryEncodeUTF8,
UP013 => violations::ConvertTypedDictFunctionalToClass,
UP014 => violations::ConvertNamedTupleFunctionalToClass,
UP015 => violations::RedundantOpenModes,
UP016 => violations::RemoveSixCompat,
UP017 => violations::DatetimeTimezoneUTC,
UP018 => violations::NativeLiterals,
UP019 => violations::TypingTextStrAlias,
UP020 => violations::OpenAlias,
UP021 => violations::ReplaceUniversalNewlines,
UP022 => violations::ReplaceStdoutStderr,
UP023 => violations::RewriteCElementTree,
UP024 => violations::OSErrorAlias,
UP025 => violations::RewriteUnicodeLiteral,
UP026 => violations::RewriteMockImport,
UP027 => violations::RewriteListComprehension,
UP028 => violations::RewriteYieldFrom,
UP029 => violations::UnnecessaryBuiltinImport,
UP030 => violations::FormatLiterals,
UP031 => violations::PrintfStringFormatting,
UP032 => violations::FString,
UP033 => violations::FunctoolsCache,
UP034 => violations::ExtraneousParentheses,
// pydocstyle
D100 => violations::PublicModule,
D101 => violations::PublicClass,
D102 => violations::PublicMethod,
D103 => violations::PublicFunction,
D104 => violations::PublicPackage,
D105 => violations::MagicMethod,
D106 => violations::PublicNestedClass,
D107 => violations::PublicInit,
D200 => violations::FitsOnOneLine,
D201 => violations::NoBlankLineBeforeFunction,
D202 => violations::NoBlankLineAfterFunction,
D203 => violations::OneBlankLineBeforeClass,
D204 => violations::OneBlankLineAfterClass,
D205 => violations::BlankLineAfterSummary,
D206 => violations::IndentWithSpaces,
D207 => violations::NoUnderIndentation,
D208 => violations::NoOverIndentation,
D209 => violations::NewLineAfterLastParagraph,
D210 => violations::NoSurroundingWhitespace,
D211 => violations::NoBlankLineBeforeClass,
D212 => violations::MultiLineSummaryFirstLine,
D213 => violations::MultiLineSummarySecondLine,
D214 => violations::SectionNotOverIndented,
D215 => violations::SectionUnderlineNotOverIndented,
D300 => violations::UsesTripleQuotes,
D301 => violations::UsesRPrefixForBackslashedContent,
D400 => violations::EndsInPeriod,
D401 => crate::rules::pydocstyle::rules::non_imperative_mood::NonImperativeMood,
D402 => violations::NoSignature,
D403 => violations::FirstLineCapitalized,
D404 => violations::NoThisPrefix,
D405 => violations::CapitalizeSectionName,
D406 => violations::NewLineAfterSectionName,
D407 => violations::DashedUnderlineAfterSection,
D408 => violations::SectionUnderlineAfterName,
D409 => violations::SectionUnderlineMatchesSectionLength,
D410 => violations::BlankLineAfterSection,
D411 => violations::BlankLineBeforeSection,
D412 => violations::NoBlankLinesBetweenHeaderAndContent,
D413 => violations::BlankLineAfterLastSection,
D414 => violations::NonEmptySection,
D415 => violations::EndsInPunctuation,
D416 => violations::SectionNameEndsInColon,
D417 => violations::DocumentAllArguments,
D418 => violations::SkipDocstring,
D419 => violations::NonEmpty,
// pep8-naming
N801 => violations::InvalidClassName,
N802 => violations::InvalidFunctionName,
N803 => violations::InvalidArgumentName,
N804 => violations::InvalidFirstArgumentNameForClassMethod,
N805 => violations::InvalidFirstArgumentNameForMethod,
N806 => violations::NonLowercaseVariableInFunction,
N807 => violations::DunderFunctionName,
N811 => violations::ConstantImportedAsNonConstant,
N812 => violations::LowercaseImportedAsNonLowercase,
N813 => violations::CamelcaseImportedAsLowercase,
N814 => violations::CamelcaseImportedAsConstant,
N815 => violations::MixedCaseVariableInClassScope,
N816 => violations::MixedCaseVariableInGlobalScope,
N817 => violations::CamelcaseImportedAsAcronym,
N818 => violations::ErrorSuffixOnExceptionName,
// isort
I001 => rules::isort::rules::UnsortedImports,
I002 => rules::isort::rules::MissingRequiredImport,
// eradicate
ERA001 => rules::eradicate::rules::CommentedOutCode,
// flake8-bandit
S101 => violations::AssertUsed,
S102 => violations::ExecUsed,
S103 => violations::BadFilePermissions,
S104 => violations::HardcodedBindAllInterfaces,
S105 => violations::HardcodedPasswordString,
S106 => violations::HardcodedPasswordFuncArg,
S107 => violations::HardcodedPasswordDefault,
S108 => violations::HardcodedTempFile,
S113 => violations::RequestWithoutTimeout,
S324 => violations::HashlibInsecureHashFunction,
S501 => violations::RequestWithNoCertValidation,
S506 => violations::UnsafeYAMLLoad,
S508 => violations::SnmpInsecureVersion,
S509 => violations::SnmpWeakCryptography,
S612 => rules::flake8_bandit::rules::LoggingConfigInsecureListen,
S701 => violations::Jinja2AutoescapeFalse,
// flake8-boolean-trap
FBT001 => rules::flake8_boolean_trap::rules::BooleanPositionalArgInFunctionDefinition,
FBT002 => rules::flake8_boolean_trap::rules::BooleanDefaultValueInFunctionDefinition,
FBT003 => rules::flake8_boolean_trap::rules::BooleanPositionalValueInFunctionCall,
// flake8-unused-arguments
ARG001 => violations::UnusedFunctionArgument,
ARG002 => violations::UnusedMethodArgument,
ARG003 => violations::UnusedClassMethodArgument,
ARG004 => violations::UnusedStaticMethodArgument,
ARG005 => violations::UnusedLambdaArgument,
// flake8-import-conventions
ICN001 => rules::flake8_import_conventions::rules::ImportAliasIsNotConventional,
// flake8-datetimez
DTZ001 => violations::CallDatetimeWithoutTzinfo,
DTZ002 => violations::CallDatetimeToday,
DTZ003 => violations::CallDatetimeUtcnow,
DTZ004 => violations::CallDatetimeUtcfromtimestamp,
DTZ005 => violations::CallDatetimeNowWithoutTzinfo,
DTZ006 => violations::CallDatetimeFromtimestamp,
DTZ007 => violations::CallDatetimeStrptimeWithoutZone,
DTZ011 => violations::CallDateToday,
DTZ012 => violations::CallDateFromtimestamp,
// pygrep-hooks
PGH001 => violations::NoEval,
PGH002 => violations::DeprecatedLogWarn,
PGH003 => violations::BlanketTypeIgnore,
PGH004 => violations::BlanketNOQA,
// pandas-vet
PD002 => violations::UseOfInplaceArgument,
PD003 => violations::UseOfDotIsNull,
PD004 => violations::UseOfDotNotNull,
PD007 => violations::UseOfDotIx,
PD008 => violations::UseOfDotAt,
PD009 => violations::UseOfDotIat,
PD010 => violations::UseOfDotPivotOrUnstack,
PD011 => violations::UseOfDotValues,
PD012 => violations::UseOfDotReadTable,
PD013 => violations::UseOfDotStack,
PD015 => violations::UseOfPdMerge,
PD901 => violations::DfIsABadVariableName,
// flake8-errmsg
EM101 => violations::RawStringInException,
EM102 => violations::FStringInException,
EM103 => violations::DotFormatInException,
// flake8-pytest-style
PT001 => violations::IncorrectFixtureParenthesesStyle,
PT002 => violations::FixturePositionalArgs,
PT003 => violations::ExtraneousScopeFunction,
PT004 => violations::MissingFixtureNameUnderscore,
PT005 => violations::IncorrectFixtureNameUnderscore,
PT006 => violations::ParametrizeNamesWrongType,
PT007 => violations::ParametrizeValuesWrongType,
PT008 => violations::PatchWithLambda,
PT009 => violations::UnittestAssertion,
PT010 => violations::RaisesWithoutException,
PT011 => violations::RaisesTooBroad,
PT012 => violations::RaisesWithMultipleStatements,
PT013 => violations::IncorrectPytestImport,
PT015 => violations::AssertAlwaysFalse,
PT016 => violations::FailWithoutMessage,
PT017 => violations::AssertInExcept,
PT018 => violations::CompositeAssertion,
PT019 => violations::FixtureParamWithoutValue,
PT020 => violations::DeprecatedYieldFixture,
PT021 => violations::FixtureFinalizerCallback,
PT022 => violations::UselessYieldFixture,
PT023 => violations::IncorrectMarkParenthesesStyle,
PT024 => violations::UnnecessaryAsyncioMarkOnFixture,
PT025 => violations::ErroneousUseFixturesOnFixture,
PT026 => violations::UseFixturesWithoutParameters,
// flake8-pie
PIE790 => rules::flake8_pie::rules::NoUnnecessaryPass,
PIE794 => rules::flake8_pie::rules::DupeClassFieldDefinitions,
PIE796 => rules::flake8_pie::rules::PreferUniqueEnums,
PIE800 => rules::flake8_pie::rules::NoUnnecessarySpread,
PIE804 => rules::flake8_pie::rules::NoUnnecessaryDictKwargs,
PIE807 => rules::flake8_pie::rules::PreferListBuiltin,
PIE810 => rules::flake8_pie::rules::SingleStartsEndsWith,
// flake8-commas
COM812 => rules::flake8_commas::rules::TrailingCommaMissing,
COM818 => rules::flake8_commas::rules::TrailingCommaOnBareTupleProhibited,
COM819 => rules::flake8_commas::rules::TrailingCommaProhibited,
// flake8-no-pep420
INP001 => rules::flake8_no_pep420::rules::ImplicitNamespacePackage,
// flake8-executable
EXE001 => rules::flake8_executable::rules::ShebangNotExecutable,
EXE002 => rules::flake8_executable::rules::ShebangMissingExecutableFile,
EXE003 => rules::flake8_executable::rules::ShebangPython,
EXE004 => rules::flake8_executable::rules::ShebangWhitespace,
EXE005 => rules::flake8_executable::rules::ShebangNewline,
// flake8-type-checking
TCH001 => rules::flake8_type_checking::rules::TypingOnlyFirstPartyImport,
TCH002 => rules::flake8_type_checking::rules::TypingOnlyThirdPartyImport,
TCH003 => rules::flake8_type_checking::rules::TypingOnlyStandardLibraryImport,
TCH004 => rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock,
TCH005 => rules::flake8_type_checking::rules::EmptyTypeCheckingBlock,
// tryceratops
TRY002 => rules::tryceratops::rules::RaiseVanillaClass,
TRY003 => rules::tryceratops::rules::RaiseVanillaArgs,
TRY004 => rules::tryceratops::rules::PreferTypeError,
TRY200 => rules::tryceratops::rules::ReraiseNoCause,
TRY201 => rules::tryceratops::rules::VerboseRaise,
TRY300 => rules::tryceratops::rules::TryConsiderElse,
TRY301 => rules::tryceratops::rules::RaiseWithinTry,
TRY400 => rules::tryceratops::rules::ErrorInsteadOfException,
// flake8-use-pathlib
PTH100 => rules::flake8_use_pathlib::violations::PathlibAbspath,
PTH101 => rules::flake8_use_pathlib::violations::PathlibChmod,
PTH102 => rules::flake8_use_pathlib::violations::PathlibMkdir,
PTH103 => rules::flake8_use_pathlib::violations::PathlibMakedirs,
PTH104 => rules::flake8_use_pathlib::violations::PathlibRename,
PTH105 => rules::flake8_use_pathlib::violations::PathlibReplace,
PTH106 => rules::flake8_use_pathlib::violations::PathlibRmdir,
PTH107 => rules::flake8_use_pathlib::violations::PathlibRemove,
PTH108 => rules::flake8_use_pathlib::violations::PathlibUnlink,
PTH109 => rules::flake8_use_pathlib::violations::PathlibGetcwd,
PTH110 => rules::flake8_use_pathlib::violations::PathlibExists,
PTH111 => rules::flake8_use_pathlib::violations::PathlibExpanduser,
PTH112 => rules::flake8_use_pathlib::violations::PathlibIsDir,
PTH113 => rules::flake8_use_pathlib::violations::PathlibIsFile,
PTH114 => rules::flake8_use_pathlib::violations::PathlibIsLink,
PTH115 => rules::flake8_use_pathlib::violations::PathlibReadlink,
PTH116 => rules::flake8_use_pathlib::violations::PathlibStat,
PTH117 => rules::flake8_use_pathlib::violations::PathlibIsAbs,
PTH118 => rules::flake8_use_pathlib::violations::PathlibJoin,
PTH119 => rules::flake8_use_pathlib::violations::PathlibBasename,
PTH120 => rules::flake8_use_pathlib::violations::PathlibDirname,
PTH121 => rules::flake8_use_pathlib::violations::PathlibSamefile,
PTH122 => rules::flake8_use_pathlib::violations::PathlibSplitext,
PTH123 => rules::flake8_use_pathlib::violations::PathlibOpen,
PTH124 => rules::flake8_use_pathlib::violations::PathlibPyPath,
// flake8-logging-format
G001 => rules::flake8_logging_format::violations::LoggingStringFormat,
G002 => rules::flake8_logging_format::violations::LoggingPercentFormat,
G003 => rules::flake8_logging_format::violations::LoggingStringConcat,
G004 => rules::flake8_logging_format::violations::LoggingFString,
G010 => rules::flake8_logging_format::violations::LoggingWarn,
G101 => rules::flake8_logging_format::violations::LoggingExtraAttrClash,
G201 => rules::flake8_logging_format::violations::LoggingExcInfo,
G202 => rules::flake8_logging_format::violations::LoggingRedundantExcInfo,
// ruff
RUF001 => violations::AmbiguousUnicodeCharacterString,
RUF002 => violations::AmbiguousUnicodeCharacterDocstring,
RUF003 => violations::AmbiguousUnicodeCharacterComment,
RUF004 => violations::KeywordArgumentBeforeStarArgument,
RUF005 => violations::UnpackInsteadOfConcatenatingToCollectionLiteral,
RUF100 => violations::UnusedNOQA,
);
#[derive(EnumIter, Debug, PartialEq, Eq, RuleNamespace)]
pub enum Linter {
/// [Pyflakes](https://pypi.org/project/pyflakes/)
#[prefix = "F"]
Pyflakes,
/// [pycodestyle](https://pypi.org/project/pycodestyle/)
#[prefix = "E"]
#[prefix = "W"]
Pycodestyle,
/// [mccabe](https://pypi.org/project/mccabe/)
#[prefix = "C90"]
McCabe,
/// [isort](https://pypi.org/project/isort/)
#[prefix = "I"]
Isort,
/// [pep8-naming](https://pypi.org/project/pep8-naming/)
#[prefix = "N"]
PEP8Naming,
/// [pydocstyle](https://pypi.org/project/pydocstyle/)
#[prefix = "D"]
Pydocstyle,
/// [pyupgrade](https://pypi.org/project/pyupgrade/)
#[prefix = "UP"]
Pyupgrade,
/// [flake8-2020](https://pypi.org/project/flake8-2020/)
#[prefix = "YTT"]
Flake82020,
/// [flake8-annotations](https://pypi.org/project/flake8-annotations/)
#[prefix = "ANN"]
Flake8Annotations,
/// [flake8-bandit](https://pypi.org/project/flake8-bandit/)
#[prefix = "S"]
Flake8Bandit,
/// [flake8-blind-except](https://pypi.org/project/flake8-blind-except/)
#[prefix = "BLE"]
Flake8BlindExcept,
/// [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/)
#[prefix = "FBT"]
Flake8BooleanTrap,
/// [flake8-bugbear](https://pypi.org/project/flake8-bugbear/)
#[prefix = "B"]
Flake8Bugbear,
/// [flake8-builtins](https://pypi.org/project/flake8-builtins/)
#[prefix = "A"]
Flake8Builtins,
/// [flake8-commas](https://pypi.org/project/flake8-commas/)
#[prefix = "COM"]
Flake8Commas,
/// [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
#[prefix = "C4"]
Flake8Comprehensions,
/// [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
#[prefix = "DTZ"]
Flake8Datetimez,
/// [flake8-debugger](https://pypi.org/project/flake8-debugger/)
#[prefix = "T10"]
Flake8Debugger,
/// [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
#[prefix = "EM"]
Flake8ErrMsg,
/// [flake8-executable](https://pypi.org/project/flake8-executable/)
#[prefix = "EXE"]
Flake8Executable,
/// [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
#[prefix = "ISC"]
Flake8ImplicitStrConcat,
/// [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
#[prefix = "ICN"]
Flake8ImportConventions,
/// [flake8-logging-format](https://pypi.org/project/flake8-logging-format/0.9.0/)
#[prefix = "G"]
Flake8LoggingFormat,
/// [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420/)
#[prefix = "INP"]
Flake8NoPep420,
/// [flake8-pie](https://pypi.org/project/flake8-pie/)
#[prefix = "PIE"]
Flake8Pie,
/// [flake8-print](https://pypi.org/project/flake8-print/)
#[prefix = "T20"]
Flake8Print,
/// [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
#[prefix = "PT"]
Flake8PytestStyle,
/// [flake8-quotes](https://pypi.org/project/flake8-quotes/)
#[prefix = "Q"]
Flake8Quotes,
/// [flake8-return](https://pypi.org/project/flake8-return/)
#[prefix = "RET"]
Flake8Return,
/// [flake8-simplify](https://pypi.org/project/flake8-simplify/)
#[prefix = "SIM"]
Flake8Simplify,
/// [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
#[prefix = "TID"]
Flake8TidyImports,
/// [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
#[prefix = "TCH"]
Flake8TypeChecking,
/// [flake8-unused-arguments](https://pypi.org/project/flake8-unused-arguments/)
#[prefix = "ARG"]
Flake8UnusedArguments,
/// [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
#[prefix = "PTH"]
Flake8UsePathlib,
/// [eradicate](https://pypi.org/project/eradicate/)
#[prefix = "ERA"]
Eradicate,
/// [pandas-vet](https://pypi.org/project/pandas-vet/)
#[prefix = "PD"]
PandasVet,
/// [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks)
#[prefix = "PGH"]
PygrepHooks,
/// [Pylint](https://pypi.org/project/pylint/)
#[prefix = "PL"]
Pylint,
/// [tryceratops](https://pypi.org/project/tryceratops/1.1.0/)
#[prefix = "TRY"]
Tryceratops,
/// Ruff-specific rules
#[prefix = "RUF"]
Ruff,
}
pub trait RuleNamespace: Sized {
fn parse_code(code: &str) -> Option<(Self, &str)>;
fn prefixes(&self) -> &'static [&'static str];
fn name(&self) -> &'static str;
fn url(&self) -> Option<&'static str>;
}
/// The prefix, name and selector for an upstream linter category.
pub struct LinterCategory(pub &'static str, pub &'static str, pub RuleSelector);
// TODO(martin): Move these constant definitions back to Linter::categories impl
// once RuleSelector is an enum with a Linter variant
const PYCODESTYLE_CATEGORIES: &[LinterCategory] = &[
LinterCategory("E", "Error", prefix_to_selector(RuleCodePrefix::E)),
LinterCategory("W", "Warning", prefix_to_selector(RuleCodePrefix::W)),
];
const PYLINT_CATEGORIES: &[LinterCategory] = &[
LinterCategory("PLC", "Convention", prefix_to_selector(RuleCodePrefix::PLC)),
LinterCategory("PLE", "Error", prefix_to_selector(RuleCodePrefix::PLE)),
LinterCategory("PLR", "Refactor", prefix_to_selector(RuleCodePrefix::PLR)),
LinterCategory("PLW", "Warning", prefix_to_selector(RuleCodePrefix::PLW)),
];
impl Linter {
pub fn categories(&self) -> Option<&'static [LinterCategory]> {
match self {
Linter::Pycodestyle => Some(PYCODESTYLE_CATEGORIES),
Linter::Pylint => Some(PYLINT_CATEGORIES),
_ => None,
}
}
}
pub enum LintSource {
Ast,
Io,
Lines,
Tokens,
Imports,
NoQa,
Filesystem,
}
impl Rule {
/// The source for the diagnostic (either the AST, the filesystem, or the
/// physical lines).
pub fn lint_source(&self) -> &'static LintSource {
match self {
Rule::UnusedNOQA => &LintSource::NoQa,
Rule::BlanketNOQA
| Rule::BlanketTypeIgnore
| Rule::DocLineTooLong
| Rule::LineTooLong
| Rule::MixedSpacesAndTabs
| Rule::NoNewLineAtEndOfFile
| Rule::PEP3120UnnecessaryCodingComment
| Rule::ShebangMissingExecutableFile
| Rule::ShebangNotExecutable
| Rule::ShebangNewline
| Rule::ShebangPython
| Rule::ShebangWhitespace => &LintSource::Lines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
| Rule::AvoidQuoteEscape
| Rule::BadQuotesDocstring
| Rule::BadQuotesInlineString
| Rule::BadQuotesMultilineString
| Rule::CommentedOutCode
| Rule::MultiLineImplicitStringConcatenation
| Rule::ExtraneousParentheses
| Rule::InvalidEscapeSequence
| Rule::SingleLineImplicitStringConcatenation
| Rule::TrailingCommaMissing
| Rule::TrailingCommaOnBareTupleProhibited
| Rule::TrailingCommaProhibited => &LintSource::Tokens,
Rule::IOError => &LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
Rule::ImplicitNamespacePackage => &LintSource::Filesystem,
_ => &LintSource::Ast,
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Diagnostic {
pub kind: DiagnosticKind,
pub location: Location,
pub end_location: Location,
pub fix: Option<Fix>,
pub parent: Option<Location>,
}
impl Diagnostic {
pub fn new<K: Into<DiagnosticKind>>(kind: K, range: Range) -> Self {
Self {
kind: kind.into(),
location: range.location,
end_location: range.end_location,
fix: None,
parent: None,
}
}
pub fn amend(&mut self, fix: Fix) -> &mut Self {
self.fix = Some(fix);
self
}
pub fn parent(&mut self, parent: Location) -> &mut Self {
self.parent = Some(parent);
self
}
}
/// Pairs of checks that shouldn't be enabled together.
pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str)] = &[(
Rule::OneBlankLineBeforeClass,
Rule::NoBlankLineBeforeClass,
"`D203` (OneBlankLineBeforeClass) and `D211` (NoBlankLinesBeforeClass) are incompatible. \
Consider adding `D203` to `ignore`.",
)];
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use super::{Linter, Rule, RuleNamespace};
#[test]
fn check_code_serialization() {
for rule in Rule::iter() {
assert!(
Rule::from_code(rule.code()).is_ok(),
"{rule:?} could not be round-trip serialized."
);
}
}
#[test]
fn test_linter_prefixes() {
for rule in Rule::iter() {
Linter::parse_code(rule.code())
.unwrap_or_else(|| panic!("couldn't parse {:?}", rule.code()));
}
}
}