Compare commits

...

12 Commits

Author SHA1 Message Date
Charlie Marsh
14cf36f922 Bump version to 0.0.140 2022-11-26 15:05:46 -05:00
Charlie Marsh
d28e026525 Preserve existing noqa codes in --add-noqa (#913) 2022-11-26 14:42:19 -05:00
Jonathan Plasse
d19a8aa54d Auto-generate CheckCodePrefix::fixables() (#916) 2022-11-26 14:10:30 -05:00
Charlie Marsh
f299940452 Respect noqa comments in U009 (#917) 2022-11-26 14:03:18 -05:00
Charlie Marsh
e1ab7163ac Respect f-string locations in B023 check (#914) 2022-11-26 10:31:23 -05:00
Jonathan Plasse
9edc479c6c Fix F821 false positive (#911) 2022-11-26 10:12:07 -05:00
Charlie Marsh
560558b814 Bump version to 0.0.139 2022-11-25 18:38:26 -05:00
Andri Bergsson
bef601b994 Add keyword argument handling for redundant open modes. (#906) 2022-11-25 18:38:05 -05:00
Charlie Marsh
7445d00b88 Implement B023 (function uses loop variable) (#907) 2022-11-25 18:29:54 -05:00
Charlie Marsh
7c78d4e103 Use docstring comment for CheckCode 2022-11-25 13:15:43 -05:00
Oliver Margetts
8b14f1b8cc Implement F522-F525 (#899) 2022-11-25 13:14:31 -05:00
Xuan (Sean) Hu
5a6b51e623 Minor changes in README. (#903) 2022-11-25 09:49:16 -05:00
46 changed files with 1822 additions and 230 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.138
rev: v0.0.140
hooks:
- id: ruff

6
Cargo.lock generated
View File

@@ -670,7 +670,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.138-dev.0"
version = "0.0.140-dev.0"
dependencies = [
"anyhow",
"clap 4.0.22",
@@ -1775,7 +1775,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.138"
version = "0.0.140"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1825,7 +1825,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.138"
version = "0.0.140"
dependencies = [
"anyhow",
"clap 4.0.22",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.138"
version = "0.0.140"
edition = "2021"
rust-version = "1.65.0"

View File

@@ -56,6 +56,7 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
1. [pep8-naming (N)](#pep8-naming)
1. [flake8-bandit (S)](#flake8-bandit)
1. [flake8-comprehensions (C)](#flake8-comprehensions)
1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap)
1. [flake8-bugbear (B)](#flake8-bugbear)
1. [flake8-builtins (A)](#flake8-builtins)
1. [flake8-tidy-imports (I25)](#flake8-tidy-imports)
@@ -64,7 +65,6 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
1. [flake8-annotations (ANN)](#flake8-annotations)
1. [flake8-2020 (YTT)](#flake8-2020)
1. [flake8-blind-except (BLE)](#flake8-blind-except)
1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap)
1. [mccabe (C90)](#mccabe)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules)
1. [Meta rules (M)](#meta-rules)
@@ -107,7 +107,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.138
rev: v0.0.140
hooks:
- id: ruff
```
@@ -285,7 +285,7 @@ Exclusions are based on globs, and can be either:
### Ignoring errors
To omit a lint check entirely, add it to the "ignore" list via `--ignore` or `--extend-ignore`,
either on the command-line or in your `project.toml` file.
either on the command-line or in your `project.toml` file.
To ignore an error in-line, Ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
To ignore an individual error, add `# noqa: {code}` to the end of the line, like so:
@@ -351,6 +351,10 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level | |
| F407 | FutureFeatureNotDefined | Future feature `...` is not defined | |
| F521 | StringDotFormatInvalidFormat | '...'.format(...) has invalid format string: ... | |
| F522 | StringDotFormatExtraNamedArguments | '...'.format(...) has unused named argument(s): ... | |
| F523 | StringDotFormatExtraPositionalArguments | '...'.format(...) has unused arguments at position(s): ... | |
| F524 | StringDotFormatMissingArguments | '...'.format(...) is missing argument(s) for placeholder(s): ... | |
| F525 | StringDotFormatMixingAutomatic | '...'.format(...) mixes automatic and manual numbering | |
| F541 | FStringMissingPlaceholders | f-string without any placeholders | |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | |
@@ -571,6 +575,7 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
| B020 | LoopVariableOverridesIterator | Loop control variable `...` overrides iterable it iterates | |
| B021 | FStringDocstring | f-string used as docstring. This will be interpreted by python as a joined string rather than a docstring. | |
| B022 | UselessContextlibSuppress | No arguments passed to `contextlib.suppress`. No exceptions will be suppressed and therefore this context manager is redundant | |
| B023 | FunctionUsesLoopVariable | Function definition does not bind loop variable `...` | |
| B024 | AbstractBaseClassWithoutAbstractMethod | `...` is an abstract base class, but it has no abstract methods | |
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
| B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged | |
@@ -807,7 +812,7 @@ automatically convert your existing configuration.)
Ruff can be used as a (near) drop-in replacement for Flake8 when used (1) without or with a small
number of plugins, (2) alongside Black, and (3) on Python 3 code.
Under those conditions Ruff is missing 14 rules related to string `.format` calls, 1 rule related
Under those conditions Ruff is missing 9 rules related to `%` string formatting, 1 rule related
to docstring parsing, and 1 rule related to redefined variables.
Ruff re-implements some of the most popular Flake8 plugins and related code quality tools natively,
@@ -816,6 +821,8 @@ including:
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`yesqa`](https://github.com/asottile/yesqa)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
@@ -823,9 +830,7 @@ including:
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (27/32)
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/)
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)
@@ -847,6 +852,8 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
@@ -855,8 +862,6 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (27/32)
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/)
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.138"
version = "0.0.140"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.138"
version = "0.0.140"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.138-dev.0"
version = "0.0.140-dev.0"
edition = "2021"
[lib]

82
resources/test/fixtures/B023.py vendored Normal file
View File

@@ -0,0 +1,82 @@
"""
Should emit:
B023 - on lines 12, 13, 16, 28, 29, 30, 31, 40, 42, 50, 51, 52, 53, 61, 68.
"""
functions = []
z = 0
for x in range(3):
y = x + 1
# Subject to late-binding problems
functions.append(lambda: x)
functions.append(lambda: y) # not just the loop var
def f_bad_1():
return x
# Actually OK
functions.append(lambda x: x * 2)
functions.append(lambda x=x: x)
functions.append(lambda: z) # OK because not assigned in the loop
def f_ok_1(x):
return x * 2
def check_inside_functions_too():
ls = [lambda: x for x in range(2)]
st = {lambda: x for x in range(2)}
gn = (lambda: x for x in range(2))
dt = {x: lambda: x for x in range(2)}
async def pointless_async_iterable():
yield 1
async def container_for_problems():
async for x in pointless_async_iterable():
functions.append(lambda: x)
[lambda: x async for x in pointless_async_iterable()]
a = 10
b = 0
while True:
a = a_ = a - 1
b += 1
functions.append(lambda: a)
functions.append(lambda: a_)
functions.append(lambda: b)
functions.append(lambda: c) # not a name error because of late binding!
c: bool = a > 3
if not c:
break
# Nested loops should not duplicate reports
for j in range(2):
for k in range(3):
lambda: j * k
for j, k, l in [(1, 2, 3)]:
def f():
j = None # OK because it's an assignment
[l for k in range(2)] # error for l, not for k
assert a and functions
a.attribute = 1 # modifying an attribute doesn't make it a loop variable
functions[0] = lambda: None # same for an element
for var in range(2):
def explicit_capture(captured=var):
return captured
for i in range(3):
lambda: f"{i}"

View File

@@ -8,14 +8,6 @@
"{foo..}".format(foo=1)
"{foo..bar}".format(foo=1)
# "{} {1}".format(1, 2) # F525
# "{0} {}".format(1, 2) # F525
# "{}".format(1, 2) # F523
# "{}".format(1, bar=2) # F522
# "{} {}".format(1) # F524
# "{2}".format() # F524
# "{bar}".format() # F524
# The following are all "good" uses of .format
"{.__class__}".format("")
"{foo[bar]}".format(foo={"bar": "barv"})

4
resources/test/fixtures/F522.py vendored Normal file
View File

@@ -0,0 +1,4 @@
"{}".format(1, bar=2) # F522
"{bar}{}".format(1, bar=2, spam=3) # F522
"{bar:{spam}}".format(bar=2, spam=3) # No issues
"{bar:{spam}}".format(bar=2, spam=3, eggs=4, ham=5) # F522

12
resources/test/fixtures/F523.py vendored Normal file
View File

@@ -0,0 +1,12 @@
# With indexes
"{0}".format(1, 2) # F523
"{1}".format(1, 2, 3) # F523
"{1:{0}}".format(1, 2) # No issues
"{1:{0}}".format(1, 2, 3) # F523
"{0}{2}".format(1, 2) # F523, # F524
# With no indexes
"{}".format(1, 2) # F523
"{}".format(1, 2, 3) # F523
"{:{}}".format(1, 2) # No issues
"{:{}}".format(1, 2, 3) # F523

6
resources/test/fixtures/F524.py vendored Normal file
View File

@@ -0,0 +1,6 @@
"{} {}".format(1) # F524
"{2}".format() # F524
"{bar}".format() # F524
"{0} {bar}".format(1) # F524
"{0} {bar}".format() # F524
"{bar} {0}".format() # F524

2
resources/test/fixtures/F525.py vendored Normal file
View File

@@ -0,0 +1,2 @@
"{} {1}".format(1, 2) # F525
"{0} {}".format(1, 2) # F523, F525

View File

@@ -12,3 +12,13 @@ x: dict["key", "value"]
# OK
x: dict[str, str]
# OK
def unimportant(name):
pass
def dang(dict, set, list):
unimportant(name=dict["name"])
unimportant(name=set["name"])
unimportant(name=list["name"])

View File

@@ -1,3 +1,3 @@
# coding=utf8
print('Hello world')
print("Hello world")

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

@@ -0,0 +1,3 @@
# coding=utf8 # noqa: U009
print("Hello world")

View File

@@ -36,3 +36,35 @@ with open("foo", "U") as fa, open("bar", "U") as fb:
pass
with open("foo", "Ub") as fa, open("bar", "Ub") as fb:
pass
open("foo", mode="U")
open(name="foo", mode="U")
open(mode="U", name="foo")
with open("foo", mode="U") as f:
pass
with open(name="foo", mode="U") as f:
pass
with open(mode="U", name="foo") as f:
pass
open("foo", mode="Ub")
open(name="foo", mode="Ub")
open(mode="Ub", name="foo")
with open("foo", mode="Ub") as f:
pass
with open(name="foo", mode="Ub") as f:
pass
with open(mode="Ub", name="foo") as f:
pass
open(file="foo", mode='U', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U')
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None)
open(mode='U', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open(file="foo", mode='Ub', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub')
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None)
open(mode='Ub', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

View File

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

View File

@@ -117,7 +117,8 @@ pub fn main(cli: &Cli) -> Result<()> {
// Construct the output contents.
let mut output = String::new();
output.push_str("//! File automatically generated by examples/generate_check_code_prefix.rs.");
output
.push_str("//! File automatically generated by `examples/generate_check_code_prefix.rs`.");
output.push('\n');
output.push('\n');
output.push_str("use serde::{{Serialize, Deserialize}};");
@@ -129,6 +130,21 @@ pub fn main(cli: &Cli) -> Result<()> {
output.push('\n');
output.push('\n');
output.push_str(&scope.to_string());
output.push('\n');
output.push('\n');
// Add the list of output categories (not generated).
output.push_str("pub const CATEGORIES: &[CheckCodePrefix] = &[");
output.push('\n');
for prefix in prefix_to_codes.keys() {
if prefix.chars().all(char::is_alphabetic) {
output.push_str(&format!("CheckCodePrefix::{prefix},"));
output.push('\n');
}
}
output.push_str("];");
output.push('\n');
output.push('\n');
// Write the output to `src/checks_gen.rs` (or stdout).
if cli.dry_run {

View File

@@ -1,7 +1,9 @@
use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind};
use rustpython_ast::{
Arguments, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind,
};
use crate::ast::types::Range;
use crate::SourceCodeLocator;
@@ -213,6 +215,27 @@ pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<Vec<&str>> {
handler_names
}
/// Return the set of all bound argument names.
pub fn collect_arg_names<'a>(arguments: &'a Arguments) -> FxHashSet<&'a str> {
let mut arg_names: FxHashSet<&'a str> = FxHashSet::default();
for arg in &arguments.posonlyargs {
arg_names.insert(arg.node.arg.as_str());
}
for arg in &arguments.args {
arg_names.insert(arg.node.arg.as_str());
}
if let Some(arg) = &arguments.vararg {
arg_names.insert(arg.node.arg.as_str());
}
for arg in &arguments.kwonlyargs {
arg_names.insert(arg.node.arg.as_str());
}
if let Some(arg) = &arguments.kwarg {
arg_names.insert(arg.node.arg.as_str());
}
arg_names
}
/// Returns `true` if a call is an argumented `super` invocation.
pub fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
// Check: is this a `super` call?

View File

@@ -1,7 +1,7 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use rustc_hash::FxHashMap;
use rustpython_ast::{Expr, Keyword};
use rustpython_ast::{Expr, Keyword, Stmt};
use rustpython_parser::ast::{Located, Location};
fn id() -> usize {
@@ -9,6 +9,12 @@ fn id() -> usize {
COUNTER.fetch_add(1, Ordering::Relaxed)
}
#[derive(Clone)]
pub enum Node<'a> {
Stmt(&'a Stmt),
Expr(&'a Expr),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Range {
pub location: Location,

View File

@@ -19,8 +19,8 @@ use crate::ast::helpers::{
use crate::ast::operations::extract_all_names;
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, ImportKind, Range, Scope,
ScopeKind,
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, ImportKind, Node, Range,
Scope, ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, walk_withitem, Visitor};
use crate::ast::{helpers, operations, visitor};
@@ -83,6 +83,8 @@ pub struct Checker<'a> {
futures_allowed: bool,
annotations_future_enabled: bool,
except_handlers: Vec<Vec<Vec<&'a str>>>,
// Check-specific state.
pub(crate) seen_b023: Vec<&'a Expr>,
}
impl<'a> Checker<'a> {
@@ -127,6 +129,8 @@ impl<'a> Checker<'a> {
futures_allowed: true,
annotations_future_enabled: false,
except_handlers: vec![],
// Check-specific state.
seen_b023: vec![],
}
}
@@ -173,6 +177,15 @@ impl<'a> Checker<'a> {
|| (typing::in_extensions(target)
&& match_call_path(call_path, "typing_extensions", target, &self.from_imports))
}
/// Return `true` if `member` is bound as a builtin.
pub fn is_builtin(&self, member: &str) -> bool {
self.current_scopes()
.find_map(|scope| scope.values.get(member))
.map_or(false, |binding| {
matches!(binding.kind, BindingKind::Builtin)
})
}
}
impl<'a, 'b> Visitor<'b> for Checker<'a>
@@ -951,8 +964,16 @@ where
flake8_bugbear::plugins::assert_raises_exception(self, stmt, items);
}
}
StmtKind::While { .. } => {
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Stmt(stmt));
}
}
StmtKind::For {
target, body, iter, ..
}
| StmtKind::AsyncFor {
target, body, iter, ..
} => {
if self.settings.enabled.contains(&CheckCode::B007) {
flake8_bugbear::plugins::unused_loop_control_variable(self, target, body);
@@ -960,6 +981,9 @@ where
if self.settings.enabled.contains(&CheckCode::B020) {
flake8_bugbear::plugins::loop_variable_overrides_iterator(self, target, iter);
}
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Stmt(stmt));
}
}
StmtKind::Try { handlers, .. } => {
if self.settings.enabled.contains(&CheckCode::F707) {
@@ -1244,20 +1268,73 @@ where
keywords,
} => {
// pyflakes
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &value.node
{
if attr == "format" {
// "...".format(...) call
if self.settings.enabled.contains(&CheckCode::F521) {
if self.settings.enabled.contains(&CheckCode::F521)
|| self.settings.enabled.contains(&CheckCode::F522)
|| self.settings.enabled.contains(&CheckCode::F523)
|| self.settings.enabled.contains(&CheckCode::F524)
|| self.settings.enabled.contains(&CheckCode::F525)
{
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &value.node
{
if attr == "format" {
// "...".format(...) call
let location = Range::from_located(expr);
if let Some(check) =
pyflakes::checks::string_dot_format_invalid(value, location)
{
self.add_check(check);
match pyflakes::format::FormatSummary::try_from(value.as_ref()) {
Err(e) => {
if self.settings.enabled.contains(&CheckCode::F521) {
self.add_check(Check::new(
CheckKind::StringDotFormatInvalidFormat(
e.to_string(),
),
location,
));
}
}
Ok(summary) => {
if self.settings.enabled.contains(&CheckCode::F522) {
if let Some(check) =
pyflakes::checks::string_dot_format_extra_named_arguments(
&summary, keywords, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F523) {
if let Some(check) =
pyflakes::checks::string_dot_format_extra_positional_arguments(
&summary, args, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F524) {
if let Some(check) =
pyflakes::checks::string_dot_format_missing_argument(
&summary, args, keywords, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F525) {
if let Some(check) =
pyflakes::checks::string_dot_format_mixing_automatic(
&summary, location,
)
{
self.add_check(check);
}
}
}
}
}
}
@@ -1757,9 +1834,15 @@ where
self.add_check(check);
};
}
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Expr(expr));
}
self.push_scope(Scope::new(ScopeKind::Generator));
}
ExprKind::GeneratorExp { .. } | ExprKind::DictComp { .. } => {
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Expr(expr));
}
self.push_scope(Scope::new(ScopeKind::Generator));
}
_ => {}
@@ -1890,6 +1973,7 @@ where
value,
&self.from_imports,
&self.import_aliases,
|member| self.is_builtin(member),
) {
Some(subscript) => {
match subscript {

View File

@@ -38,6 +38,7 @@ pub fn check_lines(
noqa_line_for: &IntMap<usize, usize>,
settings: &Settings,
autofix: bool,
ignore_noqa: bool,
) {
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::U009);
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
@@ -53,6 +54,30 @@ pub fn check_lines(
assert!(check.location.row() >= 1);
}
macro_rules! add_if {
($check:expr, $noqa:expr) => {{
match $noqa {
(Directive::All(..), matches) => {
matches.push($check.kind.code().as_ref());
if ignore_noqa {
line_checks.push($check);
}
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&$check.kind.code().as_ref()) {
matches.push($check.kind.code().as_ref());
if ignore_noqa {
line_checks.push($check);
}
} else {
line_checks.push($check);
}
}
(Directive::None, ..) => line_checks.push($check),
}
}};
}
let lines: Vec<&str> = contents.lines().collect();
for (lineno, line) in lines.iter().enumerate() {
// Grab the noqa (logical) line number for the current (physical) line.
@@ -65,21 +90,24 @@ pub fn check_lines(
if lineno < 2 {
// PEP3120 makes utf-8 the default encoding.
if CODING_COMMENT_REGEX.is_match(line) {
let line_length = line.len();
let mut check = Check::new(
CheckKind::PEP3120UnnecessaryCodingComment,
Range {
location: Location::new(lineno + 1, 0),
end_location: Location::new(lineno + 1, line_length + 1),
end_location: Location::new(lineno + 2, 0),
},
);
if autofix && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::deletion(
Location::new(lineno + 1, 0),
Location::new(lineno + 1, line_length + 1),
Location::new(lineno + 2, 0),
));
}
line_checks.push(check);
let noqa = noqa_directives.entry(noqa_lineno).or_insert_with(|| {
(noqa::extract_noqa_directive(lines[noqa_lineno]), vec![])
});
add_if!(check, noqa);
}
}
}
@@ -109,7 +137,7 @@ pub fn check_lines(
ignored.push(index);
}
}
(Directive::None, _) => {}
(Directive::None, ..) => {}
}
}
@@ -117,10 +145,6 @@ pub fn check_lines(
if enforce_line_too_long {
let line_length = line.chars().count();
if should_enforce_line_length(line, line_length, settings.line_length) {
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
let check = Check::new(
CheckKind::LineTooLong(line_length, settings.line_length),
Range {
@@ -129,35 +153,19 @@ pub fn check_lines(
},
);
match noqa {
(Directive::All(..), matches) => {
matches.push(check.kind.code().as_ref());
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&check.kind.code().as_ref()) {
matches.push(check.kind.code().as_ref());
} else {
line_checks.push(check);
}
}
(Directive::None, _) => line_checks.push(check),
}
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
add_if!(check, noqa);
}
}
}
// Enforce newlines at end of files.
// Enforce newlines at end of files (W292).
if settings.enabled.contains(&CheckCode::W292) && !contents.ends_with('\n') {
// Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't
// want to raise W292 anyway).
if let Some(line) = lines.last() {
let lineno = lines.len() - 1;
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
let check = Check::new(
CheckKind::NoNewLineAtEndOfFile,
Range {
@@ -166,23 +174,16 @@ pub fn check_lines(
},
);
match noqa {
(Directive::All(..), matches) => {
matches.push(check.kind.code().as_ref());
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&check.kind.code().as_ref()) {
matches.push(check.kind.code().as_ref());
} else {
line_checks.push(check);
}
}
(Directive::None, _) => line_checks.push(check),
}
let lineno = lines.len() - 1;
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
add_if!(check, noqa);
}
}
// Enforce that the noqa directive was actually used.
// Enforce that the noqa directive was actually used (M001).
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {
match directive {
@@ -245,9 +246,11 @@ pub fn check_lines(
}
}
ignored.sort_unstable();
for index in ignored.iter().rev() {
checks.swap_remove(*index);
if !ignore_noqa {
ignored.sort_unstable();
for index in ignored.iter().rev() {
checks.swap_remove(*index);
}
}
checks.extend(line_checks);
}
@@ -275,6 +278,7 @@ mod tests {
..Settings::for_rule(CheckCode::E501)
},
true,
false,
);
checks
};

View File

@@ -53,6 +53,10 @@ pub enum CheckCode {
F406,
F407,
F521,
F522,
F523,
F524,
F525,
F541,
F601,
F602,
@@ -100,6 +104,7 @@ pub enum CheckCode {
B020,
B021,
B022,
B023,
B024,
B025,
B026,
@@ -404,7 +409,11 @@ pub enum CheckKind {
MultiValueRepeatedKeyVariable(String),
RaiseNotImplemented,
ReturnOutsideFunction,
StringDotFormatExtraNamedArguments(Vec<String>),
StringDotFormatExtraPositionalArguments(Vec<String>),
StringDotFormatInvalidFormat(String),
StringDotFormatMissingArguments(Vec<String>),
StringDotFormatMixingAutomatic,
TwoStarredExpressions,
UndefinedExport(String),
UndefinedLocal(String),
@@ -419,32 +428,33 @@ pub enum CheckKind {
// flake8-blind-except
BlindExcept,
// flake8-bugbear
UnaryPrefixIncrement,
AssignmentToOsEnviron,
UnreliableCallableCheck,
StripWithMultiCharacters,
MutableArgumentDefault,
UnusedLoopControlVariable(String),
FunctionCallArgumentDefault(Option<String>),
GetAttrWithConstant,
SetAttrWithConstant,
DoNotAssertFalse,
JumpStatementInFinally(String),
RedundantTupleInExceptionHandler(String),
DuplicateHandlerException(Vec<String>),
UselessComparison,
CannotRaiseLiteral,
NoAssertRaisesException,
UselessExpression,
CachedInstanceMethod,
LoopVariableOverridesIterator(String),
FStringDocstring,
UselessContextlibSuppress,
AbstractBaseClassWithoutAbstractMethod(String),
AssignmentToOsEnviron,
CachedInstanceMethod,
CannotRaiseLiteral,
DoNotAssertFalse,
DuplicateHandlerException(Vec<String>),
DuplicateTryBlockException(String),
StarArgUnpackingAfterKeywordArg,
EmptyMethodWithoutAbstractDecorator(String),
FStringDocstring,
FunctionCallArgumentDefault(Option<String>),
FunctionUsesLoopVariable(String),
GetAttrWithConstant,
JumpStatementInFinally(String),
LoopVariableOverridesIterator(String),
MutableArgumentDefault,
NoAssertRaisesException,
RaiseWithoutFromInsideExcept,
RedundantTupleInExceptionHandler(String),
SetAttrWithConstant,
StarArgUnpackingAfterKeywordArg,
StripWithMultiCharacters,
UnaryPrefixIncrement,
UnreliableCallableCheck,
UnusedLoopControlVariable(String),
UselessComparison,
UselessContextlibSuppress,
UselessExpression,
// flake8-comprehensions
UnnecessaryGeneratorList,
UnnecessaryGeneratorSet,
@@ -649,6 +659,14 @@ impl CheckCode {
CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()),
CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()),
CheckCode::F521 => CheckKind::StringDotFormatInvalidFormat("...".to_string()),
CheckCode::F522 => {
CheckKind::StringDotFormatExtraNamedArguments(vec!["...".to_string()])
}
CheckCode::F523 => {
CheckKind::StringDotFormatExtraPositionalArguments(vec!["...".to_string()])
}
CheckCode::F524 => CheckKind::StringDotFormatMissingArguments(vec!["...".to_string()]),
CheckCode::F525 => CheckKind::StringDotFormatMixingAutomatic,
CheckCode::F541 => CheckKind::FStringMissingPlaceholders,
CheckCode::F601 => CheckKind::MultiValueRepeatedKeyLiteral,
CheckCode::F602 => CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
@@ -700,6 +718,7 @@ impl CheckCode {
CheckCode::B020 => CheckKind::LoopVariableOverridesIterator("...".to_string()),
CheckCode::B021 => CheckKind::FStringDocstring,
CheckCode::B022 => CheckKind::UselessContextlibSuppress,
CheckCode::B023 => CheckKind::FunctionUsesLoopVariable("...".to_string()),
CheckCode::B024 => CheckKind::AbstractBaseClassWithoutAbstractMethod("...".to_string()),
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg,
@@ -916,6 +935,10 @@ impl CheckCode {
CheckCode::F406 => CheckCategory::Pyflakes,
CheckCode::F407 => CheckCategory::Pyflakes,
CheckCode::F521 => CheckCategory::Pyflakes,
CheckCode::F522 => CheckCategory::Pyflakes,
CheckCode::F523 => CheckCategory::Pyflakes,
CheckCode::F524 => CheckCategory::Pyflakes,
CheckCode::F525 => CheckCategory::Pyflakes,
CheckCode::F541 => CheckCategory::Pyflakes,
CheckCode::F601 => CheckCategory::Pyflakes,
CheckCode::F602 => CheckCategory::Pyflakes,
@@ -961,6 +984,7 @@ impl CheckCode {
CheckCode::B020 => CheckCategory::Flake8Bugbear,
CheckCode::B021 => CheckCategory::Flake8Bugbear,
CheckCode::B022 => CheckCategory::Flake8Bugbear,
CheckCode::B023 => CheckCategory::Flake8Bugbear,
CheckCode::B024 => CheckCategory::Flake8Bugbear,
CheckCode::B025 => CheckCategory::Flake8Bugbear,
CheckCode::B026 => CheckCategory::Flake8Bugbear,
@@ -1140,7 +1164,11 @@ impl CheckKind {
CheckKind::NotIsTest => &CheckCode::E714,
CheckKind::RaiseNotImplemented => &CheckCode::F901,
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
CheckKind::StringDotFormatExtraNamedArguments(_) => &CheckCode::F522,
CheckKind::StringDotFormatExtraPositionalArguments(_) => &CheckCode::F523,
CheckKind::StringDotFormatInvalidFormat(_) => &CheckCode::F521,
CheckKind::StringDotFormatMissingArguments(_) => &CheckCode::F524,
CheckKind::StringDotFormatMixingAutomatic => &CheckCode::F525,
CheckKind::SyntaxError(_) => &CheckCode::E999,
CheckKind::ExpressionsInStarAssignment => &CheckCode::F621,
CheckKind::TrueFalseComparison(..) => &CheckCode::E712,
@@ -1160,32 +1188,33 @@ impl CheckKind {
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
// flake8-bugbear
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
CheckKind::UnreliableCallableCheck => &CheckCode::B004,
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
CheckKind::MutableArgumentDefault => &CheckCode::B006,
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
CheckKind::FunctionCallArgumentDefault(_) => &CheckCode::B008,
CheckKind::GetAttrWithConstant => &CheckCode::B009,
CheckKind::SetAttrWithConstant => &CheckCode::B010,
CheckKind::DoNotAssertFalse => &CheckCode::B011,
CheckKind::JumpStatementInFinally(_) => &CheckCode::B012,
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
CheckKind::UselessComparison => &CheckCode::B015,
CheckKind::CannotRaiseLiteral => &CheckCode::B016,
CheckKind::NoAssertRaisesException => &CheckCode::B017,
CheckKind::UselessExpression => &CheckCode::B018,
CheckKind::CachedInstanceMethod => &CheckCode::B019,
CheckKind::LoopVariableOverridesIterator(_) => &CheckCode::B020,
CheckKind::FStringDocstring => &CheckCode::B021,
CheckKind::UselessContextlibSuppress => &CheckCode::B022,
CheckKind::AbstractBaseClassWithoutAbstractMethod(_) => &CheckCode::B024,
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
CheckKind::CachedInstanceMethod => &CheckCode::B019,
CheckKind::CannotRaiseLiteral => &CheckCode::B016,
CheckKind::DoNotAssertFalse => &CheckCode::B011,
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
CheckKind::EmptyMethodWithoutAbstractDecorator(_) => &CheckCode::B027,
CheckKind::FStringDocstring => &CheckCode::B021,
CheckKind::FunctionCallArgumentDefault(_) => &CheckCode::B008,
CheckKind::FunctionUsesLoopVariable(_) => &CheckCode::B023,
CheckKind::GetAttrWithConstant => &CheckCode::B009,
CheckKind::JumpStatementInFinally(_) => &CheckCode::B012,
CheckKind::LoopVariableOverridesIterator(_) => &CheckCode::B020,
CheckKind::MutableArgumentDefault => &CheckCode::B006,
CheckKind::NoAssertRaisesException => &CheckCode::B017,
CheckKind::RaiseWithoutFromInsideExcept => &CheckCode::B904,
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
CheckKind::SetAttrWithConstant => &CheckCode::B010,
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
CheckKind::UnreliableCallableCheck => &CheckCode::B004,
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
CheckKind::UselessComparison => &CheckCode::B015,
CheckKind::UselessContextlibSuppress => &CheckCode::B022,
CheckKind::UselessExpression => &CheckCode::B018,
// flake8-blind-except
CheckKind::BlindExcept => &CheckCode::BLE001,
// flake8-comprehensions
@@ -1427,9 +1456,24 @@ impl CheckKind {
CheckKind::ReturnOutsideFunction => {
"`return` statement outside of a function/method".to_string()
}
CheckKind::StringDotFormatExtraNamedArguments(missing) => {
let message = missing.join(", ");
format!("'...'.format(...) has unused named argument(s): {message}")
}
CheckKind::StringDotFormatExtraPositionalArguments(missing) => {
let message = missing.join(", ");
format!("'...'.format(...) has unused arguments at position(s): {message}")
}
CheckKind::StringDotFormatInvalidFormat(message) => {
format!("'...'.format(...) has invalid format string: {message}")
}
CheckKind::StringDotFormatMissingArguments(missing) => {
let message = missing.join(", ");
format!("'...'.format(...) is missing argument(s) for placeholder(s): {message}")
}
CheckKind::StringDotFormatMixingAutomatic => {
"'...'.format(...) mixes automatic and manual numbering".to_string()
}
CheckKind::SyntaxError(message) => format!("SyntaxError: {message}"),
CheckKind::ExpressionsInStarAssignment => {
"Too many expressions in star-unpacking assignment".to_string()
@@ -1515,6 +1559,9 @@ impl CheckKind {
"Do not perform function call in argument defaults".to_string()
}
}
CheckKind::FunctionUsesLoopVariable(name) => {
format!("Function definition does not bind loop variable `{name}`")
}
CheckKind::GetAttrWithConstant => "Do not call `getattr` with a constant attribute \
value. It is not any safer than normal property \
access."

View File

@@ -59,6 +59,7 @@ pub enum CheckCodePrefix {
B020,
B021,
B022,
B023,
B024,
B025,
B026,
@@ -186,6 +187,12 @@ pub enum CheckCodePrefix {
F406,
F407,
F5,
F52,
F521,
F522,
F523,
F524,
F525,
F54,
F541,
F6,
@@ -415,6 +422,7 @@ impl CheckCodePrefix {
CheckCode::B020,
CheckCode::B021,
CheckCode::B022,
CheckCode::B023,
CheckCode::B024,
CheckCode::B025,
CheckCode::B026,
@@ -443,6 +451,7 @@ impl CheckCodePrefix {
CheckCode::B020,
CheckCode::B021,
CheckCode::B022,
CheckCode::B023,
CheckCode::B024,
CheckCode::B025,
CheckCode::B026,
@@ -492,6 +501,7 @@ impl CheckCodePrefix {
CheckCode::B020,
CheckCode::B021,
CheckCode::B022,
CheckCode::B023,
CheckCode::B024,
CheckCode::B025,
CheckCode::B026,
@@ -500,6 +510,7 @@ impl CheckCodePrefix {
CheckCodePrefix::B020 => vec![CheckCode::B020],
CheckCodePrefix::B021 => vec![CheckCode::B021],
CheckCodePrefix::B022 => vec![CheckCode::B022],
CheckCodePrefix::B023 => vec![CheckCode::B023],
CheckCodePrefix::B024 => vec![CheckCode::B024],
CheckCodePrefix::B025 => vec![CheckCode::B025],
CheckCodePrefix::B026 => vec![CheckCode::B026],
@@ -848,6 +859,10 @@ impl CheckCodePrefix {
CheckCode::F406,
CheckCode::F407,
CheckCode::F521,
CheckCode::F522,
CheckCode::F523,
CheckCode::F524,
CheckCode::F525,
CheckCode::F541,
CheckCode::F601,
CheckCode::F602,
@@ -895,7 +910,26 @@ impl CheckCodePrefix {
CheckCodePrefix::F405 => vec![CheckCode::F405],
CheckCodePrefix::F406 => vec![CheckCode::F406],
CheckCodePrefix::F407 => vec![CheckCode::F407],
CheckCodePrefix::F5 => vec![CheckCode::F541],
CheckCodePrefix::F5 => vec![
CheckCode::F521,
CheckCode::F522,
CheckCode::F523,
CheckCode::F524,
CheckCode::F525,
CheckCode::F541,
],
CheckCodePrefix::F52 => vec![
CheckCode::F521,
CheckCode::F522,
CheckCode::F523,
CheckCode::F524,
CheckCode::F525,
],
CheckCodePrefix::F521 => vec![CheckCode::F521],
CheckCodePrefix::F522 => vec![CheckCode::F522],
CheckCodePrefix::F523 => vec![CheckCode::F523],
CheckCodePrefix::F524 => vec![CheckCode::F524],
CheckCodePrefix::F525 => vec![CheckCode::F525],
CheckCodePrefix::F54 => vec![CheckCode::F541],
CheckCodePrefix::F541 => vec![CheckCode::F541],
CheckCodePrefix::F6 => vec![
@@ -1289,6 +1323,7 @@ impl CheckCodePrefix {
CheckCodePrefix::B020 => PrefixSpecificity::Explicit,
CheckCodePrefix::B021 => PrefixSpecificity::Explicit,
CheckCodePrefix::B022 => PrefixSpecificity::Explicit,
CheckCodePrefix::B023 => PrefixSpecificity::Explicit,
CheckCodePrefix::B024 => PrefixSpecificity::Explicit,
CheckCodePrefix::B025 => PrefixSpecificity::Explicit,
CheckCodePrefix::B026 => PrefixSpecificity::Explicit,
@@ -1416,6 +1451,12 @@ impl CheckCodePrefix {
CheckCodePrefix::F406 => PrefixSpecificity::Explicit,
CheckCodePrefix::F407 => PrefixSpecificity::Explicit,
CheckCodePrefix::F5 => PrefixSpecificity::Hundreds,
CheckCodePrefix::F52 => PrefixSpecificity::Tens,
CheckCodePrefix::F521 => PrefixSpecificity::Explicit,
CheckCodePrefix::F522 => PrefixSpecificity::Explicit,
CheckCodePrefix::F523 => PrefixSpecificity::Explicit,
CheckCodePrefix::F524 => PrefixSpecificity::Explicit,
CheckCodePrefix::F525 => PrefixSpecificity::Explicit,
CheckCodePrefix::F54 => PrefixSpecificity::Tens,
CheckCodePrefix::F541 => PrefixSpecificity::Explicit,
CheckCodePrefix::F6 => PrefixSpecificity::Hundreds,
@@ -1562,3 +1603,25 @@ impl CheckCodePrefix {
}
}
}
pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::A,
CheckCodePrefix::ANN,
CheckCodePrefix::B,
CheckCodePrefix::BLE,
CheckCodePrefix::C,
CheckCodePrefix::D,
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::FBT,
CheckCodePrefix::I,
CheckCodePrefix::M,
CheckCodePrefix::N,
CheckCodePrefix::Q,
CheckCodePrefix::RUF,
CheckCodePrefix::S,
CheckCodePrefix::T,
CheckCodePrefix::U,
CheckCodePrefix::W,
CheckCodePrefix::YTT,
];

View File

@@ -21,7 +21,7 @@ where
fn visit_stmt(&mut self, stmt: &'b Stmt) {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// No recurse.
// Don't recurse.
}
StmtKind::Return { value } => self.returns.push(value.as_ref().map(|expr| &**expr)),
_ => visitor::walk_stmt(self, stmt),

View File

@@ -0,0 +1,232 @@
use rustc_hash::FxHashSet;
use rustpython_ast::{Comprehension, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use crate::ast::helpers::collect_arg_names;
use crate::ast::types::{Node, Range};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
#[derive(Default)]
struct LoadedNamesVisitor<'a> {
// Tuple of: name, defining expression, and defining range.
names: Vec<(&'a str, &'a Expr, Range)>,
// If we're in an f-string, the range of the defining expression.
in_f_string: Option<Range>,
}
/// `Visitor` to collect all used identifiers in a statement.
impl<'a, 'b> Visitor<'b> for LoadedNamesVisitor<'a>
where
'b: 'a,
{
fn visit_expr(&mut self, expr: &'b Expr) {
match &expr.node {
ExprKind::JoinedStr { .. } => {
let prev_in_f_string = self.in_f_string;
self.in_f_string = Some(Range::from_located(expr));
visitor::walk_expr(self, expr);
self.in_f_string = prev_in_f_string;
}
ExprKind::Name { id, ctx } if matches!(ctx, ExprContext::Load) => {
self.names.push((
id,
expr,
self.in_f_string
.unwrap_or_else(|| Range::from_located(expr)),
));
}
_ => visitor::walk_expr(self, expr),
}
}
}
#[derive(Default)]
struct SuspiciousVariablesVisitor<'a> {
names: Vec<(&'a str, &'a Expr, Range)>,
}
/// `Visitor` to collect all suspicious variables (those referenced in
/// functions, but not bound as arguments).
impl<'a, 'b> Visitor<'b> for SuspiciousVariablesVisitor<'a>
where
'b: 'a,
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
match &stmt.node {
StmtKind::FunctionDef { args, body, .. }
| StmtKind::AsyncFunctionDef { args, body, .. } => {
// Collect all loaded variable names.
let mut visitor = LoadedNamesVisitor::default();
for stmt in body {
visitor.visit_stmt(stmt);
}
// Collect all argument names.
let arg_names = collect_arg_names(args);
// Treat any non-arguments as "suspicious".
self.names.extend(
visitor
.names
.into_iter()
.filter(|(id, ..)| !arg_names.contains(id)),
);
}
_ => visitor::walk_stmt(self, stmt),
}
}
fn visit_expr(&mut self, expr: &'b Expr) {
match &expr.node {
ExprKind::Lambda { args, body } => {
// Collect all loaded variable names.
let mut visitor = LoadedNamesVisitor::default();
visitor.visit_expr(body);
// Collect all argument names.
let arg_names = collect_arg_names(args);
// Treat any non-arguments as "suspicious".
self.names.extend(
visitor
.names
.into_iter()
.filter(|(id, ..)| !arg_names.contains(id)),
);
}
_ => visitor::walk_expr(self, expr),
}
}
}
#[derive(Default)]
struct NamesFromAssignmentsVisitor<'a> {
names: FxHashSet<&'a str>,
}
/// `Visitor` to collect all names used in an assignment expression.
impl<'a, 'b> Visitor<'b> for NamesFromAssignmentsVisitor<'a>
where
'b: 'a,
{
fn visit_expr(&mut self, expr: &'b Expr) {
match &expr.node {
ExprKind::Name { id, .. } => {
self.names.insert(id.as_str());
}
ExprKind::Starred { value, .. } => {
self.visit_expr(value);
}
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
for expr in elts {
self.visit_expr(expr);
}
}
_ => {}
}
}
}
#[derive(Default)]
struct AssignedNamesVisitor<'a> {
names: FxHashSet<&'a str>,
}
/// `Visitor` to collect all used identifiers in a statement.
impl<'a, 'b> Visitor<'b> for AssignedNamesVisitor<'a>
where
'b: 'a,
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
if matches!(
&stmt.node,
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. }
) {
// Don't recurse.
return;
}
match &stmt.node {
StmtKind::Assign { targets, .. } => {
let mut visitor = NamesFromAssignmentsVisitor::default();
for expr in targets {
visitor.visit_expr(expr);
}
self.names.extend(visitor.names);
}
StmtKind::AugAssign { target, .. }
| StmtKind::AnnAssign { target, .. }
| StmtKind::For { target, .. }
| StmtKind::AsyncFor { target, .. } => {
let mut visitor = NamesFromAssignmentsVisitor::default();
visitor.visit_expr(target);
self.names.extend(visitor.names);
}
_ => {}
}
visitor::walk_stmt(self, stmt);
}
fn visit_expr(&mut self, expr: &'b Expr) {
if matches!(&expr.node, ExprKind::Lambda { .. }) {
// Don't recurse.
return;
}
visitor::walk_expr(self, expr);
}
fn visit_comprehension(&mut self, comprehension: &'b Comprehension) {
let mut visitor = NamesFromAssignmentsVisitor::default();
visitor.visit_expr(&comprehension.target);
self.names.extend(visitor.names);
visitor::walk_comprehension(self, comprehension);
}
}
/// B023
pub fn function_uses_loop_variable<'a, 'b>(checker: &'a mut Checker<'b>, node: &Node<'b>)
where
'b: 'a,
{
// Identify any "suspicious" variables. These are defined as variables that are
// referenced in a function or lambda body, but aren't bound as arguments.
let suspicious_variables = {
let mut visitor = SuspiciousVariablesVisitor::<'b>::default();
match node {
Node::Stmt(stmt) => visitor.visit_stmt(stmt),
Node::Expr(expr) => visitor.visit_expr(expr),
}
visitor.names
};
if !suspicious_variables.is_empty() {
// Identify any variables that are assigned in the loop (ignoring functions).
let reassigned_in_loop = {
let mut visitor = AssignedNamesVisitor::<'b>::default();
match node {
Node::Stmt(stmt) => visitor.visit_stmt(stmt),
Node::Expr(expr) => visitor.visit_expr(expr),
}
visitor.names
};
// If a variable was used in a function or lambda body, and assigned in the
// loop, flag it.
for (name, expr, range) in suspicious_variables {
if reassigned_in_loop.contains(name) {
if !checker.seen_b023.contains(&expr) {
checker.seen_b023.push(expr);
checker.add_check(Check::new(
CheckKind::FunctionUsesLoopVariable(name.to_string()),
range,
));
}
}
}
}
}

View File

@@ -7,6 +7,7 @@ pub use cannot_raise_literal::cannot_raise_literal;
pub use duplicate_exceptions::duplicate_exceptions;
pub use f_string_docstring::f_string_docstring;
pub use function_call_argument_default::function_call_argument_default;
pub use function_uses_loop_variable::function_uses_loop_variable;
pub use getattr_with_constant::getattr_with_constant;
pub use jump_statement_in_finally::jump_statement_in_finally;
pub use loop_variable_overrides_iterator::loop_variable_overrides_iterator;
@@ -32,6 +33,7 @@ mod cannot_raise_literal;
mod duplicate_exceptions;
mod f_string_docstring;
mod function_call_argument_default;
mod function_uses_loop_variable;
mod getattr_with_constant;
mod jump_statement_in_finally;
mod loop_variable_overrides_iterator;

View File

@@ -115,6 +115,7 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
&directives,
&settings,
autofix,
false,
)?;
Ok(checks)

View File

@@ -54,6 +54,7 @@ pub(crate) fn check_path(
directives: &Directives,
settings: &Settings,
autofix: bool,
ignore_noqa: bool,
) -> Result<Vec<Check>> {
// Aggregate all checks.
let mut checks: Vec<Check> = vec![];
@@ -113,6 +114,7 @@ pub(crate) fn check_path(
&directives.noqa_line_for,
settings,
autofix,
ignore_noqa,
);
// Create path ignores.
@@ -179,6 +181,7 @@ pub fn lint_path(
&directives,
settings,
autofix.into(),
false,
)?;
// Apply autofix.
@@ -242,15 +245,19 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
directives::Flags::from_settings(settings),
);
// Generate checks.
// Generate checks, ignoring any existing `noqa` directives.
let checks = check_path(
path,
&contents,
tokens,
&locator,
&directives,
&Directives {
noqa_line_for: Default::default(),
isort_exclusions: directives.isort_exclusions,
},
settings,
false,
true,
)?;
add_noqa(&checks, &contents, &directives.noqa_line_for, path)
@@ -313,6 +320,7 @@ pub fn lint_stdin(
&directives,
settings,
autofix.into(),
false,
)?;
// Apply autofix.
@@ -373,6 +381,7 @@ pub fn test_path(path: &Path, settings: &Settings, autofix: bool) -> Result<Vec<
&directives,
settings,
autofix,
false,
)
}
@@ -413,6 +422,7 @@ mod tests {
#[test_case(CheckCode::B020, Path::new("B020.py"); "B020")]
#[test_case(CheckCode::B021, Path::new("B021.py"); "B021")]
#[test_case(CheckCode::B022, Path::new("B022.py"); "B022")]
#[test_case(CheckCode::B023, Path::new("B023.py"); "B023")]
#[test_case(CheckCode::B024, Path::new("B024.py"); "B024")]
#[test_case(CheckCode::B025, Path::new("B025.py"); "B025")]
#[test_case(CheckCode::B026, Path::new("B026.py"); "B026")]
@@ -507,6 +517,10 @@ mod tests {
#[test_case(CheckCode::F406, Path::new("F406.py"); "F406")]
#[test_case(CheckCode::F407, Path::new("F407.py"); "F407")]
#[test_case(CheckCode::F521, Path::new("F521.py"); "F521")]
#[test_case(CheckCode::F522, Path::new("F522.py"); "F522")]
#[test_case(CheckCode::F523, Path::new("F523.py"); "F523")]
#[test_case(CheckCode::F524, Path::new("F524.py"); "F524")]
#[test_case(CheckCode::F525, Path::new("F525.py"); "F525")]
#[test_case(CheckCode::F541, Path::new("F541.py"); "F541")]
#[test_case(CheckCode::F601, Path::new("F601.py"); "F601")]
#[test_case(CheckCode::F602, Path::new("F602.py"); "F602")]
@@ -566,6 +580,7 @@ mod tests {
#[test_case(CheckCode::U009, Path::new("U009_1.py"); "U009_1")]
#[test_case(CheckCode::U009, Path::new("U009_2.py"); "U009_2")]
#[test_case(CheckCode::U009, Path::new("U009_3.py"); "U009_3")]
#[test_case(CheckCode::U009, Path::new("U009_4.py"); "U009_4")]
#[test_case(CheckCode::U010, Path::new("U010.py"); "U010")]
#[test_case(CheckCode::U011, Path::new("U011_0.py"); "U011_0")]
#[test_case(CheckCode::U011, Path::new("U011_1.py"); "U011_1")]

View File

@@ -99,18 +99,42 @@ fn add_noqa_inner(
Some(codes) => {
match extract_noqa_directive(line) {
Directive::None => {
output.push_str(line);
// Add existing content.
output.push_str(line.trim_end());
// Add `noqa` directive.
output.push_str(" # noqa: ");
// Add codes.
let codes: Vec<&str> = codes.iter().map(AsRef::as_ref).collect();
let suffix = codes.join(", ");
output.push_str(&suffix);
output.push('\n');
count += 1;
}
Directive::All(_, start, _) | Directive::Codes(_, start, ..) => {
output.push_str(&line[..start]);
output.push_str("# noqa: ");
let mut new_line = String::new();
// Add existing content.
new_line.push_str(&line[..start].trim_end());
// Add `noqa` directive.
new_line.push_str(" # noqa: ");
// Add codes.
let codes: Vec<&str> = codes.iter().map(AsRef::as_ref).collect();
let suffix = codes.join(", ");
new_line.push_str(&suffix);
output.push_str(&new_line);
output.push('\n');
// Only count if the new line is an actual edit.
if &new_line != line {
count += 1;
}
}
};
let codes: Vec<&str> = codes.iter().map(AsRef::as_ref).collect();
output.push_str(&codes.join(", "));
output.push('\n');
count += 1;
}
}
}

View File

@@ -1,33 +1,139 @@
use std::string::ToString;
use regex::Regex;
use rustc_hash::FxHashSet;
use rustpython_ast::{Keyword, KeywordData};
use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind,
};
use crate::ast::types::{BindingKind, FunctionScope, Range, Scope, ScopeKind};
use crate::checks::{Check, CheckKind};
use crate::vendored::format::{FieldName, FormatPart, FormatString, FromTemplate};
use crate::pyflakes::format::FormatSummary;
// F521
pub fn string_dot_format_invalid(literal: &str, location: Range) -> Option<Check> {
match FormatString::from_str(literal) {
Err(e) => Some(Check::new(
CheckKind::StringDotFormatInvalidFormat(e.to_string()),
fn has_star_star_kwargs(keywords: &[Keyword]) -> bool {
keywords.iter().any(|k| {
let KeywordData { arg, .. } = &k.node;
arg.is_none()
})
}
fn has_star_args(args: &[Expr]) -> bool {
args.iter()
.any(|a| matches!(&a.node, ExprKind::Starred { .. }))
}
/// F522
pub(crate) fn string_dot_format_extra_named_arguments(
summary: &FormatSummary,
keywords: &[Keyword],
location: Range,
) -> Option<Check> {
if has_star_star_kwargs(keywords) {
return None;
}
let keywords = keywords.iter().filter_map(|k| {
let KeywordData { arg, .. } = &k.node;
arg.as_ref()
});
let missing: Vec<String> = keywords
.filter(|&k| !summary.keywords.contains(k))
.cloned()
.collect();
if missing.is_empty() {
None
} else {
Some(Check::new(
CheckKind::StringDotFormatExtraNamedArguments(missing),
location,
)),
Ok(format_string) => {
for part in format_string.format_parts {
if let FormatPart::Field { field_name, .. } = &part {
if let Err(e) = FieldName::parse(field_name) {
return Some(Check::new(
CheckKind::StringDotFormatInvalidFormat(e.to_string()),
location,
));
}
}
}
None
}
))
}
}
/// F523
pub(crate) fn string_dot_format_extra_positional_arguments(
summary: &FormatSummary,
args: &[Expr],
location: Range,
) -> Option<Check> {
if has_star_args(args) {
return None;
}
let missing: Vec<String> = (0..args.len())
.filter(|i| !(summary.autos.contains(i) || summary.indexes.contains(i)))
.map(|i| i.to_string())
.collect();
if missing.is_empty() {
None
} else {
Some(Check::new(
CheckKind::StringDotFormatExtraPositionalArguments(missing),
location,
))
}
}
/// F524
pub(crate) fn string_dot_format_missing_argument(
summary: &FormatSummary,
args: &[Expr],
keywords: &[Keyword],
location: Range,
) -> Option<Check> {
if has_star_args(args) || has_star_star_kwargs(keywords) {
return None;
}
let keywords: FxHashSet<_> = keywords
.iter()
.filter_map(|k| {
let KeywordData { arg, .. } = &k.node;
arg.as_ref()
})
.collect();
let missing: Vec<String> = summary
.autos
.iter()
.chain(summary.indexes.iter())
.filter(|&&i| i >= args.len())
.map(ToString::to_string)
.chain(
summary
.keywords
.iter()
.filter(|k| !keywords.contains(k))
.cloned(),
)
.collect();
if missing.is_empty() {
None
} else {
Some(Check::new(
CheckKind::StringDotFormatMissingArguments(missing),
location,
))
}
}
/// F525
pub(crate) fn string_dot_format_mixing_automatic(
summary: &FormatSummary,
location: Range,
) -> Option<Check> {
if summary.autos.is_empty() || summary.indexes.is_empty() {
None
} else {
Some(Check::new(
CheckKind::StringDotFormatMixingAutomatic,
location,
))
}
}

View File

@@ -1,7 +1,12 @@
//! Implements helper functions for using vendored/format.rs
use std::convert::TryFrom;
use std::fmt;
use crate::vendored::format::FormatParseError;
use rustc_hash::FxHashSet;
use crate::vendored::format::{
FieldName, FieldType, FormatParseError, FormatPart, FormatString, FromTemplate,
};
impl fmt::Display for FormatParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -20,3 +25,110 @@ impl fmt::Display for FormatParseError {
write!(f, "{message}")
}
}
pub(crate) struct FormatSummary {
pub autos: FxHashSet<usize>,
pub indexes: FxHashSet<usize>,
pub keywords: FxHashSet<String>,
}
impl TryFrom<&str> for FormatSummary {
type Error = FormatParseError;
fn try_from(literal: &str) -> Result<Self, Self::Error> {
let format_string = FormatString::from_str(literal)?;
let mut autos = FxHashSet::default();
let mut indexes = FxHashSet::default();
let mut keywords = FxHashSet::default();
for format_part in format_string.format_parts {
if let FormatPart::Field {
field_name,
format_spec,
..
} = format_part
{
let parsed = FieldName::parse(&field_name)?;
match parsed.field_type {
FieldType::Auto => autos.insert(autos.len()),
FieldType::Index(i) => indexes.insert(i),
FieldType::Keyword(k) => keywords.insert(k),
};
let nested = FormatString::from_str(&format_spec)?;
for nested_part in nested.format_parts {
if let FormatPart::Field { field_name, .. } = nested_part {
let parsed = FieldName::parse(&field_name)?;
match parsed.field_type {
FieldType::Auto => autos.insert(autos.len()),
FieldType::Index(i) => indexes.insert(i),
FieldType::Keyword(k) => keywords.insert(k),
};
}
}
}
}
Ok(FormatSummary {
autos,
indexes,
keywords,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vendored::format::FromTemplate;
#[test]
fn test_format_summary() {
let literal = "foo{foo}a{}b{2}c{2}d{1}{}{}e{bar}{foo}f{spam}";
let expected_autos = [0usize, 1usize, 2usize].into_iter().collect();
let expected_indexes = [1usize, 2usize].into_iter().collect();
let expected_keywords = ["foo", "bar", "spam"]
.into_iter()
.map(String::from)
.collect();
let format_summary = FormatSummary::try_from(literal).unwrap();
assert_eq!(format_summary.autos, expected_autos);
assert_eq!(format_summary.indexes, expected_indexes);
assert_eq!(format_summary.keywords, expected_keywords);
}
#[test]
fn test_format_summary_nested() {
let literal = "foo{foo}a{:{}{}}b{2:{3}{4}}c{2}d{1}{}e{bar:{spam}{eggs}}";
let expected_autos = [0usize, 1usize, 2usize, 3usize].into_iter().collect();
let expected_indexes = [1usize, 2usize, 3usize, 4usize].into_iter().collect();
let expected_keywords = ["foo", "bar", "spam", "eggs"]
.into_iter()
.map(String::from)
.collect();
let format_summary = FormatSummary::try_from(literal).unwrap();
assert_eq!(format_summary.autos, expected_autos);
assert_eq!(format_summary.indexes, expected_indexes);
assert_eq!(format_summary.keywords, expected_keywords);
}
#[test]
fn test_format_summary_invalid() {
assert!(FormatSummary::try_from("{").is_err());
let literal = "{foo}a{}b{bar..}";
assert!(FormatString::from_str(literal).is_ok());
assert!(FormatSummary::try_from(literal).is_err());
let literal_nested = "{foo}a{}b{bar:{spam..}}";
assert!(FormatString::from_str(literal_nested).is_ok());
assert!(FormatSummary::try_from(literal_nested).is_err());
}
}

View File

@@ -1,4 +1,4 @@
pub mod checks;
pub mod fixes;
mod format;
pub mod format;
pub mod plugins;

View File

@@ -209,15 +209,21 @@ pub enum SubscriptKind {
PEP593AnnotatedSubscript,
}
pub fn match_annotated_subscript(
pub fn match_annotated_subscript<F>(
expr: &Expr,
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
import_aliases: &FxHashMap<&str, &str>,
) -> Option<SubscriptKind> {
is_builtin: F,
) -> Option<SubscriptKind>
where
F: Fn(&str) -> bool,
{
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
if !call_path.is_empty() {
for (module, member) in SUBSCRIPTS {
if match_call_path(&call_path, module, member, from_imports) {
if match_call_path(&call_path, module, member, from_imports)
&& (!module.is_empty() || is_builtin(member))
{
return Some(SubscriptKind::AnnotatedSubscript);
}
}

View File

@@ -2,7 +2,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Result};
use log::error;
use rustpython_ast::{Constant, Expr, ExprKind, Located, Location};
use rustpython_ast::{Constant, Expr, ExprKind, Keyword, KeywordData, Location};
use rustpython_parser::lexer;
use rustpython_parser::token::Tok;
@@ -14,6 +14,7 @@ use crate::checks::{Check, CheckCode, CheckKind};
use crate::source_code_locator::SourceCodeLocator;
const OPEN_FUNC_NAME: &str = "open";
const MODE_KEYWORD_ARGUMENT: &str = "mode";
enum OpenMode {
U,
@@ -56,15 +57,20 @@ impl OpenMode {
}
}
fn match_open(expr: &Expr) -> Option<&Expr> {
if let ExprKind::Call { func, args, .. } = &expr.node {
fn match_open(expr: &Expr) -> (Option<&Expr>, Vec<Keyword>) {
if let ExprKind::Call {
func,
args,
keywords,
} = &expr.node
{
// TODO(andberger): Verify that "open" is still bound to the built-in function.
if match_name_or_attr(func, OPEN_FUNC_NAME) {
// Return the "open mode" parameter.
return args.get(1);
// Return the "open mode" parameter and keywords.
return (args.get(1), keywords.clone());
}
}
None
(None, vec![])
}
fn create_check(
@@ -101,19 +107,36 @@ fn create_remove_param_fix(
location: expr.location,
end_location: expr.end_location.unwrap(),
});
// Find the last comma before mode_param
// and delete that comma as well as mode_param.
// Find the last comma before mode_param and create a deletion fix
// starting from the comma and ending after mode_param.
let mut fix_start: Option<Location> = None;
let mut fix_end: Option<Location> = None;
let mut is_first_arg: bool = false;
let mut delete_first_arg: bool = false;
for (start, tok, end) in lexer::make_tokenizer(&content).flatten() {
let start = helpers::to_absolute(start, expr.location);
let end = helpers::to_absolute(end, expr.location);
if start == mode_param.location {
if is_first_arg {
delete_first_arg = true;
continue;
}
fix_end = Some(end);
break;
}
if delete_first_arg && matches!(tok, Tok::Name { .. }) {
fix_end = Some(start);
break;
}
if matches!(tok, Tok::Lpar) {
is_first_arg = true;
fix_start = Some(end);
}
if matches!(tok, Tok::Comma) {
fix_start = Some(start);
is_first_arg = false;
if !delete_first_arg {
fix_start = Some(start);
}
}
}
match (fix_start, fix_end) {
@@ -126,20 +149,41 @@ fn create_remove_param_fix(
/// U015
pub fn redundant_open_modes(checker: &mut Checker, expr: &Expr) {
// TODO(andberger): Add "mode" keyword argument handling to handle invocations
// on the following formats:
// - `open("foo", mode="U")`
// - `open(name="foo", mode="U")`
// - `open(mode="U", name="foo")`
if let Some(mode_param) = match_open(expr) {
if let Located {
node:
ExprKind::Constant {
value: Constant::Str(mode_param_value),
..
},
let (mode_param, keywords): (Option<&Expr>, Vec<Keyword>) = match_open(expr);
if mode_param.is_none() && !keywords.is_empty() {
if let Some(value) = keywords.iter().find_map(|keyword| {
let KeywordData { arg, value } = &keyword.node;
if arg
.as_ref()
.map(|arg| arg == MODE_KEYWORD_ARGUMENT)
.unwrap_or_default()
{
Some(value)
} else {
None
}
}) {
if let ExprKind::Constant {
value: Constant::Str(mode_param_value),
..
} = &value.node
{
if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) {
checker.add_check(create_check(
expr,
value,
mode.replacement_value(),
checker.locator,
checker.patch(&CheckCode::U015),
));
}
}
}
} else if let Some(mode_param) = mode_param {
if let ExprKind::Constant {
value: Constant::Str(mode_param_value),
..
} = mode_param
} = &mode_param.node
{
if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) {
checker.add_check(create_check(

View File

@@ -21,12 +21,7 @@ fn is_module_star_imported(checker: &Checker, module: &str) -> bool {
/// Return `true` if `exit` is (still) bound as a built-in in the current scope.
fn has_builtin_exit_in_scope(checker: &Checker) -> bool {
!is_module_star_imported(checker, "sys")
&& checker
.current_scopes()
.find_map(|scope| scope.values.get("exit"))
.map(|binding| matches!(binding.kind, BindingKind::Builtin))
.unwrap_or_default()
!is_module_star_imported(checker, "sys") && checker.is_builtin("exit")
}
/// Return the appropriate `sys.exit` reference based on the current set of

View File

@@ -9,7 +9,7 @@ use once_cell::sync::Lazy;
use path_absolutize::path_dedot;
use regex::Regex;
use crate::checks_gen::CheckCodePrefix;
use crate::checks_gen::{CheckCodePrefix, CATEGORIES};
use crate::settings::pyproject::load_options;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::{
@@ -117,28 +117,7 @@ impl Configuration {
.unwrap_or_else(|| vec![CheckCodePrefix::E, CheckCodePrefix::F]),
extend_select: options.extend_select.unwrap_or_default(),
fix: options.fix.unwrap_or_default(),
fixable: options.fixable.unwrap_or_else(|| {
// TODO(charlie): Autogenerate this list.
vec![
CheckCodePrefix::A,
CheckCodePrefix::B,
CheckCodePrefix::BLE,
CheckCodePrefix::C,
CheckCodePrefix::D,
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::I,
CheckCodePrefix::M,
CheckCodePrefix::N,
CheckCodePrefix::Q,
CheckCodePrefix::RUF,
CheckCodePrefix::S,
CheckCodePrefix::T,
CheckCodePrefix::U,
CheckCodePrefix::W,
CheckCodePrefix::YTT,
]
}),
fixable: options.fixable.unwrap_or_else(|| CATEGORIES.to_vec()),
unfixable: options.unfixable.unwrap_or_default(),
ignore: options.ignore.unwrap_or_default(),
line_length: options.line_length.unwrap_or(88),

View File

@@ -0,0 +1,158 @@
---
source: src/linter.rs
expression: checks
---
- kind:
FunctionUsesLoopVariable: x
location:
row: 12
column: 29
end_location:
row: 12
column: 30
fix: ~
- kind:
FunctionUsesLoopVariable: y
location:
row: 13
column: 29
end_location:
row: 13
column: 30
fix: ~
- kind:
FunctionUsesLoopVariable: x
location:
row: 16
column: 15
end_location:
row: 16
column: 16
fix: ~
- kind:
FunctionUsesLoopVariable: x
location:
row: 28
column: 18
end_location:
row: 28
column: 19
fix: ~
- kind:
FunctionUsesLoopVariable: x
location:
row: 29
column: 18
end_location:
row: 29
column: 19
fix: ~
- kind:
FunctionUsesLoopVariable: x
location:
row: 30
column: 18
end_location:
row: 30
column: 19
fix: ~
- kind:
FunctionUsesLoopVariable: x
location:
row: 31
column: 21
end_location:
row: 31
column: 22
fix: ~
- kind:
FunctionUsesLoopVariable: x
location:
row: 40
column: 33
end_location:
row: 40
column: 34
fix: ~
- kind:
FunctionUsesLoopVariable: x
location:
row: 42
column: 13
end_location:
row: 42
column: 14
fix: ~
- kind:
FunctionUsesLoopVariable: a
location:
row: 50
column: 29
end_location:
row: 50
column: 30
fix: ~
- kind:
FunctionUsesLoopVariable: a_
location:
row: 51
column: 29
end_location:
row: 51
column: 31
fix: ~
- kind:
FunctionUsesLoopVariable: b
location:
row: 52
column: 29
end_location:
row: 52
column: 30
fix: ~
- kind:
FunctionUsesLoopVariable: c
location:
row: 53
column: 29
end_location:
row: 53
column: 30
fix: ~
- kind:
FunctionUsesLoopVariable: j
location:
row: 61
column: 16
end_location:
row: 61
column: 17
fix: ~
- kind:
FunctionUsesLoopVariable: k
location:
row: 61
column: 20
end_location:
row: 61
column: 21
fix: ~
- kind:
FunctionUsesLoopVariable: l
location:
row: 68
column: 9
end_location:
row: 68
column: 10
fix: ~
- kind:
FunctionUsesLoopVariable: i
location:
row: 82
column: 12
end_location:
row: 82
column: 18
fix: ~

View File

@@ -0,0 +1,36 @@
---
source: src/linter.rs
expression: checks
---
- kind:
StringDotFormatExtraNamedArguments:
- bar
location:
row: 1
column: 0
end_location:
row: 1
column: 21
fix: ~
- kind:
StringDotFormatExtraNamedArguments:
- spam
location:
row: 2
column: 0
end_location:
row: 2
column: 34
fix: ~
- kind:
StringDotFormatExtraNamedArguments:
- eggs
- ham
location:
row: 4
column: 0
end_location:
row: 4
column: 51
fix: ~

View File

@@ -0,0 +1,77 @@
---
source: src/linter.rs
expression: checks
---
- kind:
StringDotFormatExtraPositionalArguments:
- "1"
location:
row: 2
column: 0
end_location:
row: 2
column: 18
fix: ~
- kind:
StringDotFormatExtraPositionalArguments:
- "0"
- "2"
location:
row: 3
column: 0
end_location:
row: 3
column: 21
fix: ~
- kind:
StringDotFormatExtraPositionalArguments:
- "2"
location:
row: 5
column: 0
end_location:
row: 5
column: 25
fix: ~
- kind:
StringDotFormatExtraPositionalArguments:
- "1"
location:
row: 6
column: 0
end_location:
row: 6
column: 21
fix: ~
- kind:
StringDotFormatExtraPositionalArguments:
- "1"
location:
row: 9
column: 0
end_location:
row: 9
column: 17
fix: ~
- kind:
StringDotFormatExtraPositionalArguments:
- "1"
- "2"
location:
row: 10
column: 0
end_location:
row: 10
column: 20
fix: ~
- kind:
StringDotFormatExtraPositionalArguments:
- "2"
location:
row: 12
column: 0
end_location:
row: 12
column: 23
fix: ~

View File

@@ -0,0 +1,67 @@
---
source: src/linter.rs
expression: checks
---
- kind:
StringDotFormatMissingArguments:
- "1"
location:
row: 1
column: 0
end_location:
row: 1
column: 17
fix: ~
- kind:
StringDotFormatMissingArguments:
- "2"
location:
row: 2
column: 0
end_location:
row: 2
column: 14
fix: ~
- kind:
StringDotFormatMissingArguments:
- bar
location:
row: 3
column: 0
end_location:
row: 3
column: 16
fix: ~
- kind:
StringDotFormatMissingArguments:
- bar
location:
row: 4
column: 0
end_location:
row: 4
column: 21
fix: ~
- kind:
StringDotFormatMissingArguments:
- "0"
- bar
location:
row: 5
column: 0
end_location:
row: 5
column: 20
fix: ~
- kind:
StringDotFormatMissingArguments:
- "0"
- bar
location:
row: 6
column: 0
end_location:
row: 6
column: 20
fix: ~

View File

@@ -0,0 +1,21 @@
---
source: src/linter.rs
expression: checks
---
- kind: StringDotFormatMixingAutomatic
location:
row: 1
column: 0
end_location:
row: 1
column: 21
fix: ~
- kind: StringDotFormatMixingAutomatic
location:
row: 2
column: 0
end_location:
row: 2
column: 21
fix: ~

View File

@@ -7,8 +7,8 @@ expression: checks
row: 1
column: 0
end_location:
row: 1
column: 14
row: 2
column: 0
fix:
patch:
content: ""
@@ -16,6 +16,6 @@ expression: checks
row: 1
column: 0
end_location:
row: 1
column: 14
row: 2
column: 0

View File

@@ -7,8 +7,8 @@ expression: checks
row: 2
column: 0
end_location:
row: 2
column: 24
row: 3
column: 0
fix:
patch:
content: ""
@@ -16,6 +16,6 @@ expression: checks
row: 2
column: 0
end_location:
row: 2
column: 24
row: 3
column: 0

View File

@@ -0,0 +1,6 @@
---
source: src/linter.rs
expression: checks
---
[]

View File

@@ -386,4 +386,324 @@ expression: checks
end_location:
row: 37
column: 46
- kind: RedundantOpenModes
location:
row: 40
column: 0
end_location:
row: 40
column: 21
fix:
patch:
content: ""
location:
row: 40
column: 10
end_location:
row: 40
column: 20
- kind: RedundantOpenModes
location:
row: 41
column: 0
end_location:
row: 41
column: 26
fix:
patch:
content: ""
location:
row: 41
column: 15
end_location:
row: 41
column: 25
- kind: RedundantOpenModes
location:
row: 42
column: 0
end_location:
row: 42
column: 26
fix:
patch:
content: ""
location:
row: 42
column: 5
end_location:
row: 42
column: 15
- kind: RedundantOpenModes
location:
row: 44
column: 5
end_location:
row: 44
column: 26
fix:
patch:
content: ""
location:
row: 44
column: 15
end_location:
row: 44
column: 25
- kind: RedundantOpenModes
location:
row: 46
column: 5
end_location:
row: 46
column: 31
fix:
patch:
content: ""
location:
row: 46
column: 20
end_location:
row: 46
column: 30
- kind: RedundantOpenModes
location:
row: 48
column: 5
end_location:
row: 48
column: 31
fix:
patch:
content: ""
location:
row: 48
column: 10
end_location:
row: 48
column: 20
- kind: RedundantOpenModes
location:
row: 51
column: 0
end_location:
row: 51
column: 22
fix:
patch:
content: "\"rb\""
location:
row: 51
column: 17
end_location:
row: 51
column: 21
- kind: RedundantOpenModes
location:
row: 52
column: 0
end_location:
row: 52
column: 27
fix:
patch:
content: "\"rb\""
location:
row: 52
column: 22
end_location:
row: 52
column: 26
- kind: RedundantOpenModes
location:
row: 53
column: 0
end_location:
row: 53
column: 27
fix:
patch:
content: "\"rb\""
location:
row: 53
column: 10
end_location:
row: 53
column: 14
- kind: RedundantOpenModes
location:
row: 55
column: 5
end_location:
row: 55
column: 27
fix:
patch:
content: "\"rb\""
location:
row: 55
column: 22
end_location:
row: 55
column: 26
- kind: RedundantOpenModes
location:
row: 57
column: 5
end_location:
row: 57
column: 32
fix:
patch:
content: "\"rb\""
location:
row: 57
column: 27
end_location:
row: 57
column: 31
- kind: RedundantOpenModes
location:
row: 59
column: 5
end_location:
row: 59
column: 32
fix:
patch:
content: "\"rb\""
location:
row: 59
column: 15
end_location:
row: 59
column: 19
- kind: RedundantOpenModes
location:
row: 62
column: 0
end_location:
row: 62
column: 110
fix:
patch:
content: ""
location:
row: 62
column: 15
end_location:
row: 62
column: 25
- kind: RedundantOpenModes
location:
row: 63
column: 0
end_location:
row: 63
column: 110
fix:
patch:
content: ""
location:
row: 63
column: 99
end_location:
row: 63
column: 109
- kind: RedundantOpenModes
location:
row: 64
column: 0
end_location:
row: 64
column: 110
fix:
patch:
content: ""
location:
row: 64
column: 58
end_location:
row: 64
column: 68
- kind: RedundantOpenModes
location:
row: 65
column: 0
end_location:
row: 65
column: 110
fix:
patch:
content: ""
location:
row: 65
column: 5
end_location:
row: 65
column: 15
- kind: RedundantOpenModes
location:
row: 67
column: 0
end_location:
row: 67
column: 111
fix:
patch:
content: "\"rb\""
location:
row: 67
column: 22
end_location:
row: 67
column: 26
- kind: RedundantOpenModes
location:
row: 68
column: 0
end_location:
row: 68
column: 111
fix:
patch:
content: "\"rb\""
location:
row: 68
column: 106
end_location:
row: 68
column: 110
- kind: RedundantOpenModes
location:
row: 69
column: 0
end_location:
row: 69
column: 111
fix:
patch:
content: "\"rb\""
location:
row: 69
column: 65
end_location:
row: 69
column: 69
- kind: RedundantOpenModes
location:
row: 70
column: 0
end_location:
row: 70
column: 111
fix:
patch:
content: "\"rb\""
location:
row: 70
column: 10
end_location:
row: 70
column: 14