Compare commits

..

26 Commits

Author SHA1 Message Date
Charlie Marsh
062d7081a0 Bump version to 0.0.36 2022-09-12 11:16:26 -04:00
Charlie Marsh
40c1e7e005 Implement F701 and F702 (#169) 2022-09-12 11:16:08 -04:00
Charlie Marsh
3cbd05ddff Update README 2022-09-12 09:31:16 -04:00
Harutaka Kawamura
9414090617 Implement E722 (#166) 2022-09-12 09:04:39 -04:00
Harutaka Kawamura
825777edc1 Add test case for assignment expression in E741.py (#168) 2022-09-12 09:03:36 -04:00
Charlie Marsh
4e0807e908 Include line length in E501 messages (#165) 2022-09-11 22:54:11 -04:00
Charlie Marsh
546be5692a Bump version to 0.0.35 2022-09-11 21:54:00 -04:00
Charlie Marsh
43e1f20b28 Allow unused assignments in for loops and unpacking (#163) 2022-09-11 21:53:45 -04:00
Harutaka Kawamura
97388cefda Implement E743 (#162) 2022-09-11 21:27:33 -04:00
Harutaka Kawamura
63ce579989 Implement E742 (#160) 2022-09-11 20:27:48 -04:00
Charlie Marsh
5f4a62aa40 Bump version to 0.0.34 2022-09-11 18:05:52 -04:00
Charlie Marsh
02ab52b3e2 Implement F407 (#158) 2022-09-11 18:05:28 -04:00
Charlie Marsh
549732b1da Implement F404 (#159) 2022-09-11 18:05:00 -04:00
Harutaka Kawamura
c4565fe0f5 Implement E741 (#137) 2022-09-11 12:30:28 -04:00
Charlie Marsh
81ae3bfc94 Bump version to 0.0.33 2022-09-11 10:45:02 -04:00
Charlie Marsh
62e6feadc7 Handle accesses within inner functions (#156) 2022-09-11 10:44:27 -04:00
Charlie Marsh
18a26e8f0b Allow setting --exclude on the command-line (#157) 2022-09-11 10:44:23 -04:00
Charlie Marsh
2371de3895 Make late imports more permissive (#155) 2022-09-11 10:44:06 -04:00
Charlie Marsh
e3c8f61340 Ignore deletes in conditional branches (#154) 2022-09-11 10:28:07 -04:00
Harutaka Kawamura
f6628ae100 Fix Message.cmp (#152) 2022-09-11 10:18:27 -04:00
Jakub Kuczys
989ed9c10b Fix ruff's pyproject.toml section name in README.md (#148) 2022-09-11 10:18:19 -04:00
Charlie Marsh
8698c06c36 Bump version to 0.0.32 2022-09-10 15:21:01 -04:00
Charlie Marsh
dfd8a4158d Parse function annotations within the ClassDef scope (#144) 2022-09-10 15:20:39 -04:00
Charlie Marsh
c247730bf5 Avoid treating keys as annotations in TypedDict 2022-09-10 15:19:11 -04:00
Charlie Marsh
024472d578 Implement F621 and F622 (#143) 2022-09-10 15:04:33 -04:00
Charlie Marsh
7d69a153e8 Support remaining typing module members (#141) 2022-09-10 14:51:43 -04:00
34 changed files with 1530 additions and 170 deletions

View File

@@ -1,5 +1,5 @@
repos:
- repo: https://github.com/charliermarsh/ruff
rev: v0.0.31
rev: v0.0.36
hooks:
- id: lint

3
Cargo.lock generated
View File

@@ -1744,7 +1744,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.31"
version = "0.0.36"
dependencies = [
"anyhow",
"bincode",
@@ -1759,6 +1759,7 @@ dependencies = [
"filetime",
"glob",
"itertools",
"lazy_static",
"log",
"notify",
"once_cell",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.31"
version = "0.0.36"
edition = "2021"
[lib]
@@ -20,6 +20,7 @@ fern = { version = "0.6.1" }
filetime = { version = "0.2.17" }
glob = { version = "0.3.0" }
itertools = "0.10.3"
lazy_static = "1.4.0"
log = { version = "0.4.17" }
notify = { version = "4.0.17" }
once_cell = { version = "1.13.1" }

View File

@@ -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.31
rev: v0.0.36
hooks:
- id: lint
```
@@ -86,7 +86,7 @@ ruff path/to/code/ --select F401 F403
See `ruff --help` for more:
```shell
ruff (v0.0.31)
ruff (v0.0.36)
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,13 +124,12 @@ 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 24 rules. (Note that these 24 rules likely cover a disproportionate share of errors:
implements 36 rules. (Note that these 36 rules likely cover a disproportionate share of errors:
unused imports, undefined variables, etc.)
Of the unimplemented rules, ruff is missing:
- 15 rules related to string `.format` calls.
- 6 rules related to misplaced `yield`, `break`, and `return` statements.
- 3 rules related to syntax errors in doctests and annotations.
- 2 rules related to redefining unused names.
@@ -146,20 +146,30 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
| Code | Name | Message |
| ---- | ----- | ------- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file |
| E501 | LineTooLong | Line too long |
| E501 | LineTooLong | Line too long (89 > 88 characters) |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` |
| E713 | NotInTest | Test for membership should be `not in` |
| E714 | NotIsTest | Test for object identity should be `is not` |
| E722 | DoNotUseBareExcept | Do not use bare `except` |
| 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 |
| F621 | TooManyExpressionsInStarredAssignment | too many expressions in star-unpacking assignment |
| F622 | TwoStarredExpressions | two starred expressions in assignment |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` |
| F634 | IfTuple | If test is a tuple, which is always `True` |
| F701 | BreakOutsideLoop | `break` outside loop |
| F702 | ContinueOutsideLoop | `continue` not properly in loop |
| F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method |
| F706 | ReturnOutsideFunction | a `return` statement outside of a function/method |
| F707 | DefaultExceptNotLast | an `except:` block as not the last exception handler |
@@ -204,7 +214,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",

View File

@@ -3,15 +3,23 @@ use ruff::checks::{CheckKind, RejectedCmpop};
fn main() {
let mut check_kinds: Vec<CheckKind> = vec![
CheckKind::AmbiguousClassName("...".to_string()),
CheckKind::AmbiguousFunctionName("...".to_string()),
CheckKind::AmbiguousVariableName("...".to_string()),
CheckKind::AssertTuple,
CheckKind::BreakOutsideLoop,
CheckKind::ContinueOutsideLoop,
CheckKind::DefaultExceptNotLast,
CheckKind::DoNotAssignLambda,
CheckKind::DoNotUseBareExcept,
CheckKind::DuplicateArgumentName,
CheckKind::FStringMissingPlaceholders,
CheckKind::FutureFeatureNotDefined("...".to_string()),
CheckKind::IOError("...".to_string()),
CheckKind::IfTuple,
CheckKind::ImportStarUsage,
CheckKind::LineTooLong,
CheckKind::LateFutureImport,
CheckKind::LineTooLong(89, 88),
CheckKind::ModuleImportNotAtTopOfFile,
CheckKind::MultiValueRepeatedKeyLiteral,
CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
@@ -21,7 +29,9 @@ fn main() {
CheckKind::NotIsTest,
CheckKind::RaiseNotImplemented,
CheckKind::ReturnOutsideFunction,
CheckKind::TooManyExpressionsInStarredAssignment,
CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
CheckKind::TwoStarredExpressions,
CheckKind::UndefinedExport("...".to_string()),
CheckKind::UndefinedLocal("...".to_string()),
CheckKind::UndefinedName("...".to_string()),

9
resources/test/fixtures/E722.py vendored Normal file
View File

@@ -0,0 +1,9 @@
try:
pass
except:
pass
try:
pass
except Exception:
pass

75
resources/test/fixtures/E741.py vendored Normal file
View File

@@ -0,0 +1,75 @@
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
if (l := 5) > 0:
pass

14
resources/test/fixtures/E742.py vendored Normal file
View 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
View File

@@ -0,0 +1,15 @@
def l():
pass
def I():
pass
class X:
def O(self):
pass
def x():
pass

View File

@@ -11,10 +11,13 @@ import multiprocessing.pool
import multiprocessing.process
import logging.config
import logging.handlers
from typing import NamedTuple, Dict, Type, TypeVar, List, Set
from typing import TYPING_CHECK, NamedTuple, Dict, Type, TypeVar, List, Set, Union, cast
from blah import ClassA, ClassB, ClassC
if TYPING_CHECK:
from models import Fruit, Nut, Vegetable
class X:
datetime: datetime
@@ -32,3 +35,7 @@ __all__ += ["ClassC"]
X = TypeVar("X")
Y = TypeVar("Y", bound="Dict")
Z = TypeVar("Z", "List", "Set")
a = list["Fruit"]
b = Union["Nut", None]
c = cast("Vegetable", b)

7
resources/test/fixtures/F404.py vendored Normal file
View 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
View File

@@ -0,0 +1,2 @@
from __future__ import print_function
from __future__ import non_existent_feature

3
resources/test/fixtures/F622.py vendored Normal file
View File

@@ -0,0 +1,3 @@
*a, *b, c = (1, 2, 3)
*a, b, c = (1, 2, 3)
a, b, *c = (1, 2, 3)

23
resources/test/fixtures/F701.py vendored Normal file
View File

@@ -0,0 +1,23 @@
for i in range(10):
break
else:
break
i = 0
while i < 10:
i += 1
break
def f():
for i in range(10):
break
break
class Foo:
break
break

23
resources/test/fixtures/F702.py vendored Normal file
View File

@@ -0,0 +1,23 @@
for i in range(10):
continue
else:
continue
i = 0
while i < 10:
i += 1
continue
def f():
for i in range(10):
continue
continue
class Foo:
continue
continue

View File

@@ -64,4 +64,16 @@ from typing import List, TypedDict
class Item(TypedDict):
nodes: List[TypedDict("Node", {"id": str})]
nodes: List[TypedDict("Node", {"name": str})]
from enum import Enum
class Ticket:
class Status(Enum):
OPEN = "OPEN"
CLOSED = "CLOSED"
def set_status(self, status: Status):
self.status = status

View File

@@ -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

View File

@@ -8,15 +8,25 @@ select = [
"E712",
"E713",
"E714",
"E722",
"E731",
"E741",
"E742",
"E743",
"E902",
"F401",
"F403",
"F404",
"F407",
"F541",
"F601",
"F602",
"F621",
"F622",
"F631",
"F634",
"F701",
"F702",
"F704",
"F706",
"F707",

View File

@@ -3,7 +3,7 @@ use std::collections::BTreeSet;
use itertools::izip;
use rustpython_parser::ast::{
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword,
Location, Stmt, Unaryop,
Location, Stmt, StmtKind, Unaryop,
};
use crate::ast::operations::SourceCodeLocator;
@@ -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,
@@ -395,3 +435,110 @@ pub fn check_literal_comparisons(
checks
}
/// Check TwoStarredExpressions and TooManyExpressionsInStarredAssignment compliance.
pub fn check_starred_expressions(
elts: &[Expr],
location: Location,
check_too_many_expressions: bool,
check_two_starred_expressions: bool,
) -> Option<Check> {
let mut has_starred: bool = false;
let mut starred_index: Option<usize> = None;
for (index, elt) in elts.iter().enumerate() {
if matches!(elt.node, ExprKind::Starred { .. }) {
if has_starred && check_two_starred_expressions {
return Some(Check::new(CheckKind::TwoStarredExpressions, location));
}
has_starred = true;
starred_index = Some(index);
}
}
if check_too_many_expressions {
if let Some(starred_index) = starred_index {
if starred_index >= 1 << 8 || elts.len() - starred_index > 1 << 24 {
return Some(Check::new(
CheckKind::TooManyExpressionsInStarredAssignment,
location,
));
}
}
}
None
}
/// Check BreakOutsideLoop compliance.
pub fn check_break_outside_loop(
stmt: &Stmt,
parents: &[&Stmt],
parent_stack: &[usize],
) -> Option<Check> {
let mut allowed: bool = false;
let mut parent = stmt;
for index in parent_stack.iter().rev() {
let child = parent;
parent = parents[*index];
match &parent.node {
StmtKind::For { orelse, .. }
| StmtKind::AsyncFor { orelse, .. }
| StmtKind::While { orelse, .. } => {
if !orelse.contains(child) {
allowed = true;
break;
}
}
StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::ClassDef { .. } => {
break;
}
_ => {}
}
}
if !allowed {
Some(Check::new(CheckKind::BreakOutsideLoop, stmt.location))
} else {
None
}
}
/// Check ContinueOutsideLoop compliance.
pub fn check_continue_outside_loop(
stmt: &Stmt,
parents: &[&Stmt],
parent_stack: &[usize],
) -> Option<Check> {
let mut allowed: bool = false;
let mut parent = stmt;
for index in parent_stack.iter().rev() {
let child = parent;
parent = parents[*index];
match &parent.node {
StmtKind::For { orelse, .. }
| StmtKind::AsyncFor { orelse, .. }
| StmtKind::While { orelse, .. } => {
if !orelse.contains(child) {
allowed = true;
break;
}
}
StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::ClassDef { .. } => {
break;
}
_ => {}
}
}
if !allowed {
Some(Check::new(CheckKind::ContinueOutsideLoop, stmt.location))
} else {
None
}
}

View File

@@ -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,

View File

@@ -35,8 +35,10 @@ impl Scope {
#[derive(Clone, Debug)]
pub enum BindingKind {
Annotation,
Argument,
Assignment,
Binding,
Builtin,
ClassDefinition,
Definition,

View File

@@ -1,3 +1,4 @@
use std::ops::Deref;
use std::path::Path;
use rustpython_parser::ast::{
@@ -10,10 +11,12 @@ 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::builtins::{BUILTINS, MAGIC_GLOBALS};
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;
pub const GLOBAL_SCOPE_INDEX: usize = 0;
@@ -36,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> {
@@ -65,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,
}
}
}
@@ -82,6 +89,14 @@ fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
}
}
fn is_annotated_subscript(expr: &Expr) -> bool {
match &expr.node {
ExprKind::Attribute { attr, .. } => typing::is_annotated_subscript(attr),
ExprKind::Name { id, .. } => typing::is_annotated_subscript(id),
_ => false,
}
}
impl<'a, 'b> Visitor<'b> for Checker<'a>
where
'b: 'a,
@@ -89,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 } => {
@@ -112,25 +187,88 @@ 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::Break => {
if self.settings.select.contains(&CheckCode::F701) {
if let Some(check) =
checks::check_break_outside_loop(stmt, &self.parents, &self.parent_stack)
{
self.checks.push(check);
}
}
}
StmtKind::Continue => {
if self.settings.select.contains(&CheckCode::F702) {
if let Some(check) =
checks::check_continue_outside_loop(stmt, &self.parents, &self.parent_stack)
{
self.checks.push(check);
}
}
}
StmtKind::FunctionDef {
name,
decorator_list,
returns,
args,
..
}
| StmtKind::AsyncFunctionDef {
name,
decorator_list,
returns,
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);
}
for arg in &args.posonlyargs {
if let Some(expr) = &arg.node.annotation {
self.visit_annotation(expr);
}
}
for arg in &args.args {
if let Some(expr) = &arg.node.annotation {
self.visit_annotation(expr);
}
}
if let Some(arg) = &args.vararg {
if let Some(expr) = &arg.node.annotation {
self.visit_annotation(expr);
}
}
for arg in &args.kwonlyargs {
if let Some(expr) = &arg.node.annotation {
self.visit_annotation(expr);
}
}
if let Some(arg) = &args.kwarg {
if let Some(expr) = &arg.node.annotation {
self.visit_annotation(expr);
}
}
for expr in returns {
self.visit_annotation(expr);
}
for expr in &args.kw_defaults {
self.visit_expr(expr);
}
for expr in &args.defaults {
self.visit_expr(expr);
}
self.add_binding(
name.to_string(),
Binding {
@@ -182,6 +320,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)
}
@@ -278,6 +422,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,
@@ -315,7 +474,6 @@ where
}
}
StmtKind::AugAssign { target, .. } => {
self.seen_non_import = true;
self.handle_node_load(target);
}
StmtKind::If { test, .. } => {
@@ -326,7 +484,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);
@@ -340,21 +497,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);
@@ -362,7 +505,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) =
@@ -373,9 +515,7 @@ where
}
}
}
StmtKind::Delete { .. } => {
self.seen_non_import = true;
}
StmtKind::Delete { .. } => {}
_ => {}
}
@@ -422,6 +562,7 @@ where
fn visit_expr(&mut self, expr: &'b Expr) {
let prev_in_f_string = self.in_f_string;
let prev_in_literal = self.in_literal;
let prev_in_annotation = self.in_annotation;
// Pre-visit.
match &expr.node {
@@ -430,12 +571,35 @@ where
self.in_literal = true;
}
}
ExprKind::Name { ctx, .. } => match 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);
let check_two_starred_expressions =
self.settings.select.contains(&CheckCode::F622);
if let Some(check) = checks::check_starred_expressions(
elts,
expr.location,
check_too_many_expressions,
check_two_starred_expressions,
) {
self.checks.push(check);
}
}
}
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),
},
@@ -544,7 +708,25 @@ where
args,
keywords,
} => {
if match_name_or_attr(func, "TypeVar") {
if match_name_or_attr(func, "ForwardRef") {
self.visit_expr(func);
for expr in args {
self.visit_annotation(expr);
}
} else if match_name_or_attr(func, "cast") {
self.visit_expr(func);
if !args.is_empty() {
self.visit_annotation(&args[0]);
}
for expr in args.iter().skip(1) {
self.visit_expr(expr);
}
} else if match_name_or_attr(func, "NewType") {
self.visit_expr(func);
for expr in args.iter().skip(1) {
self.visit_annotation(expr);
}
} else if match_name_or_attr(func, "TypeVar") {
self.visit_expr(func);
for expr in args.iter().skip(1) {
self.visit_annotation(expr);
@@ -555,16 +737,54 @@ where
if id == "bound" {
self.visit_annotation(value);
} else {
self.in_annotation = false;
self.visit_expr(value);
self.in_annotation = prev_in_annotation;
}
}
}
} else if match_name_or_attr(func, "NamedTuple") {
self.visit_expr(func);
// NamedTuple("a", [("a", int)])
if args.len() > 1 {
match &args[1].node {
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
for elt in elts {
match &elt.node {
ExprKind::List { elts, .. }
| ExprKind::Tuple { elts, .. } => {
if elts.len() == 2 {
self.in_annotation = false;
self.visit_expr(&elts[0]);
self.in_annotation = prev_in_annotation;
self.visit_annotation(&elts[1]);
}
}
_ => {}
}
}
}
_ => {}
}
}
// NamedTuple("a", a=int)
for keyword in keywords {
let KeywordData { value, .. } = &keyword.node;
self.visit_annotation(value);
}
} else if match_name_or_attr(func, "TypedDict") {
self.visit_expr(func);
// TypedDict("a", {"a": int})
if args.len() > 1 {
if let ExprKind::Dict { keys, values } = &args[1].node {
for key in keys {
self.in_annotation = false;
self.visit_expr(key);
self.in_annotation = prev_in_annotation;
}
for value in values {
self.visit_annotation(value);
@@ -582,7 +802,7 @@ where
}
}
ExprKind::Subscript { value, slice, ctx } => {
if match_name_or_attr(value, "Type") {
if is_annotated_subscript(value) {
self.visit_expr(value);
self.visit_annotation(slice);
self.visit_expr_context(ctx);
@@ -603,19 +823,53 @@ where
}
_ => {}
};
self.in_annotation = prev_in_annotation;
self.in_literal = prev_in_literal;
self.in_f_string = prev_in_f_string;
}
fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) {
match &excepthandler.node {
ExcepthandlerKind::ExceptHandler { name, .. } => match name {
Some(name) => {
let scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if scope.values.contains_key(name) {
ExcepthandlerKind::ExceptHandler { type_, name, .. } => {
if self.settings.select.contains(&CheckCode::E722) && type_.is_none() {
self.checks.push(Check::new(
CheckKind::DoNotUseBareExcept,
excepthandler.location,
));
}
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) {
let parent = self.parents
[*(self.parent_stack.last().expect("No parent found."))];
self.handle_node_store(
&Expr::new(
excepthandler.location,
ExprKind::Name {
id: name.to_string(),
ctx: ExprContext::Store,
},
),
parent,
);
self.parents.push(parent);
}
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
let definition = scope.values.get(name).cloned();
self.handle_node_store(
&Expr::new(
excepthandler.location,
@@ -624,48 +878,32 @@ where
ctx: ExprContext::Store,
},
),
Some(parent),
parent,
);
self.parents.push(parent);
}
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
let scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let definition = scope.values.get(name).cloned();
self.handle_node_store(
&Expr::new(
excepthandler.location,
ExprKind::Name {
id: name.to_string(),
ctx: ExprContext::Store,
},
),
Some(parent),
);
self.parents.push(parent);
walk_excepthandler(self, excepthandler);
walk_excepthandler(self, excepthandler);
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(binding) = &scope.values.remove(name) {
if self.settings.select.contains(&CheckCode::F841)
&& binding.used.is_none()
{
self.checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),
excepthandler.location,
));
}
}
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(binding) = &scope.values.remove(name) {
if self.settings.select.contains(&CheckCode::F841) && binding.used.is_none()
{
self.checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),
excepthandler.location,
));
if let Some(binding) = definition {
scope.values.insert(name.to_string(), binding);
}
}
if let Some(binding) = definition {
scope.values.insert(name.to_string(), binding);
}
None => walk_excepthandler(self, excepthandler),
}
None => walk_excepthandler(self, excepthandler),
},
}
}
}
@@ -674,10 +912,27 @@ where
self.checks
.extend(checks::check_duplicate_arguments(arguments));
}
visitor::walk_arguments(self, arguments);
// Bind, but intentionally avoid walking default expressions, as we handle them upstream.
for arg in &arguments.posonlyargs {
self.visit_arg(arg);
}
for arg in &arguments.args {
self.visit_arg(arg);
}
if let Some(arg) = &arguments.vararg {
self.visit_arg(arg);
}
for arg in &arguments.kwonlyargs {
self.visit_arg(arg);
}
if let Some(arg) = &arguments.kwarg {
self.visit_arg(arg);
}
}
fn visit_arg(&mut self, arg: &'b Arg) {
// Bind, but intentionally avoid walking the annotation, as we handle it upstream.
self.add_binding(
arg.node.arg.to_string(),
Binding {
@@ -686,7 +941,13 @@ where
location: arg.location,
},
);
visitor::walk_arg(self, arg);
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);
}
}
}
}
@@ -789,7 +1050,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."))];
@@ -819,40 +1080,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)
@@ -900,10 +1190,8 @@ impl<'a> Checker<'a> {
_ => {}
}
let scope = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if self.settings.select.contains(&CheckCode::F841) {
self.checks.extend(checks::check_unused_variables(scope));
}
self.deferred_assignments
.push(*self.scope_stack.last().expect("No current scope found."));
self.pop_scope();
}
@@ -922,15 +1210,23 @@ impl<'a> Checker<'a> {
self.visit_expr(body);
}
let scope = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if self.settings.select.contains(&CheckCode::F841) {
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)
@@ -1008,6 +1304,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);

View File

@@ -1,11 +1,11 @@
use rustpython_parser::ast::Location;
use crate::checks::{Check, CheckKind};
use crate::checks::{Check, CheckCode, CheckKind};
use crate::settings::Settings;
/// Whether the given line is too long and should be reported.
fn should_enforce_line_length(line: &str, limit: usize) -> bool {
if line.len() > limit {
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
if length > limit {
let mut chunks = line.split_whitespace();
if let (Some(first), Some(_)) = (chunks.next(), chunks.next()) {
// Do not enforce the line length for commented lines with a single word
@@ -20,7 +20,7 @@ fn should_enforce_line_length(line: &str, limit: usize) -> bool {
}
pub fn check_lines(checks: &mut Vec<Check>, contents: &str, settings: &Settings) {
let enforce_line_too_long = settings.select.contains(CheckKind::LineTooLong.code());
let enforce_line_too_long = settings.select.contains(&CheckCode::E501);
let mut line_checks = vec![];
let mut ignored = vec![];
@@ -34,13 +34,16 @@ pub fn check_lines(checks: &mut Vec<Check>, contents: &str, settings: &Settings)
}
// Enforce line length.
if enforce_line_too_long && should_enforce_line_length(line, settings.line_length) {
let check = Check::new(
CheckKind::LineTooLong,
Location::new(row + 1, settings.line_length + 1),
);
if !check.is_inline_ignored(line) {
line_checks.push(check);
if enforce_line_too_long {
let line_length = line.len();
if should_enforce_line_length(line, line_length, settings.line_length) {
let check = Check::new(
CheckKind::LineTooLong(line_length, settings.line_length),
Location::new(row + 1, settings.line_length + 1),
);
if !check.is_inline_ignored(line) {
line_checks.push(check);
}
}
}
}

View File

@@ -14,15 +14,25 @@ pub enum CheckCode {
E712,
E713,
E714,
E722,
E731,
E741,
E742,
E743,
E902,
F401,
F403,
F404,
F407,
F541,
F601,
F602,
F621,
F622,
F631,
F634,
F701,
F702,
F704,
F706,
F707,
@@ -46,16 +56,26 @@ impl FromStr for CheckCode {
"E711" => Ok(CheckCode::E711),
"E712" => Ok(CheckCode::E712),
"E713" => Ok(CheckCode::E713),
"E722" => Ok(CheckCode::E722),
"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),
"F621" => Ok(CheckCode::F621),
"F622" => Ok(CheckCode::F622),
"F631" => Ok(CheckCode::F631),
"F634" => Ok(CheckCode::F634),
"F701" => Ok(CheckCode::F701),
"F702" => Ok(CheckCode::F702),
"F704" => Ok(CheckCode::F704),
"F706" => Ok(CheckCode::F706),
"F707" => Ok(CheckCode::F707),
@@ -81,15 +101,25 @@ impl CheckCode {
CheckCode::E712 => "E712",
CheckCode::E713 => "E713",
CheckCode::E714 => "E714",
CheckCode::E722 => "E722",
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",
CheckCode::F621 => "F621",
CheckCode::F622 => "F622",
CheckCode::F631 => "F631",
CheckCode::F634 => "F634",
CheckCode::F701 => "F701",
CheckCode::F702 => "F702",
CheckCode::F704 => "F704",
CheckCode::F706 => "F706",
CheckCode::F707 => "F707",
@@ -113,15 +143,25 @@ impl CheckCode {
CheckCode::E712 => &LintSource::AST,
CheckCode::E713 => &LintSource::AST,
CheckCode::E714 => &LintSource::AST,
CheckCode::E722 => &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,
CheckCode::F621 => &LintSource::AST,
CheckCode::F622 => &LintSource::AST,
CheckCode::F631 => &LintSource::AST,
CheckCode::F634 => &LintSource::AST,
CheckCode::F701 => &LintSource::AST,
CheckCode::F702 => &LintSource::AST,
CheckCode::F704 => &LintSource::AST,
CheckCode::F706 => &LintSource::AST,
CheckCode::F707 => &LintSource::AST,
@@ -152,15 +192,23 @@ pub enum RejectedCmpop {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
AmbiguousClassName(String),
AmbiguousFunctionName(String),
AmbiguousVariableName(String),
AssertTuple,
BreakOutsideLoop,
ContinueOutsideLoop,
DefaultExceptNotLast,
DoNotAssignLambda,
DoNotUseBareExcept,
DuplicateArgumentName,
FStringMissingPlaceholders,
FutureFeatureNotDefined(String),
IOError(String),
IfTuple,
ImportStarUsage,
LineTooLong,
LateFutureImport,
LineTooLong(usize, usize),
ModuleImportNotAtTopOfFile,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable(String),
@@ -170,7 +218,9 @@ pub enum CheckKind {
NotIsTest,
RaiseNotImplemented,
ReturnOutsideFunction,
TooManyExpressionsInStarredAssignment,
TrueFalseComparison(bool, RejectedCmpop),
TwoStarredExpressions,
UndefinedExport(String),
UndefinedLocal(String),
UndefinedName(String),
@@ -184,15 +234,23 @@ impl CheckKind {
/// The name of the check.
pub fn name(&self) -> &'static str {
match self {
CheckKind::AmbiguousClassName(_) => "AmbiguousClassName",
CheckKind::AmbiguousFunctionName(_) => "AmbiguousFunctionName",
CheckKind::AmbiguousVariableName(_) => "AmbiguousVariableName",
CheckKind::AssertTuple => "AssertTuple",
CheckKind::BreakOutsideLoop => "BreakOutsideLoop",
CheckKind::ContinueOutsideLoop => "ContinueOutsideLoop",
CheckKind::DefaultExceptNotLast => "DefaultExceptNotLast",
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
CheckKind::DoNotUseBareExcept => "DoNotUseBareExcept",
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined",
CheckKind::IOError(_) => "IOError",
CheckKind::IfTuple => "IfTuple",
CheckKind::ImportStarUsage => "ImportStarUsage",
CheckKind::LineTooLong => "LineTooLong",
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
CheckKind::LateFutureImport => "LateFutureImport",
CheckKind::LineTooLong(_, _) => "LineTooLong",
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
CheckKind::MultiValueRepeatedKeyLiteral => "MultiValueRepeatedKeyLiteral",
CheckKind::MultiValueRepeatedKeyVariable(_) => "MultiValueRepeatedKeyVariable",
@@ -202,7 +260,11 @@ impl CheckKind {
CheckKind::NotIsTest => "NotIsTest",
CheckKind::RaiseNotImplemented => "RaiseNotImplemented",
CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction",
CheckKind::TooManyExpressionsInStarredAssignment => {
"TooManyExpressionsInStarredAssignment"
}
CheckKind::TrueFalseComparison(_, _) => "TrueFalseComparison",
CheckKind::TwoStarredExpressions => "TwoStarredExpressions",
CheckKind::UndefinedExport(_) => "UndefinedExport",
CheckKind::UndefinedLocal(_) => "UndefinedLocal",
CheckKind::UndefinedName(_) => "UndefinedName",
@@ -216,15 +278,23 @@ impl CheckKind {
/// A four-letter shorthand code for the check.
pub fn code(&self) -> &'static CheckCode {
match self {
CheckKind::AmbiguousClassName(_) => &CheckCode::E742,
CheckKind::AmbiguousFunctionName(_) => &CheckCode::E743,
CheckKind::AmbiguousVariableName(_) => &CheckCode::E741,
CheckKind::AssertTuple => &CheckCode::F631,
CheckKind::BreakOutsideLoop => &CheckCode::F701,
CheckKind::ContinueOutsideLoop => &CheckCode::F702,
CheckKind::DefaultExceptNotLast => &CheckCode::F707,
CheckKind::DoNotAssignLambda => &CheckCode::E731,
CheckKind::DoNotUseBareExcept => &CheckCode::E722,
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::LineTooLong => &CheckCode::E501,
CheckKind::DoNotAssignLambda => &CheckCode::E731,
CheckKind::LateFutureImport => &CheckCode::F404,
CheckKind::LineTooLong(_, _) => &CheckCode::E501,
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
@@ -234,7 +304,9 @@ impl CheckKind {
CheckKind::NotIsTest => &CheckCode::E714,
CheckKind::RaiseNotImplemented => &CheckCode::F901,
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
CheckKind::TooManyExpressionsInStarredAssignment => &CheckCode::F621,
CheckKind::TrueFalseComparison(_, _) => &CheckCode::E712,
CheckKind::TwoStarredExpressions => &CheckCode::F622,
CheckKind::UndefinedExport(_) => &CheckCode::F822,
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
CheckKind::UndefinedName(_) => &CheckCode::F821,
@@ -248,26 +320,46 @@ impl CheckKind {
/// The body text for the check.
pub fn body(&self) -> String {
match self {
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::AssertTuple => {
"Assert test is a non-empty tuple, which is always `True`".to_string()
}
CheckKind::BreakOutsideLoop => "`break` outside loop".to_string(),
CheckKind::ContinueOutsideLoop => "`continue` not properly in loop".to_string(),
CheckKind::DefaultExceptNotLast => {
"an `except:` block as not the last exception handler".to_string()
}
CheckKind::DoNotAssignLambda => {
"Do not assign a lambda expression, use a def".to_string()
}
CheckKind::DoNotUseBareExcept => "Do not use bare `except`".to_string(),
CheckKind::DuplicateArgumentName => {
"Duplicate argument name in function definition".to_string()
}
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::LineTooLong => "Line too long".to_string(),
CheckKind::DoNotAssignLambda => {
"Do not assign a lambda expression, use a def".to_string()
CheckKind::LateFutureImport => {
"from __future__ imports must occur at the beginning of the file".to_string()
}
CheckKind::LineTooLong(length, limit) => {
format!("Line too long ({length} > {limit} characters)")
}
CheckKind::ModuleImportNotAtTopOfFile => {
"Module level import not at top of file".to_string()
@@ -295,6 +387,9 @@ impl CheckKind {
CheckKind::ReturnOutsideFunction => {
"a `return` statement outside of a function/method".to_string()
}
CheckKind::TooManyExpressionsInStarredAssignment => {
"too many expressions in star-unpacking assignment".to_string()
}
CheckKind::TrueFalseComparison(value, op) => match *value {
true => match op {
RejectedCmpop::Eq => {
@@ -313,15 +408,16 @@ impl CheckKind {
}
},
},
CheckKind::TwoStarredExpressions => "two starred expressions in assignment".to_string(),
CheckKind::UndefinedExport(name) => {
format!("Undefined name `{name}` in `__all__`")
}
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UndefinedLocal(name) => {
format!("Local variable `{name}` referenced before assignment")
}
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedImport(name) => format!("`{name}` imported but unused"),
CheckKind::UnusedVariable(name) => {
format!("Local variable `{name}` is assigned to but never used")
@@ -337,34 +433,10 @@ impl CheckKind {
/// Whether the check kind is (potentially) fixable.
pub fn fixable(&self) -> bool {
match self {
CheckKind::AssertTuple => false,
CheckKind::DefaultExceptNotLast => false,
CheckKind::DuplicateArgumentName => false,
CheckKind::FStringMissingPlaceholders => false,
CheckKind::IOError(_) => false,
CheckKind::IfTuple => false,
CheckKind::ImportStarUsage => false,
CheckKind::DoNotAssignLambda => false,
CheckKind::LineTooLong => false,
CheckKind::ModuleImportNotAtTopOfFile => false,
CheckKind::MultiValueRepeatedKeyLiteral => false,
CheckKind::MultiValueRepeatedKeyVariable(_) => false,
CheckKind::NoAssertEquals => true,
CheckKind::NotInTest => false,
CheckKind::NotIsTest => false,
CheckKind::NoneComparison(_) => false,
CheckKind::RaiseNotImplemented => false,
CheckKind::ReturnOutsideFunction => false,
CheckKind::TrueFalseComparison(_, _) => false,
CheckKind::UndefinedExport(_) => false,
CheckKind::UndefinedLocal(_) => false,
CheckKind::UndefinedName(_) => false,
CheckKind::UnusedImport(_) => false,
CheckKind::UnusedVariable(_) => false,
CheckKind::UselessObjectInheritance(_) => true,
CheckKind::YieldOutsideFunction => false,
}
matches!(
self,
CheckKind::NoAssertEquals | CheckKind::UselessObjectInheritance(_)
)
}
}

View File

@@ -2,7 +2,6 @@ extern crate core;
mod ast;
mod autofix;
mod builtins;
mod cache;
pub mod check_ast;
mod check_lines;
@@ -12,4 +11,5 @@ pub mod linter;
pub mod logging;
pub mod message;
mod pyproject;
mod python;
pub mod settings;

View File

@@ -128,7 +128,7 @@ mod tests {
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![Check {
kind: CheckKind::LineTooLong,
kind: CheckKind::LineTooLong(123, 88),
location: Location::new(5, 89),
fix: None,
}];
@@ -240,6 +240,31 @@ mod tests {
Ok(())
}
#[test]
fn e722() -> Result<()> {
let mut actual = check_path(
Path::new("./resources/test/fixtures/E722.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::E722]),
},
&fixer::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![Check {
kind: CheckKind::DoNotUseBareExcept,
location: Location::new(3, 1),
fix: None,
}];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn e714() -> Result<()> {
let mut actual = check_path(
@@ -298,6 +323,230 @@ 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,
},
Check {
kind: CheckKind::AmbiguousVariableName("l".to_string()),
location: Location::new(74, 5),
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 +615,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(
@@ -463,6 +763,30 @@ mod tests {
Ok(())
}
#[test]
fn f622() -> Result<()> {
let actual = check_path(
Path::new("./resources/test/fixtures/F622.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F622]),
},
&fixer::Mode::Generate,
)?;
let expected = vec![Check {
kind: CheckKind::TwoStarredExpressions,
location: Location::new(1, 1),
fix: None,
}];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f631() -> Result<()> {
let mut actual = check_path(
@@ -527,6 +851,90 @@ mod tests {
Ok(())
}
#[test]
fn f701() -> Result<()> {
let mut actual = check_path(
Path::new("./resources/test/fixtures/F701.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F701]),
},
&fixer::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::BreakOutsideLoop,
location: Location::new(4, 5),
fix: None,
},
Check {
kind: CheckKind::BreakOutsideLoop,
location: Location::new(16, 5),
fix: None,
},
Check {
kind: CheckKind::BreakOutsideLoop,
location: Location::new(20, 5),
fix: None,
},
Check {
kind: CheckKind::BreakOutsideLoop,
location: Location::new(23, 1),
fix: None,
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f702() -> Result<()> {
let mut actual = check_path(
Path::new("./resources/test/fixtures/F702.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F702]),
},
&fixer::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::ContinueOutsideLoop,
location: Location::new(4, 5),
fix: None,
},
Check {
kind: CheckKind::ContinueOutsideLoop,
location: Location::new(16, 5),
fix: None,
},
Check {
kind: CheckKind::ContinueOutsideLoop,
location: Location::new(20, 5),
fix: None,
},
Check {
kind: CheckKind::ContinueOutsideLoop,
location: Location::new(23, 1),
fix: None,
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f704() -> Result<()> {
let mut actual = check_path(
@@ -790,6 +1198,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() {

View File

@@ -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 {

View File

@@ -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(),
))
}
}

View File

@@ -265,15 +265,25 @@ other-attribute = 1
CheckCode::E712,
CheckCode::E713,
CheckCode::E714,
CheckCode::E722,
CheckCode::E731,
CheckCode::E741,
CheckCode::E742,
CheckCode::E743,
CheckCode::E902,
CheckCode::F401,
CheckCode::F403,
CheckCode::F404,
CheckCode::F407,
CheckCode::F541,
CheckCode::F601,
CheckCode::F602,
CheckCode::F621,
CheckCode::F622,
CheckCode::F631,
CheckCode::F634,
CheckCode::F701,
CheckCode::F702,
CheckCode::F704,
CheckCode::F706,
CheckCode::F707,

3
src/python.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod builtins;
pub mod future;
pub mod typing;

View File

@@ -156,8 +156,8 @@ pub const BUILTINS: &[&str] = &[
// Globally defined names which are not attributes of the builtins module, or are only present on
// some platforms.
pub const MAGIC_GLOBALS: &[&str] = &[
"__file__",
"__builtins__",
"__annotations__",
"WindowsError",
"__annotations__",
"__builtins__",
"__file__",
];

13
src/python/future.rs Normal file
View 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",
];

88
src/python/typing.rs Normal file
View File

@@ -0,0 +1,88 @@
use lazy_static::lazy_static;
use std::collections::BTreeSet;
lazy_static! {
static ref ANNOTATED_SUBSCRIPTS: BTreeSet<&'static str> = BTreeSet::from([
"AbstractAsyncContextManager",
"AbstractContextManager",
"AbstractSet",
"AsyncContextManager",
"AsyncGenerator",
"AsyncIterable",
"AsyncIterator",
"Awaitable",
"BinaryIO",
"BsdDbShelf",
"ByteString",
"Callable",
"ChainMap",
"ClassVar",
"Collection",
"Concatenate",
"Container",
"ContextManager",
"Coroutine",
"Counter",
"Counter",
"DbfilenameShelf",
"DefaultDict",
"Deque",
"Dict",
"Field",
"Final",
"FrozenSet",
"Generator",
"Iterator",
"Generic",
"IO",
"ItemsView",
"Iterable",
"Iterator",
"KeysView",
"LifoQueue",
"List",
"Mapping",
"MappingProxyType",
"MappingView",
"Match",
"MutableMapping",
"MutableSequence",
"MutableSet",
"Optional",
"OrderedDict",
"PathLike",
"Pattern",
"PriorityQueue",
"Protocol",
"Queue",
"Reversible",
"Sequence",
"Set",
"Shelf",
"SimpleQueue",
"TextIO",
"Tuple",
"Type",
"TypeGuard",
"Union",
"ValuesView",
"WeakKeyDictionary",
"WeakMethod",
"WeakSet",
"WeakValueDictionary",
"cached_property",
"defaultdict",
"deque",
"dict",
"frozenset",
"list",
"partialmethod",
"set",
"tuple",
"type",
]);
}
pub fn is_annotated_subscript(name: &str) -> bool {
ANNOTATED_SUBSCRIPTS.contains(name)
}

View File

@@ -50,15 +50,24 @@ impl Settings {
CheckCode::E712,
CheckCode::E713,
CheckCode::E714,
CheckCode::E722,
CheckCode::E731,
CheckCode::E741,
CheckCode::E742,
CheckCode::E743,
CheckCode::E902,
CheckCode::F401,
CheckCode::F403,
CheckCode::F407,
CheckCode::F541,
CheckCode::F601,
CheckCode::F602,
CheckCode::F621,
CheckCode::F622,
CheckCode::F631,
CheckCode::F634,
CheckCode::F701,
CheckCode::F702,
CheckCode::F704,
CheckCode::F706,
CheckCode::F707,
@@ -92,4 +101,8 @@ impl Settings {
self.select.remove(code);
}
}
pub fn exclude(&mut self, exclude: Vec<Pattern>) {
self.exclude = exclude;
}
}