Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
546be5692a | ||
|
|
43e1f20b28 | ||
|
|
97388cefda | ||
|
|
63ce579989 | ||
|
|
5f4a62aa40 | ||
|
|
02ab52b3e2 | ||
|
|
549732b1da | ||
|
|
c4565fe0f5 | ||
|
|
81ae3bfc94 | ||
|
|
62e6feadc7 | ||
|
|
18a26e8f0b | ||
|
|
2371de3895 | ||
|
|
e3c8f61340 | ||
|
|
f6628ae100 | ||
|
|
989ed9c10b |
@@ -1,5 +1,5 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.32
|
||||
rev: v0.0.35
|
||||
hooks:
|
||||
- id: lint
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1744,7 +1744,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.32"
|
||||
version = "0.0.35"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.32"
|
||||
version = "0.0.35"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
34
README.md
34
README.md
@@ -57,7 +57,7 @@ ruff also works with [Pre-Commit](https://pre-commit.com) (requires Cargo on sys
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.32
|
||||
rev: v0.0.35
|
||||
hooks:
|
||||
- id: lint
|
||||
```
|
||||
@@ -86,7 +86,7 @@ ruff path/to/code/ --select F401 F403
|
||||
See `ruff --help` for more:
|
||||
|
||||
```shell
|
||||
ruff (v0.0.32)
|
||||
ruff (v0.0.35)
|
||||
An extremely fast Python linter.
|
||||
|
||||
USAGE:
|
||||
@@ -96,16 +96,17 @@ ARGS:
|
||||
<FILES>...
|
||||
|
||||
OPTIONS:
|
||||
-e, --exit-zero Exit with status code "0", even upon detecting errors
|
||||
-f, --fix Attempt to automatically fix lint errors
|
||||
-h, --help Print help information
|
||||
--ignore <IGNORE>... Comma-separated list of error codes to ignore
|
||||
-n, --no-cache Disable cache reads
|
||||
-q, --quiet Disable all logging (but still exit with status code "1" upon
|
||||
detecting errors)
|
||||
--select <SELECT>... Comma-separated list of error codes to enable
|
||||
-v, --verbose Enable verbose logging
|
||||
-w, --watch Run in watch mode by re-running whenever files change
|
||||
-e, --exit-zero Exit with status code "0", even upon detecting errors
|
||||
--exclude <EXCLUDE>... List of file and/or directory patterns to exclude from checks
|
||||
-f, --fix Attempt to automatically fix lint errors
|
||||
-h, --help Print help information
|
||||
--ignore <IGNORE>... List of error codes to ignore
|
||||
-n, --no-cache Disable cache reads
|
||||
-q, --quiet Disable all logging (but still exit with status code "1" upon
|
||||
detecting errors)
|
||||
--select <SELECT>... List of error codes to enable
|
||||
-v, --verbose Enable verbose logging
|
||||
-w, --watch Run in watch mode by re-running whenever files change
|
||||
```
|
||||
|
||||
### Compatibility with Black
|
||||
@@ -123,7 +124,7 @@ ruff's goal is to achieve feature-parity with Flake8 when used (1) without any p
|
||||
stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.)
|
||||
|
||||
Under those conditions, Flake8 implements about 58 rules, give or take. At time of writing, ruff
|
||||
implements 28 rules. (Note that these 28 rules likely cover a disproportionate share of errors:
|
||||
implements 33 rules. (Note that these 33 rules likely cover a disproportionate share of errors:
|
||||
unused imports, undefined variables, etc.)
|
||||
|
||||
Of the unimplemented rules, ruff is missing:
|
||||
@@ -152,9 +153,14 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
|
||||
| E713 | NotInTest | Test for membership should be `not in` |
|
||||
| E714 | NotIsTest | Test for object identity should be `is not` |
|
||||
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def |
|
||||
| E741 | AmbiguousVariableName | ambiguous variable name '...' |
|
||||
| E742 | AmbiguousClassName | ambiguous class name '...' |
|
||||
| E743 | AmbiguousFunctionName | ambiguous function name '...' |
|
||||
| E902 | IOError | No such file or directory: `...` |
|
||||
| F401 | UnusedImport | `...` imported but unused |
|
||||
| F403 | ImportStarUsage | Unable to detect undefined names |
|
||||
| F404 | LateFutureImport | from __future__ imports must occur at the beginning of the file |
|
||||
| F407 | FutureFeatureNotDefined | future feature '...' is not defined |
|
||||
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
|
||||
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated |
|
||||
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated |
|
||||
@@ -206,7 +212,7 @@ git clone --branch 3.10 https://github.com/python/cpython.git resources/test/cpy
|
||||
Add this `pyproject.toml` to the CPython directory:
|
||||
|
||||
```toml
|
||||
[tool.linter]
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
exclude = [
|
||||
"Lib/lib2to3/tests/data/bom.py",
|
||||
|
||||
@@ -3,14 +3,19 @@ use ruff::checks::{CheckKind, RejectedCmpop};
|
||||
|
||||
fn main() {
|
||||
let mut check_kinds: Vec<CheckKind> = vec![
|
||||
CheckKind::AmbiguousVariableName("...".to_string()),
|
||||
CheckKind::AmbiguousClassName("...".to_string()),
|
||||
CheckKind::AmbiguousFunctionName("...".to_string()),
|
||||
CheckKind::AssertTuple,
|
||||
CheckKind::DefaultExceptNotLast,
|
||||
CheckKind::DoNotAssignLambda,
|
||||
CheckKind::DuplicateArgumentName,
|
||||
CheckKind::FStringMissingPlaceholders,
|
||||
CheckKind::FutureFeatureNotDefined("...".to_string()),
|
||||
CheckKind::IOError("...".to_string()),
|
||||
CheckKind::IfTuple,
|
||||
CheckKind::ImportStarUsage,
|
||||
CheckKind::LateFutureImport,
|
||||
CheckKind::LineTooLong,
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
CheckKind::MultiValueRepeatedKeyLiteral,
|
||||
|
||||
72
resources/test/fixtures/E741.py
vendored
Normal file
72
resources/test/fixtures/E741.py
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
from contextlib import contextmanager
|
||||
|
||||
l = 0
|
||||
I = 0
|
||||
O = 0
|
||||
l: int = 0
|
||||
|
||||
a, l = 0, 1
|
||||
[a, l] = 0, 1
|
||||
a, *l = 0, 1, 2
|
||||
a = l = 0
|
||||
|
||||
o = 0
|
||||
i = 0
|
||||
|
||||
for l in range(3):
|
||||
pass
|
||||
|
||||
|
||||
for a, l in zip(range(3), range(3)):
|
||||
pass
|
||||
|
||||
|
||||
def f1():
|
||||
global l
|
||||
l = 0
|
||||
|
||||
|
||||
def f2():
|
||||
l = 0
|
||||
|
||||
def f3():
|
||||
nonlocal l
|
||||
l = 1
|
||||
|
||||
f3()
|
||||
return l
|
||||
|
||||
|
||||
def f4(l, /, I):
|
||||
return l, I, O
|
||||
|
||||
|
||||
def f5(l=0, *, I=1):
|
||||
return l, I
|
||||
|
||||
|
||||
def f6(*l, **I):
|
||||
return l, I
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ctx1():
|
||||
yield 0
|
||||
|
||||
|
||||
with ctx1() as l:
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ctx2():
|
||||
yield 0, 1
|
||||
|
||||
|
||||
with ctx2() as (a, l):
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError as l:
|
||||
pass
|
||||
14
resources/test/fixtures/E742.py
vendored
Normal file
14
resources/test/fixtures/E742.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
class l:
|
||||
pass
|
||||
|
||||
|
||||
class I:
|
||||
pass
|
||||
|
||||
|
||||
class O:
|
||||
pass
|
||||
|
||||
|
||||
class X:
|
||||
pass
|
||||
15
resources/test/fixtures/E743.py
vendored
Normal file
15
resources/test/fixtures/E743.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
def l():
|
||||
pass
|
||||
|
||||
|
||||
def I():
|
||||
pass
|
||||
|
||||
|
||||
class X:
|
||||
def O(self):
|
||||
pass
|
||||
|
||||
|
||||
def x():
|
||||
pass
|
||||
7
resources/test/fixtures/F404.py
vendored
Normal file
7
resources/test/fixtures/F404.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import print_function
|
||||
"""Docstring"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from __future__ import print_function
|
||||
2
resources/test/fixtures/F407.py
vendored
Normal file
2
resources/test/fixtures/F407.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
from __future__ import print_function
|
||||
from __future__ import non_existent_feature
|
||||
8
resources/test/fixtures/F841.py
vendored
8
resources/test/fixtures/F841.py
vendored
@@ -14,3 +14,11 @@ def f():
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + y
|
||||
|
||||
|
||||
def g():
|
||||
foo = (1, 2)
|
||||
(a, b) = (1, 2)
|
||||
|
||||
bar = (1, 2)
|
||||
(c, d) = bar
|
||||
|
||||
5
resources/test/fixtures/pyproject.toml
vendored
5
resources/test/fixtures/pyproject.toml
vendored
@@ -9,9 +9,14 @@ select = [
|
||||
"E713",
|
||||
"E714",
|
||||
"E731",
|
||||
"E741",
|
||||
"E742",
|
||||
"E743",
|
||||
"E902",
|
||||
"F401",
|
||||
"F403",
|
||||
"F404",
|
||||
"F407",
|
||||
"F541",
|
||||
"F601",
|
||||
"F602",
|
||||
|
||||
@@ -93,6 +93,46 @@ pub fn check_do_not_assign_lambda(value: &Expr, location: Location) -> Option<Ch
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ambiguous_name(name: &str) -> bool {
|
||||
name == "l" || name == "I" || name == "O"
|
||||
}
|
||||
|
||||
/// Check AmbiguousVariableName compliance.
|
||||
pub fn check_ambiguous_variable_name(name: &str, location: Location) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousVariableName(name.to_string()),
|
||||
location,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check AmbiguousClassName compliance.
|
||||
pub fn check_ambiguous_class_name(name: &str, location: Location) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousClassName(name.to_string()),
|
||||
location,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check AmbiguousFunctionName compliance.
|
||||
pub fn check_ambiguous_function_name(name: &str, location: Location) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousFunctionName(name.to_string()),
|
||||
location,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check UselessObjectInheritance compliance.
|
||||
pub fn check_useless_object_inheritance(
|
||||
stmt: &Stmt,
|
||||
|
||||
@@ -66,6 +66,58 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
names
|
||||
}
|
||||
|
||||
/// Check if a node is parent of a conditional branch.
|
||||
pub fn on_conditional_branch(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
for index in parent_stack.iter().rev() {
|
||||
let parent = parents[*index];
|
||||
if matches!(parent.node, StmtKind::If { .. })
|
||||
|| matches!(parent.node, StmtKind::While { .. })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if let StmtKind::Expr { value } = &parent.node {
|
||||
if matches!(value.node, ExprKind::IfExp { .. }) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if a node is in a nested block.
|
||||
pub fn in_nested_block(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
for index in parent_stack.iter().rev() {
|
||||
let parent = parents[*index];
|
||||
if matches!(parent.node, StmtKind::Try { .. })
|
||||
|| matches!(parent.node, StmtKind::If { .. })
|
||||
|| matches!(parent.node, StmtKind::With { .. })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if a node represents an unpacking assignment.
|
||||
pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
|
||||
if let StmtKind::Assign { targets, value, .. } = &stmt.node {
|
||||
for child in targets {
|
||||
match &child.node {
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => {}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
match &value.node {
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => return false,
|
||||
_ => {}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Struct used to efficiently slice source code at (row, column) Locations.
|
||||
pub struct SourceCodeLocator<'a> {
|
||||
content: &'a str,
|
||||
|
||||
@@ -35,8 +35,10 @@ impl Scope {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BindingKind {
|
||||
Annotation,
|
||||
Argument,
|
||||
Assignment,
|
||||
Binding,
|
||||
Builtin,
|
||||
ClassDefinition,
|
||||
Definition,
|
||||
|
||||
235
src/check_ast.rs
235
src/check_ast.rs
@@ -1,3 +1,4 @@
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
use rustpython_parser::ast::{
|
||||
@@ -10,10 +11,11 @@ use crate::ast::operations::{extract_all_names, SourceCodeLocator};
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{Binding, BindingKind, Scope, ScopeKind};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::{checks, visitor};
|
||||
use crate::ast::{checks, operations, visitor};
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::python::future::ALL_FEATURE_NAMES;
|
||||
use crate::python::typing;
|
||||
use crate::settings::Settings;
|
||||
|
||||
@@ -37,12 +39,14 @@ struct Checker<'a> {
|
||||
deferred_annotations: Vec<(Location, &'a str)>,
|
||||
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>,
|
||||
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
|
||||
deferred_assignments: Vec<usize>,
|
||||
// Derivative state.
|
||||
in_f_string: bool,
|
||||
in_annotation: bool,
|
||||
in_literal: bool,
|
||||
seen_non_import: bool,
|
||||
seen_docstring: bool,
|
||||
futures_allowed: bool,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -66,11 +70,13 @@ impl<'a> Checker<'a> {
|
||||
deferred_annotations: vec![],
|
||||
deferred_functions: vec![],
|
||||
deferred_lambdas: vec![],
|
||||
deferred_assignments: vec![],
|
||||
in_f_string: false,
|
||||
in_annotation: false,
|
||||
in_literal: false,
|
||||
seen_non_import: false,
|
||||
seen_docstring: false,
|
||||
futures_allowed: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +104,66 @@ where
|
||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||
self.push_parent(stmt);
|
||||
|
||||
// Track whether we've seen docstrings, non-imports, etc.
|
||||
match &stmt.node {
|
||||
StmtKind::ImportFrom { module, .. } => {
|
||||
// Allow __future__ imports until we see a non-__future__ import.
|
||||
if self.futures_allowed {
|
||||
if let Some(module) = module {
|
||||
if module != "__future__" {
|
||||
self.futures_allowed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::Import { .. } => {
|
||||
self.futures_allowed = false;
|
||||
}
|
||||
StmtKind::Expr { value } => {
|
||||
if self.seen_docstring
|
||||
&& !self.seen_non_import
|
||||
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
|
||||
{
|
||||
self.seen_non_import = true;
|
||||
}
|
||||
|
||||
if !self.seen_docstring
|
||||
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
|
||||
&& matches!(
|
||||
&value.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
},
|
||||
)
|
||||
{
|
||||
self.seen_docstring = true;
|
||||
}
|
||||
|
||||
// Allow docstrings to interrupt __future__ imports.
|
||||
if self.futures_allowed
|
||||
&& !matches!(
|
||||
&value.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
},
|
||||
)
|
||||
{
|
||||
self.futures_allowed = false;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.futures_allowed = false;
|
||||
|
||||
if !self.seen_non_import
|
||||
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
|
||||
{
|
||||
self.seen_non_import = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-visit.
|
||||
match &stmt.node {
|
||||
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
|
||||
@@ -121,6 +187,12 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::E741) {
|
||||
self.checks.extend(names.iter().filter_map(|name| {
|
||||
checks::check_ambiguous_variable_name(name, stmt.location)
|
||||
}));
|
||||
}
|
||||
}
|
||||
StmtKind::FunctionDef {
|
||||
name,
|
||||
@@ -136,6 +208,12 @@ where
|
||||
args,
|
||||
..
|
||||
} => {
|
||||
if self.settings.select.contains(&CheckCode::E743) {
|
||||
if let Some(check) = checks::check_ambiguous_function_name(name, stmt.location)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
for expr in decorator_list {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
@@ -224,6 +302,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::E742) {
|
||||
if let Some(check) = checks::check_ambiguous_class_name(name, stmt.location) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
for expr in bases {
|
||||
self.visit_expr(expr)
|
||||
}
|
||||
@@ -320,6 +404,21 @@ where
|
||||
location: stmt.location,
|
||||
},
|
||||
);
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F407)
|
||||
&& !ALL_FEATURE_NAMES.contains(&alias.node.name.deref())
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::FutureFeatureNotDefined(alias.node.name.to_string()),
|
||||
stmt.location,
|
||||
));
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F404) && !self.futures_allowed
|
||||
{
|
||||
self.checks
|
||||
.push(Check::new(CheckKind::LateFutureImport, stmt.location));
|
||||
}
|
||||
} else if alias.node.name == "*" {
|
||||
self.add_binding(
|
||||
name,
|
||||
@@ -357,7 +456,6 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::AugAssign { target, .. } => {
|
||||
self.seen_non_import = true;
|
||||
self.handle_node_load(target);
|
||||
}
|
||||
StmtKind::If { test, .. } => {
|
||||
@@ -368,7 +466,6 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::Assert { test, .. } => {
|
||||
self.seen_non_import = true;
|
||||
if self.settings.select.contains(CheckKind::AssertTuple.code()) {
|
||||
if let Some(check) = checks::check_assert_tuple(test, stmt.location) {
|
||||
self.checks.push(check);
|
||||
@@ -382,21 +479,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::Expr { value } => {
|
||||
if !self.seen_docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
} = &value.node
|
||||
{
|
||||
self.seen_docstring = true;
|
||||
}
|
||||
} else {
|
||||
self.seen_non_import = true;
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { value, .. } => {
|
||||
self.seen_non_import = true;
|
||||
if self.settings.select.contains(&CheckCode::E731) {
|
||||
if let Some(check) = checks::check_do_not_assign_lambda(value, stmt.location) {
|
||||
self.checks.push(check);
|
||||
@@ -404,7 +487,6 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign { value, .. } => {
|
||||
self.seen_non_import = true;
|
||||
if self.settings.select.contains(&CheckCode::E731) {
|
||||
if let Some(value) = value {
|
||||
if let Some(check) =
|
||||
@@ -415,9 +497,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::Delete { .. } => {
|
||||
self.seen_non_import = true;
|
||||
}
|
||||
StmtKind::Delete { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -473,7 +553,7 @@ where
|
||||
self.in_literal = true;
|
||||
}
|
||||
}
|
||||
ExprKind::Tuple { elts, ctx } => {
|
||||
ExprKind::Tuple { elts, ctx } | ExprKind::List { elts, ctx } => {
|
||||
if matches!(ctx, ExprContext::Store) {
|
||||
let check_too_many_expressions =
|
||||
self.settings.select.contains(&CheckCode::F621);
|
||||
@@ -489,12 +569,19 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Name { ctx, .. } => match ctx {
|
||||
ExprKind::Name { id, ctx } => match ctx {
|
||||
ExprContext::Load => self.handle_node_load(expr),
|
||||
ExprContext::Store => {
|
||||
if self.settings.select.contains(&CheckCode::E741) {
|
||||
if let Some(check) =
|
||||
checks::check_ambiguous_variable_name(id, expr.location)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
let parent =
|
||||
self.parents[*(self.parent_stack.last().expect("No parent found."))];
|
||||
self.handle_node_store(expr, Some(parent));
|
||||
self.handle_node_store(expr, parent);
|
||||
}
|
||||
ExprContext::Del => self.handle_node_delete(expr),
|
||||
},
|
||||
@@ -728,6 +815,13 @@ where
|
||||
match &excepthandler.node {
|
||||
ExcepthandlerKind::ExceptHandler { name, .. } => match name {
|
||||
Some(name) => {
|
||||
if self.settings.select.contains(&CheckCode::E741) {
|
||||
if let Some(check) =
|
||||
checks::check_ambiguous_variable_name(name, excepthandler.location)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
let scope =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if scope.values.contains_key(name) {
|
||||
@@ -741,7 +835,7 @@ where
|
||||
ctx: ExprContext::Store,
|
||||
},
|
||||
),
|
||||
Some(parent),
|
||||
parent,
|
||||
);
|
||||
self.parents.push(parent);
|
||||
}
|
||||
@@ -759,7 +853,7 @@ where
|
||||
ctx: ExprContext::Store,
|
||||
},
|
||||
),
|
||||
Some(parent),
|
||||
parent,
|
||||
);
|
||||
self.parents.push(parent);
|
||||
|
||||
@@ -820,6 +914,13 @@ where
|
||||
location: arg.location,
|
||||
},
|
||||
);
|
||||
|
||||
if self.settings.select.contains(&CheckCode::E741) {
|
||||
if let Some(check) = checks::check_ambiguous_variable_name(&arg.node.arg, arg.location)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -922,7 +1023,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_node_store(&mut self, expr: &Expr, parent: Option<&Stmt>) {
|
||||
fn handle_node_store(&mut self, expr: &Expr, parent: &Stmt) {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
let current =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
@@ -952,40 +1053,69 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(charlie): Handle alternate binding types (like `Annotation`).
|
||||
if id == "__all__"
|
||||
&& matches!(current.kind, ScopeKind::Module)
|
||||
&& parent
|
||||
.map(|stmt| {
|
||||
matches!(stmt.node, StmtKind::Assign { .. })
|
||||
|| matches!(stmt.node, StmtKind::AugAssign { .. })
|
||||
|| matches!(stmt.node, StmtKind::AnnAssign { .. })
|
||||
})
|
||||
.unwrap_or_default()
|
||||
if matches!(parent.node, StmtKind::AnnAssign { value: None, .. }) {
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Annotation,
|
||||
used: None,
|
||||
location: expr.location,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(charlie): Include comprehensions here.
|
||||
if matches!(parent.node, StmtKind::For { .. })
|
||||
|| matches!(parent.node, StmtKind::AsyncFor { .. })
|
||||
|| operations::is_unpacking_assignment(parent)
|
||||
{
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Export(extract_all_names(parent.unwrap(), current)),
|
||||
kind: BindingKind::Binding,
|
||||
used: None,
|
||||
location: expr.location,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if id == "__all__"
|
||||
&& matches!(current.kind, ScopeKind::Module)
|
||||
&& (matches!(parent.node, StmtKind::Assign { .. })
|
||||
|| matches!(parent.node, StmtKind::AugAssign { .. })
|
||||
|| matches!(parent.node, StmtKind::AnnAssign { .. }))
|
||||
{
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
kind: BindingKind::Export(extract_all_names(parent, current)),
|
||||
used: None,
|
||||
location: expr.location,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
used: None,
|
||||
location: expr.location,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_node_delete(&mut self, expr: &Expr) {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
// Check if we're on a conditional branch.
|
||||
if operations::on_conditional_branch(&self.parent_stack, &self.parents) {
|
||||
return;
|
||||
}
|
||||
|
||||
let scope =
|
||||
&mut self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if scope.values.remove(id).is_none() && self.settings.select.contains(&CheckCode::F821)
|
||||
@@ -1033,11 +1163,8 @@ impl<'a> Checker<'a> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F841) {
|
||||
let scope =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
self.checks.extend(checks::check_unused_variables(scope));
|
||||
}
|
||||
self.deferred_assignments
|
||||
.push(*self.scope_stack.last().expect("No current scope found."));
|
||||
|
||||
self.pop_scope();
|
||||
}
|
||||
@@ -1056,16 +1183,23 @@ impl<'a> Checker<'a> {
|
||||
self.visit_expr(body);
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F841) {
|
||||
let scope =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
self.checks.extend(checks::check_unused_variables(scope));
|
||||
}
|
||||
self.deferred_assignments
|
||||
.push(*self.scope_stack.last().expect("No current scope found."));
|
||||
|
||||
self.pop_scope();
|
||||
}
|
||||
}
|
||||
|
||||
fn check_deferred_assignments(&mut self) {
|
||||
while !self.deferred_assignments.is_empty() {
|
||||
let index = self.deferred_assignments.pop().unwrap();
|
||||
if self.settings.select.contains(&CheckCode::F841) {
|
||||
self.checks
|
||||
.extend(checks::check_unused_variables(&self.scopes[index]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_dead_scopes(&mut self) {
|
||||
if !self.settings.select.contains(&CheckCode::F822)
|
||||
&& !self.settings.select.contains(&CheckCode::F401)
|
||||
@@ -1143,6 +1277,7 @@ pub fn check_ast(
|
||||
// Check any deferred statements.
|
||||
checker.check_deferred_functions();
|
||||
checker.check_deferred_lambdas();
|
||||
checker.check_deferred_assignments();
|
||||
let mut allocator = vec![];
|
||||
checker.check_deferred_annotations(path, &mut allocator);
|
||||
|
||||
|
||||
@@ -15,9 +15,14 @@ pub enum CheckCode {
|
||||
E713,
|
||||
E714,
|
||||
E731,
|
||||
E741,
|
||||
E742,
|
||||
E743,
|
||||
E902,
|
||||
F401,
|
||||
F403,
|
||||
F404,
|
||||
F407,
|
||||
F541,
|
||||
F601,
|
||||
F602,
|
||||
@@ -50,9 +55,14 @@ impl FromStr for CheckCode {
|
||||
"E713" => Ok(CheckCode::E713),
|
||||
"E714" => Ok(CheckCode::E714),
|
||||
"E731" => Ok(CheckCode::E731),
|
||||
"E741" => Ok(CheckCode::E741),
|
||||
"E742" => Ok(CheckCode::E742),
|
||||
"E743" => Ok(CheckCode::E743),
|
||||
"E902" => Ok(CheckCode::E902),
|
||||
"F401" => Ok(CheckCode::F401),
|
||||
"F403" => Ok(CheckCode::F403),
|
||||
"F404" => Ok(CheckCode::F404),
|
||||
"F407" => Ok(CheckCode::F407),
|
||||
"F541" => Ok(CheckCode::F541),
|
||||
"F601" => Ok(CheckCode::F601),
|
||||
"F602" => Ok(CheckCode::F602),
|
||||
@@ -86,9 +96,14 @@ impl CheckCode {
|
||||
CheckCode::E713 => "E713",
|
||||
CheckCode::E714 => "E714",
|
||||
CheckCode::E731 => "E731",
|
||||
CheckCode::E741 => "E741",
|
||||
CheckCode::E742 => "E742",
|
||||
CheckCode::E743 => "E743",
|
||||
CheckCode::E902 => "E902",
|
||||
CheckCode::F401 => "F401",
|
||||
CheckCode::F403 => "F403",
|
||||
CheckCode::F404 => "F404",
|
||||
CheckCode::F407 => "F407",
|
||||
CheckCode::F541 => "F541",
|
||||
CheckCode::F601 => "F601",
|
||||
CheckCode::F602 => "F602",
|
||||
@@ -120,9 +135,14 @@ impl CheckCode {
|
||||
CheckCode::E713 => &LintSource::AST,
|
||||
CheckCode::E714 => &LintSource::AST,
|
||||
CheckCode::E731 => &LintSource::AST,
|
||||
CheckCode::E741 => &LintSource::AST,
|
||||
CheckCode::E742 => &LintSource::AST,
|
||||
CheckCode::E743 => &LintSource::AST,
|
||||
CheckCode::E902 => &LintSource::FileSystem,
|
||||
CheckCode::F401 => &LintSource::AST,
|
||||
CheckCode::F403 => &LintSource::AST,
|
||||
CheckCode::F404 => &LintSource::AST,
|
||||
CheckCode::F407 => &LintSource::AST,
|
||||
CheckCode::F541 => &LintSource::AST,
|
||||
CheckCode::F601 => &LintSource::AST,
|
||||
CheckCode::F602 => &LintSource::AST,
|
||||
@@ -161,13 +181,18 @@ pub enum RejectedCmpop {
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CheckKind {
|
||||
AssertTuple,
|
||||
AmbiguousVariableName(String),
|
||||
AmbiguousClassName(String),
|
||||
AmbiguousFunctionName(String),
|
||||
DefaultExceptNotLast,
|
||||
DoNotAssignLambda,
|
||||
DuplicateArgumentName,
|
||||
FStringMissingPlaceholders,
|
||||
FutureFeatureNotDefined(String),
|
||||
IOError(String),
|
||||
IfTuple,
|
||||
ImportStarUsage,
|
||||
LateFutureImport,
|
||||
LineTooLong,
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
@@ -195,12 +220,17 @@ impl CheckKind {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
CheckKind::AssertTuple => "AssertTuple",
|
||||
CheckKind::AmbiguousVariableName(_) => "AmbiguousVariableName",
|
||||
CheckKind::AmbiguousClassName(_) => "AmbiguousClassName",
|
||||
CheckKind::AmbiguousFunctionName(_) => "AmbiguousFunctionName",
|
||||
CheckKind::DefaultExceptNotLast => "DefaultExceptNotLast",
|
||||
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
|
||||
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
|
||||
CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined",
|
||||
CheckKind::IOError(_) => "IOError",
|
||||
CheckKind::IfTuple => "IfTuple",
|
||||
CheckKind::ImportStarUsage => "ImportStarUsage",
|
||||
CheckKind::LateFutureImport => "LateFutureImport",
|
||||
CheckKind::LineTooLong => "LineTooLong",
|
||||
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
|
||||
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
|
||||
@@ -234,11 +264,16 @@ impl CheckKind {
|
||||
CheckKind::DefaultExceptNotLast => &CheckCode::F707,
|
||||
CheckKind::DuplicateArgumentName => &CheckCode::F831,
|
||||
CheckKind::FStringMissingPlaceholders => &CheckCode::F541,
|
||||
CheckKind::FutureFeatureNotDefined(_) => &CheckCode::F407,
|
||||
CheckKind::IOError(_) => &CheckCode::E902,
|
||||
CheckKind::IfTuple => &CheckCode::F634,
|
||||
CheckKind::ImportStarUsage => &CheckCode::F403,
|
||||
CheckKind::LateFutureImport => &CheckCode::F404,
|
||||
CheckKind::LineTooLong => &CheckCode::E501,
|
||||
CheckKind::DoNotAssignLambda => &CheckCode::E731,
|
||||
CheckKind::AmbiguousVariableName(_) => &CheckCode::E741,
|
||||
CheckKind::AmbiguousClassName(_) => &CheckCode::E742,
|
||||
CheckKind::AmbiguousFunctionName(_) => &CheckCode::E743,
|
||||
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
|
||||
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
|
||||
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
|
||||
@@ -276,15 +311,30 @@ impl CheckKind {
|
||||
CheckKind::FStringMissingPlaceholders => {
|
||||
"f-string without any placeholders".to_string()
|
||||
}
|
||||
CheckKind::FutureFeatureNotDefined(name) => {
|
||||
format!("future feature '{name}' is not defined")
|
||||
}
|
||||
CheckKind::IOError(name) => {
|
||||
format!("No such file or directory: `{name}`")
|
||||
}
|
||||
CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(),
|
||||
CheckKind::ImportStarUsage => "Unable to detect undefined names".to_string(),
|
||||
CheckKind::LateFutureImport => {
|
||||
"from __future__ imports must occur at the beginning of the file".to_string()
|
||||
}
|
||||
CheckKind::LineTooLong => "Line too long".to_string(),
|
||||
CheckKind::DoNotAssignLambda => {
|
||||
"Do not assign a lambda expression, use a def".to_string()
|
||||
}
|
||||
CheckKind::AmbiguousClassName(name) => {
|
||||
format!("ambiguous class name '{}'", name)
|
||||
}
|
||||
CheckKind::AmbiguousFunctionName(name) => {
|
||||
format!("ambiguous function name '{}'", name)
|
||||
}
|
||||
CheckKind::AmbiguousVariableName(name) => {
|
||||
format!("ambiguous variable name '{}'", name)
|
||||
}
|
||||
CheckKind::ModuleImportNotAtTopOfFile => {
|
||||
"Module level import not at top of file".to_string()
|
||||
}
|
||||
@@ -358,22 +408,27 @@ impl CheckKind {
|
||||
/// Whether the check kind is (potentially) fixable.
|
||||
pub fn fixable(&self) -> bool {
|
||||
match self {
|
||||
CheckKind::AmbiguousClassName(_) => true,
|
||||
CheckKind::AmbiguousFunctionName(_) => true,
|
||||
CheckKind::AmbiguousVariableName(_) => false,
|
||||
CheckKind::AssertTuple => false,
|
||||
CheckKind::DefaultExceptNotLast => false,
|
||||
CheckKind::DoNotAssignLambda => false,
|
||||
CheckKind::DuplicateArgumentName => false,
|
||||
CheckKind::FStringMissingPlaceholders => false,
|
||||
CheckKind::FutureFeatureNotDefined(_) => false,
|
||||
CheckKind::IOError(_) => false,
|
||||
CheckKind::IfTuple => false,
|
||||
CheckKind::ImportStarUsage => false,
|
||||
CheckKind::DoNotAssignLambda => false,
|
||||
CheckKind::LateFutureImport => false,
|
||||
CheckKind::LineTooLong => false,
|
||||
CheckKind::ModuleImportNotAtTopOfFile => false,
|
||||
CheckKind::MultiValueRepeatedKeyLiteral => false,
|
||||
CheckKind::MultiValueRepeatedKeyVariable(_) => false,
|
||||
CheckKind::NoAssertEquals => true,
|
||||
CheckKind::NoneComparison(_) => false,
|
||||
CheckKind::NotInTest => false,
|
||||
CheckKind::NotIsTest => false,
|
||||
CheckKind::NoneComparison(_) => false,
|
||||
CheckKind::RaiseNotImplemented => false,
|
||||
CheckKind::ReturnOutsideFunction => false,
|
||||
CheckKind::TooManyExpressionsInStarredAssignment => false,
|
||||
|
||||
285
src/linter.rs
285
src/linter.rs
@@ -298,6 +298,225 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e741() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
Path::new("./resources/test/fixtures/E741.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E741]),
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
actual.sort_by_key(|check| check.location);
|
||||
let expected = vec![
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(3, 1),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("I".to_string()),
|
||||
location: Location::new(4, 1),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("O".to_string()),
|
||||
location: Location::new(5, 1),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(6, 1),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(8, 4),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(9, 5),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(10, 5),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(11, 5),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(16, 5),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(20, 8),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(25, 5),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(26, 5),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(30, 5),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(33, 9),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(34, 9),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(40, 8),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("I".to_string()),
|
||||
location: Location::new(40, 14),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(44, 8),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("I".to_string()),
|
||||
location: Location::new(44, 16),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(48, 9),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("I".to_string()),
|
||||
location: Location::new(48, 14),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(57, 16),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(66, 20),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousVariableName("l".to_string()),
|
||||
location: Location::new(71, 1),
|
||||
fix: None,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e742() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
Path::new("./resources/test/fixtures/E742.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E742]),
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
actual.sort_by_key(|check| check.location);
|
||||
let expected = vec![
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousClassName("l".to_string()),
|
||||
location: Location::new(1, 1),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousClassName("I".to_string()),
|
||||
location: Location::new(5, 1),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousClassName("O".to_string()),
|
||||
location: Location::new(9, 1),
|
||||
fix: None,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e743() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
Path::new("./resources/test/fixtures/E743.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E743]),
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
actual.sort_by_key(|check| check.location);
|
||||
let expected = vec![
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousFunctionName("l".to_string()),
|
||||
location: Location::new(1, 1),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousFunctionName("I".to_string()),
|
||||
location: Location::new(5, 1),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::AmbiguousFunctionName("O".to_string()),
|
||||
location: Location::new(10, 5),
|
||||
fix: None,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f401() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
@@ -366,6 +585,57 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f404() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
Path::new("./resources/test/fixtures/F404.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F404]),
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
actual.sort_by_key(|check| check.location);
|
||||
let expected = vec![Check {
|
||||
kind: CheckKind::LateFutureImport,
|
||||
location: Location::new(7, 1),
|
||||
fix: None,
|
||||
}];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f407() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
Path::new("./resources/test/fixtures/F407.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F407]),
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
actual.sort_by_key(|check| check.location);
|
||||
let expected = vec![Check {
|
||||
kind: CheckKind::FutureFeatureNotDefined("non_existent_feature".to_string()),
|
||||
location: Location::new(2, 1),
|
||||
fix: None,
|
||||
}];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f541() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
@@ -814,6 +1084,21 @@ mod tests {
|
||||
location: Location::new(16, 5),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::UnusedVariable("foo".to_string()),
|
||||
location: Location::new(20, 5),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::UnusedVariable("a".to_string()),
|
||||
location: Location::new(21, 6),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::UnusedVariable("b".to_string()),
|
||||
location: Location::new(21, 9),
|
||||
fix: None,
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
|
||||
11
src/main.rs
11
src/main.rs
@@ -6,6 +6,7 @@ use std::time::Instant;
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, ValueHint};
|
||||
use colored::Colorize;
|
||||
use glob::Pattern;
|
||||
use log::{debug, error};
|
||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||
use rayon::prelude::*;
|
||||
@@ -47,12 +48,15 @@ struct Cli {
|
||||
/// Disable cache reads.
|
||||
#[clap(short, long, action)]
|
||||
no_cache: bool,
|
||||
/// Comma-separated list of error codes to enable.
|
||||
/// List of error codes to enable.
|
||||
#[clap(long, multiple = true)]
|
||||
select: Vec<CheckCode>,
|
||||
/// Comma-separated list of error codes to ignore.
|
||||
/// List of error codes to ignore.
|
||||
#[clap(long, multiple = true)]
|
||||
ignore: Vec<CheckCode>,
|
||||
/// List of file and/or directory patterns to exclude from checks.
|
||||
#[clap(long, multiple = true)]
|
||||
exclude: Vec<Pattern>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
@@ -188,6 +192,9 @@ fn inner_main() -> Result<ExitCode> {
|
||||
if !cli.ignore.is_empty() {
|
||||
settings.ignore(&cli.ignore);
|
||||
}
|
||||
if !cli.exclude.is_empty() {
|
||||
settings.exclude(cli.exclude);
|
||||
}
|
||||
|
||||
if cli.watch {
|
||||
if cli.fix {
|
||||
|
||||
@@ -20,7 +20,7 @@ impl Ord for Message {
|
||||
(&self.filename, self.location.row(), self.location.column()).cmp(&(
|
||||
&other.filename,
|
||||
other.location.row(),
|
||||
self.location.column(),
|
||||
other.location.column(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,9 +266,14 @@ other-attribute = 1
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F404,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod builtins;
|
||||
pub mod future;
|
||||
pub mod typing;
|
||||
|
||||
13
src/python/future.rs
Normal file
13
src/python/future.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
/// A copy of `__future__.all_feature_names`.
|
||||
pub const ALL_FEATURE_NAMES: &[&str] = &[
|
||||
"nested_scopes",
|
||||
"generators",
|
||||
"division",
|
||||
"absolute_import",
|
||||
"with_statement",
|
||||
"print_function",
|
||||
"unicode_literals",
|
||||
"barry_as_FLUFL",
|
||||
"generator_stop",
|
||||
"annotations",
|
||||
];
|
||||
@@ -51,9 +51,13 @@ impl Settings {
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
@@ -94,4 +98,8 @@ impl Settings {
|
||||
self.select.remove(code);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exclude(&mut self, exclude: Vec<Pattern>) {
|
||||
self.exclude = exclude;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user