Compare commits

...

13 Commits

Author SHA1 Message Date
Charlie Marsh
63b3e00c97 Bump version to 0.0.174 2022-12-10 12:08:48 -05:00
Charlie Marsh
39440aa274 Create function and lambda scopes eagerly (#1181) 2022-12-10 12:08:33 -05:00
Charlie Marsh
add96d3dc5 Implement E0117 (nonlocal-without-binding) (#1180) 2022-12-10 11:41:57 -05:00
Charlie Marsh
6f8e0224d0 Implement W0602 (global-variable-not-assigned) (#1179) 2022-12-10 11:33:24 -05:00
Charlie Marsh
b8bbafd85b Flag global usages prior to global declarations (#1178) 2022-12-10 11:19:24 -05:00
Charlie Marsh
40b54d3e8c Ignore imports in class scopes (#1176) 2022-12-10 10:23:33 -05:00
Charlie Marsh
2b44941d63 Add pacman instructions to README (#1175) 2022-12-10 10:00:01 -05:00
Charlie Marsh
257bd7f1d7 Bump version to 0.0.173 2022-12-09 23:23:12 -05:00
Charlie Marsh
5728dceef0 Add note around redefinitions 2022-12-09 23:18:51 -05:00
Charlie Marsh
6739602806 Mark redefined-but-unused imports as unused regardless of scope (#1173) 2022-12-09 23:17:33 -05:00
Charlie Marsh
305326f7d7 Remove some string clones from docstring helpers (#1172) 2022-12-09 22:30:34 -05:00
Charlie Marsh
69866f5461 Extract docstring exactly once (#1171) 2022-12-09 22:21:16 -05:00
Charlie Marsh
41ca29c4f4 Add TODO in redefined_by_function 2022-12-09 21:19:57 -05:00
32 changed files with 1207 additions and 662 deletions

View File

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

8
Cargo.lock generated
View File

@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.172-dev.0"
version = "0.0.174-dev.0"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1821,7 +1821,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.172"
version = "0.0.174"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1874,7 +1874,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.172"
version = "0.0.174"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1892,7 +1892,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.172"
version = "0.0.174"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.172"
version = "0.0.174"
edition = "2021"
rust-version = "1.65.0"
@@ -41,7 +41,7 @@ quick-junit = { version = "0.3.2" }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.172", path = "ruff_macros" }
ruff_macros = { version = "0.0.174", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }

View File

@@ -120,12 +120,18 @@ For **macOS Homebrew** and **Linuxbrew** users, Ruff is also available as [`ruff
brew install ruff
```
For Conda users, Ruff is also available as [`ruff`](https://anaconda.org/conda-forge/ruff) on `conda-forge`:
For **Conda** users, Ruff is also available as [`ruff`](https://anaconda.org/conda-forge/ruff) on `conda-forge`:
```shell
conda install -c conda-forge ruff
```
For **Arch Linux** users, Ruff is also available as [`ruff`](https://archlinux.org/packages/community/x86_64/ruff/) on the official repositories:
```shell
pacman -S ruff
```
### Usage
To run Ruff, try any of the following:
@@ -147,7 +153,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.172
rev: v0.0.174
hooks:
- id: ruff
```
@@ -809,6 +815,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| PLC0414 | UselessImportAlias | Import alias does not rename original package | 🛠 |
| PLC2201 | MisplacedComparisonConstant | Comparison should be ... | 🛠 |
| PLC3002 | UnnecessaryDirectLambdaCall | Lambda expression called directly. Execute the expression inline instead. | |
| PLE0118 | UsedPriorGlobalDeclaration | Name `...` is used prior to global declaration on line 1 | |
| PLE1142 | AwaitOutsideAsync | `await` should be used within an async function | |
| PLR0206 | PropertyWithParameters | Cannot have defined parameters for properties | |
| PLR0402 | ConsiderUsingFromImport | Use `from ... import ...` in lieu of alias | |

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
###
# Errors.
###
def f():
global x
def f():
global x
print(x)
###
# Non-errors.
###
def f():
global x
x = 1
def f():
global x
(x, y) = (1, 2)
def f():
global x
del x

View File

@@ -0,0 +1,19 @@
nonlocal x
def f():
nonlocal x
def f():
nonlocal y
def f():
x = 1
def f():
nonlocal x
def f():
nonlocal y

View File

@@ -0,0 +1,148 @@
###
# Errors.
###
def f():
print(x)
global x
print(x)
def f():
global x
print(x)
global x
print(x)
def f():
print(x)
global x, y
print(x)
def f():
global x, y
print(x)
global x, y
print(x)
def f():
x = 1
global x
x = 1
def f():
global x
x = 1
global x
x = 1
def f():
del x
global x, y
del x
def f():
global x, y
del x
global x, y
del x
def f():
del x
global x
del x
def f():
global x
del x
global x
del x
def f():
del x
global x, y
del x
def f():
global x, y
del x
global x, y
del x
###
# Non-errors.
###
def f():
global x
print(x)
def f():
global x, y
print(x)
def f():
global x
x = 1
def f():
global x, y
x = 1
def f():
global x
del x
def f():
global x, y
del x

View File

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

View File

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

View File

@@ -1,9 +1,12 @@
use rustc_hash::FxHashMap;
use rustpython_ast::{Cmpop, Located};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use crate::ast::types::{Binding, BindingKind, Scope};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
/// Extract the names bound to a given __all__ assignment.
pub fn extract_all_names(stmt: &Stmt, scope: &Scope, bindings: &[Binding]) -> Vec<String> {
@@ -69,6 +72,38 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope, bindings: &[Binding]) -> Ve
names
}
#[derive(Default)]
struct GlobalVisitor<'a> {
globals: FxHashMap<&'a str, &'a Stmt>,
}
impl<'a> Visitor<'a> for GlobalVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match &stmt.node {
StmtKind::Global { names } => {
for name in names {
self.globals.insert(name, stmt);
}
}
StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::ClassDef { .. } => {
// Don't recurse.
}
_ => visitor::walk_stmt(self, stmt),
}
}
}
/// Extract a map from global name to its last-defining `Stmt`.
pub fn extract_globals(body: &[Stmt]) -> FxHashMap<&str, &Stmt> {
let mut visitor = GlobalVisitor::default();
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.globals
}
/// Check if a node is parent of a conditional branch.
pub fn on_conditional_branch<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool {
parents.any(|parent| {

View File

@@ -32,23 +32,29 @@ impl Range {
#[derive(Debug)]
pub struct FunctionDef<'a> {
// Properties derived from StmtKind::FunctionDef.
pub name: &'a str,
pub args: &'a Arguments,
pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// pub returns: Option<&'a Expr>,
// pub type_comment: Option<&'a str>,
// Scope-specific properties.
// TODO(charlie): Create AsyncFunctionDef to mirror the AST.
pub async_: bool,
pub globals: FxHashMap<&'a str, &'a Stmt>,
}
#[derive(Debug)]
pub struct ClassDef<'a> {
// Properties derived from StmtKind::ClassDef.
pub name: &'a str,
pub bases: &'a [Expr],
pub keywords: &'a [Keyword],
// pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// Scope-specific properties.
pub globals: FxHashMap<&'a str, &'a Stmt>,
}
#[derive(Debug)]
@@ -73,7 +79,11 @@ pub struct Scope<'a> {
pub kind: ScopeKind<'a>,
pub import_starred: bool,
pub uses_locals: bool,
/// A map from bound name to binding index.
pub values: FxHashMap<&'a str, usize>,
/// A list of (name, index) pairs for bindings that were overridden in the
/// scope.
pub overridden: Vec<(&'a str, usize)>,
}
impl<'a> Scope<'a> {
@@ -84,6 +94,7 @@ impl<'a> Scope<'a> {
import_starred: false,
uses_locals: false,
values: FxHashMap::default(),
overridden: Vec::new(),
}
}
}
@@ -117,8 +128,6 @@ pub struct Binding<'a> {
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used.
pub used: Option<(usize, Range)>,
/// A list of pointers to `Binding` instances that redefined this binding.
pub redefined: Vec<usize>,
}
// Pyflakes defines the following binding hierarchy (via inheritance):

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::str::Lines;
use rustpython_ast::{Located, Location};
@@ -5,31 +6,26 @@ use rustpython_ast::{Located, Location};
use crate::ast::types::Range;
use crate::check_ast::Checker;
/// Extract the leading indentation from a line.
pub fn indentation<'a, T>(checker: &'a Checker, located: &'a Located<T>) -> Cow<'a, str> {
let range = Range::from_located(located);
checker.locator.slice_source_code_range(&Range {
location: Location::new(range.location.row(), 0),
end_location: Location::new(range.location.row(), range.location.column()),
})
}
/// Extract the leading words from a line of text.
pub fn leading_words(line: &str) -> String {
line.trim()
.chars()
.take_while(|char| char.is_alphanumeric() || char.is_whitespace())
.collect()
pub fn leading_words(line: &str) -> &str {
let line = line.trim();
line.find(|char: char| !char.is_alphanumeric() && !char.is_whitespace())
.map_or(line, |index| &line[..index])
}
/// Extract the leading whitespace from a line of text.
pub fn leading_space(line: &str) -> String {
line.chars()
.take_while(|char| char.is_whitespace())
.collect()
}
/// Extract the leading indentation from a line.
pub fn indentation<T>(checker: &Checker, located: &Located<T>) -> String {
let range = Range::from_located(located);
checker
.locator
.slice_source_code_range(&Range {
location: Location::new(range.location.row(), 0),
end_location: Location::new(range.location.row(), range.location.column()),
})
.to_string()
pub fn leading_space(line: &str) -> &str {
line.find(|char: char| !char.is_whitespace())
.map_or(line, |index| &line[..index])
}
/// Replace any non-whitespace characters from an indentation string.

View File

@@ -4,7 +4,9 @@ use std::path::Path;
use itertools::Itertools;
use log::error;
use nohash_hasher::IntMap;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::Location;
use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
KeywordData, Operator, Stmt, StmtKind, Suite,
@@ -22,7 +24,7 @@ use crate::ast::types::{
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{branch_detection, cast, helpers, operations, visitor};
use crate::checks::{Check, CheckCode, CheckKind, DeferralKeyword};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
use crate::docstrings::definition::{Definition, DefinitionKind, Docstring, Documentable};
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
use crate::python::future::ALL_FEATURE_NAMES;
use crate::python::typing;
@@ -67,6 +69,7 @@ pub struct Checker<'a> {
pub(crate) depths: FxHashMap<RefEquality<'a, Stmt>, usize>,
pub(crate) child_to_parent: FxHashMap<RefEquality<'a, Stmt>, RefEquality<'a, Stmt>>,
pub(crate) bindings: Vec<Binding<'a>>,
pub(crate) redefinitions: IntMap<usize, Vec<usize>>,
scopes: Vec<Scope<'a>>,
scope_stack: Vec<usize>,
dead_scopes: Vec<usize>,
@@ -74,7 +77,7 @@ pub struct Checker<'a> {
deferred_type_definitions: Vec<(&'a Expr, bool, DeferralContext<'a>)>,
deferred_functions: Vec<(&'a Stmt, DeferralContext<'a>, VisibleScope)>,
deferred_lambdas: Vec<(&'a Expr, DeferralContext<'a>)>,
deferred_assignments: Vec<(usize, DeferralContext<'a>)>,
deferred_assignments: Vec<DeferralContext<'a>>,
// Internal, derivative state.
visible_scope: VisibleScope,
in_f_string: Option<Range>,
@@ -113,6 +116,7 @@ impl<'a> Checker<'a> {
depths: FxHashMap::default(),
child_to_parent: FxHashMap::default(),
bindings: vec![],
redefinitions: IntMap::default(),
scopes: vec![],
scope_stack: vec![],
dead_scopes: vec![],
@@ -237,24 +241,6 @@ where
StmtKind::Global { names } => {
let scope_index = *self.scope_stack.last().expect("No current scope found");
if scope_index != GLOBAL_SCOPE_INDEX {
// If the binding doesn't already exist in the global scope, add it.
for name in names {
if !self.scopes[GLOBAL_SCOPE_INDEX]
.values
.contains_key(&name.as_str())
{
let index = self.bindings.len();
self.bindings.push(Binding {
kind: BindingKind::Assignment,
used: None,
range: Range::from_located(stmt),
source: None,
redefined: vec![],
});
self.scopes[GLOBAL_SCOPE_INDEX].values.insert(name, index);
}
}
// Add the binding to the current scope.
let scope = &mut self.scopes[scope_index];
let usage = Some((scope.id, Range::from_located(stmt)));
@@ -264,8 +250,7 @@ where
kind: BindingKind::Global,
used: usage,
range: Range::from_located(stmt),
source: None,
redefined: vec![],
source: Some(RefEquality(stmt)),
});
scope.values.insert(name, index);
}
@@ -295,8 +280,7 @@ where
kind: BindingKind::Nonlocal,
used: usage,
range: Range::from_located(stmt),
source: None,
redefined: vec![],
source: Some(RefEquality(stmt)),
});
scope.values.insert(name, index);
}
@@ -304,11 +288,23 @@ where
// Mark the binding in the defining scopes as used too. (Skip the global scope
// and the current scope.)
for name in names {
let mut exists = false;
for index in self.scope_stack.iter().skip(1).rev().skip(1) {
if let Some(index) = self.scopes[*index].values.get(&name.as_str()) {
exists = true;
self.bindings[*index].used = usage;
}
}
// Ensure that every nonlocal has an existing binding from a parent scope.
if !exists {
if self.settings.enabled.contains(&CheckCode::PLE0117) {
self.add_check(Check::new(
CheckKind::NonlocalWithoutBinding(name.to_string()),
Range::from_located(stmt),
));
}
}
}
}
@@ -512,7 +508,6 @@ where
used: None,
range: Range::from_located(stmt),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
}
@@ -589,12 +584,6 @@ where
for expr in decorator_list {
self.visit_expr(expr);
}
self.push_scope(Scope::new(ScopeKind::Class(ClassDef {
name,
bases,
keywords,
decorator_list,
})));
}
StmtKind::Import { names } => {
if self.settings.enabled.contains(&CheckCode::E402) {
@@ -622,7 +611,6 @@ where
used: None,
range: Range::from_located(alias),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
} else {
@@ -663,7 +651,6 @@ where
},
range: Range::from_located(alias),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
}
@@ -808,7 +795,6 @@ where
)),
range: Range::from_located(alias),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
@@ -840,7 +826,6 @@ where
used: None,
range: Range::from_located(stmt),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
@@ -910,7 +895,6 @@ where
},
range,
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
}
@@ -1140,7 +1124,20 @@ where
// Recurse.
let prev_visible_scope = self.visible_scope.clone();
match &stmt.node {
StmtKind::FunctionDef { body, .. } | StmtKind::AsyncFunctionDef { body, .. } => {
StmtKind::FunctionDef {
body,
name,
args,
decorator_list,
..
}
| StmtKind::AsyncFunctionDef {
body,
name,
args,
decorator_list,
..
} => {
if self.settings.enabled.contains(&CheckCode::B021) {
flake8_bugbear::plugins::f_string_docstring(self, body);
}
@@ -1155,13 +1152,44 @@ where
.push((definition, scope.visibility.clone()));
self.visible_scope = scope;
// If any global bindings don't already exist in the global scope, add it.
let globals = operations::extract_globals(body);
for (name, stmt) in operations::extract_globals(body) {
if !self.scopes[GLOBAL_SCOPE_INDEX].values.contains_key(name) {
let index = self.bindings.len();
self.bindings.push(Binding {
kind: BindingKind::Assignment,
used: None,
range: Range::from_located(stmt),
source: Some(RefEquality(stmt)),
});
self.scopes[GLOBAL_SCOPE_INDEX].values.insert(name, index);
}
}
self.push_scope(Scope::new(ScopeKind::Function(FunctionDef {
name,
body,
args,
decorator_list,
async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }),
globals,
})));
self.deferred_functions.push((
stmt,
(self.scope_stack.clone(), self.parents.clone()),
self.visible_scope.clone(),
));
}
StmtKind::ClassDef { body, .. } => {
StmtKind::ClassDef {
body,
name,
bases,
keywords,
decorator_list,
..
} => {
if self.settings.enabled.contains(&CheckCode::B021) {
flake8_bugbear::plugins::f_string_docstring(self, body);
}
@@ -1176,6 +1204,29 @@ where
.push((definition, scope.visibility.clone()));
self.visible_scope = scope;
// If any global bindings don't already exist in the global scope, add it.
let globals = operations::extract_globals(body);
for (name, stmt) in &globals {
if !self.scopes[GLOBAL_SCOPE_INDEX].values.contains_key(name) {
let index = self.bindings.len();
self.bindings.push(Binding {
kind: BindingKind::Assignment,
used: None,
range: Range::from_located(stmt),
source: Some(RefEquality(stmt)),
});
self.scopes[GLOBAL_SCOPE_INDEX].values.insert(name, index);
}
}
self.push_scope(Scope::new(ScopeKind::Class(ClassDef {
name,
bases,
keywords,
decorator_list,
globals,
})));
for stmt in body {
self.visit_stmt(stmt);
}
@@ -1227,19 +1278,24 @@ where
self.visible_scope = prev_visible_scope;
// Post-visit.
if let StmtKind::ClassDef { name, .. } = &stmt.node {
self.pop_scope();
self.add_binding(
name,
Binding {
kind: BindingKind::ClassDefinition,
used: None,
range: Range::from_located(stmt),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
};
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
self.pop_scope();
}
StmtKind::ClassDef { name, .. } => {
self.pop_scope();
self.add_binding(
name,
Binding {
kind: BindingKind::ClassDefinition,
used: None,
range: Range::from_located(stmt),
source: Some(self.current_parent().clone()),
},
);
}
_ => {}
}
self.pop_parent();
}
@@ -1369,6 +1425,10 @@ where
if self.settings.enabled.contains(&CheckCode::YTT202) {
flake8_2020::plugins::name_or_attribute(self, expr);
}
if self.settings.enabled.contains(&CheckCode::PLE0118) {
pylint::plugins::used_prior_global_declaration(self, id, expr);
}
}
ExprKind::Attribute { attr, .. } => {
// Ex) typing.List[...]
@@ -2497,7 +2557,6 @@ where
used: None,
range: Range::from_located(arg),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
@@ -2563,7 +2622,6 @@ impl<'a> Checker<'a> {
range: Range::default(),
used: None,
source: None,
redefined: vec![],
});
scope.values.insert(builtin, index);
}
@@ -2592,8 +2650,9 @@ impl<'a> Checker<'a> {
where
'b: 'a,
{
let index = self.bindings.len();
let binding_index = self.bindings.len();
let mut overridden = None;
if let Some((stack_index, scope_index)) = self
.scope_stack
.iter()
@@ -2601,8 +2660,8 @@ impl<'a> Checker<'a> {
.enumerate()
.find(|(_, scope_index)| self.scopes[**scope_index].values.contains_key(&name))
{
let existing_index = self.scopes[*scope_index].values.get(&name).unwrap();
let existing = &self.bindings[*existing_index];
let existing_binding_index = self.scopes[*scope_index].values.get(&name).unwrap();
let existing = &self.bindings[*existing_binding_index];
let in_current_scope = stack_index == 0;
if !matches!(existing.kind, BindingKind::Builtin)
&& existing.source.as_ref().map_or(true, |left| {
@@ -2625,6 +2684,7 @@ impl<'a> Checker<'a> {
| BindingKind::FutureImportation
);
if matches!(binding.kind, BindingKind::LoopVar) && existing_is_import {
overridden = Some((*scope_index, *existing_binding_index));
if self.settings.enabled.contains(&CheckCode::F402) {
self.add_check(Check::new(
CheckKind::ImportShadowedByLoopVar(
@@ -2644,6 +2704,7 @@ impl<'a> Checker<'a> {
cast::decorator_list(existing.source.as_ref().unwrap().0),
))
{
overridden = Some((*scope_index, *existing_binding_index));
if self.settings.enabled.contains(&CheckCode::F811) {
self.add_check(Check::new(
CheckKind::RedefinedWhileUnused(
@@ -2655,11 +2716,21 @@ impl<'a> Checker<'a> {
}
}
} else if existing_is_import && binding.redefines(existing) {
self.bindings[*existing_index].redefined.push(index);
self.redefinitions
.entry(*existing_binding_index)
.or_insert_with(Vec::new)
.push(binding_index);
}
}
}
// If we're about to lose the binding, store it as overriden.
if let Some((scope_index, binding_index)) = overridden {
self.scopes[scope_index]
.overridden
.push((name, binding_index));
}
// Assume the rebound name is used as a global or within a loop.
let scope = self.current_scope();
let binding = match scope.values.get(&name) {
@@ -2674,7 +2745,7 @@ impl<'a> Checker<'a> {
// in scope.
let scope = &mut self.scopes[*(self.scope_stack.last().expect("No current scope found"))];
if !(matches!(binding.kind, BindingKind::Annotation) && scope.values.contains_key(name)) {
scope.values.insert(name, index);
scope.values.insert(name, binding_index);
}
self.bindings.push(binding);
@@ -2849,7 +2920,6 @@ impl<'a> Checker<'a> {
used: None,
range: Range::from_located(expr),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
return;
@@ -2867,7 +2937,6 @@ impl<'a> Checker<'a> {
used: None,
range: Range::from_located(expr),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
return;
@@ -2881,7 +2950,6 @@ impl<'a> Checker<'a> {
used: None,
range: Range::from_located(expr),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
return;
@@ -2932,7 +3000,6 @@ impl<'a> Checker<'a> {
used: None,
range: Range::from_located(expr),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
return;
@@ -2946,7 +3013,6 @@ impl<'a> Checker<'a> {
used: None,
range: Range::from_located(expr),
source: Some(self.current_parent().clone()),
redefined: vec![],
},
);
}
@@ -3054,27 +3120,8 @@ impl<'a> Checker<'a> {
self.visible_scope = visibility;
match &stmt.node {
StmtKind::FunctionDef {
name,
body,
args,
decorator_list,
..
}
| StmtKind::AsyncFunctionDef {
name,
body,
args,
decorator_list,
..
} => {
self.push_scope(Scope::new(ScopeKind::Function(FunctionDef {
name,
body,
args,
decorator_list,
async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }),
})));
StmtKind::FunctionDef { body, args, .. }
| StmtKind::AsyncFunctionDef { body, args, .. } => {
self.visit_arguments(args);
for stmt in body {
self.visit_stmt(stmt);
@@ -3083,12 +3130,7 @@ impl<'a> Checker<'a> {
_ => unreachable!("Expected StmtKind::FunctionDef | StmtKind::AsyncFunctionDef"),
}
self.deferred_assignments.push((
*self.scope_stack.last().expect("No current scope found"),
(scopes, parents),
));
self.pop_scope();
self.deferred_assignments.push((scopes, parents));
}
}
@@ -3099,29 +3141,25 @@ impl<'a> Checker<'a> {
self.parents = parents.clone();
if let ExprKind::Lambda { args, body } = &expr.node {
self.push_scope(Scope::new(ScopeKind::Lambda(Lambda { args, body })));
self.visit_arguments(args);
self.visit_expr(body);
} else {
unreachable!("Expected ExprKind::Lambda");
}
self.deferred_assignments.push((
*self.scope_stack.last().expect("No current scope found"),
(scopes, parents),
));
self.pop_scope();
self.deferred_assignments.push((scopes, parents));
}
}
fn check_deferred_assignments(&mut self) {
self.deferred_assignments.reverse();
while let Some((index, (scopes, _parents))) = self.deferred_assignments.pop() {
while let Some((scopes, _parents)) = self.deferred_assignments.pop() {
let scope_index = scopes[scopes.len() - 1];
let parent_scope_index = scopes[scopes.len() - 2];
if self.settings.enabled.contains(&CheckCode::F841) {
self.add_checks(
pyflakes::checks::unused_variable(
&self.scopes[index],
&self.scopes[scope_index],
&self.bindings,
&self.settings.dummy_variable_rgx,
)
@@ -3131,7 +3169,7 @@ impl<'a> Checker<'a> {
if self.settings.enabled.contains(&CheckCode::F842) {
self.add_checks(
pyflakes::checks::unused_annotation(
&self.scopes[index],
&self.scopes[scope_index],
&self.bindings,
&self.settings.dummy_variable_rgx,
)
@@ -3147,10 +3185,8 @@ impl<'a> Checker<'a> {
self.add_checks(
flake8_unused_arguments::plugins::unused_arguments(
self,
&self.scopes[*scopes
.last()
.expect("Expected parent scope above function scope")],
&self.scopes[index],
&self.scopes[parent_scope_index],
&self.scopes[scope_index],
&self.bindings,
)
.into_iter(),
@@ -3164,6 +3200,7 @@ impl<'a> Checker<'a> {
&& !self.settings.enabled.contains(&CheckCode::F405)
&& !self.settings.enabled.contains(&CheckCode::F811)
&& !self.settings.enabled.contains(&CheckCode::F822)
&& !self.settings.enabled.contains(&CheckCode::PLW0602)
{
return;
}
@@ -3175,6 +3212,24 @@ impl<'a> Checker<'a> {
.rev()
.map(|index| &self.scopes[*index])
{
// PLW0602
if self.settings.enabled.contains(&CheckCode::PLW0602) {
for (name, index) in &scope.values {
let binding = &self.bindings[*index];
if matches!(binding.kind, BindingKind::Global) {
checks.push(Check::new(
CheckKind::GlobalVariableNotAssigned((*name).to_string()),
binding.range,
));
}
}
}
// Imports in classes are public members.
if matches!(scope.kind, ScopeKind::Class(..)) {
continue;
}
let all_binding: Option<&Binding> = scope
.values
.get("__all__")
@@ -3202,6 +3257,9 @@ impl<'a> Checker<'a> {
}
}
// Look for any bindings that were redefined in another scope, and remain
// unused. Note that we only store references in `redefinitions` if
// the bindings are in different scopes.
if self.settings.enabled.contains(&CheckCode::F811) {
for (name, index) in &scope.values {
let binding = &self.bindings[*index];
@@ -3224,14 +3282,16 @@ impl<'a> Checker<'a> {
continue;
}
for index in &binding.redefined {
checks.push(Check::new(
CheckKind::RedefinedWhileUnused(
(*name).to_string(),
binding.range.location.row(),
),
self.bindings[*index].range,
));
if let Some(indices) = self.redefinitions.get(index) {
for index in indices {
checks.push(Check::new(
CheckKind::RedefinedWhileUnused(
(*name).to_string(),
binding.range.location.row(),
),
self.bindings[*index].range,
));
}
}
}
}
@@ -3278,7 +3338,11 @@ impl<'a> Checker<'a> {
let mut unused: FxHashMap<BindingContext, Vec<UnusedImport>> = FxHashMap::default();
for (name, index) in &scope.values {
for (name, index) in scope
.values
.iter()
.chain(scope.overridden.iter().map(|(a, b)| (a, b)))
{
let binding = &self.bindings[*index];
let (BindingKind::Importation(_, full_name)
@@ -3354,22 +3418,68 @@ impl<'a> Checker<'a> {
}
fn check_definitions(&mut self) {
let enforce_annotations = self.settings.enabled.contains(&CheckCode::ANN001)
|| self.settings.enabled.contains(&CheckCode::ANN002)
|| self.settings.enabled.contains(&CheckCode::ANN003)
|| self.settings.enabled.contains(&CheckCode::ANN101)
|| self.settings.enabled.contains(&CheckCode::ANN102)
|| self.settings.enabled.contains(&CheckCode::ANN201)
|| self.settings.enabled.contains(&CheckCode::ANN202)
|| self.settings.enabled.contains(&CheckCode::ANN204)
|| self.settings.enabled.contains(&CheckCode::ANN205)
|| self.settings.enabled.contains(&CheckCode::ANN206)
|| self.settings.enabled.contains(&CheckCode::ANN401);
let enforce_docstrings = self.settings.enabled.contains(&CheckCode::D100)
|| self.settings.enabled.contains(&CheckCode::D101)
|| self.settings.enabled.contains(&CheckCode::D102)
|| self.settings.enabled.contains(&CheckCode::D103)
|| self.settings.enabled.contains(&CheckCode::D104)
|| self.settings.enabled.contains(&CheckCode::D105)
|| self.settings.enabled.contains(&CheckCode::D106)
|| self.settings.enabled.contains(&CheckCode::D107)
|| self.settings.enabled.contains(&CheckCode::D200)
|| self.settings.enabled.contains(&CheckCode::D201)
|| self.settings.enabled.contains(&CheckCode::D202)
|| self.settings.enabled.contains(&CheckCode::D203)
|| self.settings.enabled.contains(&CheckCode::D204)
|| self.settings.enabled.contains(&CheckCode::D205)
|| self.settings.enabled.contains(&CheckCode::D206)
|| self.settings.enabled.contains(&CheckCode::D207)
|| self.settings.enabled.contains(&CheckCode::D208)
|| self.settings.enabled.contains(&CheckCode::D209)
|| self.settings.enabled.contains(&CheckCode::D210)
|| self.settings.enabled.contains(&CheckCode::D211)
|| self.settings.enabled.contains(&CheckCode::D212)
|| self.settings.enabled.contains(&CheckCode::D213)
|| self.settings.enabled.contains(&CheckCode::D214)
|| self.settings.enabled.contains(&CheckCode::D215)
|| self.settings.enabled.contains(&CheckCode::D300)
|| self.settings.enabled.contains(&CheckCode::D301)
|| self.settings.enabled.contains(&CheckCode::D400)
|| self.settings.enabled.contains(&CheckCode::D402)
|| self.settings.enabled.contains(&CheckCode::D403)
|| self.settings.enabled.contains(&CheckCode::D404)
|| self.settings.enabled.contains(&CheckCode::D405)
|| self.settings.enabled.contains(&CheckCode::D406)
|| self.settings.enabled.contains(&CheckCode::D407)
|| self.settings.enabled.contains(&CheckCode::D408)
|| self.settings.enabled.contains(&CheckCode::D409)
|| self.settings.enabled.contains(&CheckCode::D410)
|| self.settings.enabled.contains(&CheckCode::D411)
|| self.settings.enabled.contains(&CheckCode::D412)
|| self.settings.enabled.contains(&CheckCode::D413)
|| self.settings.enabled.contains(&CheckCode::D414)
|| self.settings.enabled.contains(&CheckCode::D415)
|| self.settings.enabled.contains(&CheckCode::D416)
|| self.settings.enabled.contains(&CheckCode::D417)
|| self.settings.enabled.contains(&CheckCode::D418)
|| self.settings.enabled.contains(&CheckCode::D419);
let mut overloaded_name: Option<String> = None;
self.definitions.reverse();
while let Some((definition, visibility)) = self.definitions.pop() {
// flake8-annotations
if self.settings.enabled.contains(&CheckCode::ANN001)
|| self.settings.enabled.contains(&CheckCode::ANN002)
|| self.settings.enabled.contains(&CheckCode::ANN003)
|| self.settings.enabled.contains(&CheckCode::ANN101)
|| self.settings.enabled.contains(&CheckCode::ANN102)
|| self.settings.enabled.contains(&CheckCode::ANN201)
|| self.settings.enabled.contains(&CheckCode::ANN202)
|| self.settings.enabled.contains(&CheckCode::ANN204)
|| self.settings.enabled.contains(&CheckCode::ANN205)
|| self.settings.enabled.contains(&CheckCode::ANN206)
|| self.settings.enabled.contains(&CheckCode::ANN401)
{
if enforce_annotations {
// TODO(charlie): This should be even stricter, in that an overload
// implementation should come immediately after the overloaded
// interfaces, without any AST nodes in between. Right now, we
@@ -3388,87 +3498,110 @@ impl<'a> Checker<'a> {
}
// pydocstyle
if !pydocstyle::plugins::not_empty(self, &definition) {
continue;
}
if !pydocstyle::plugins::not_missing(self, &definition, &visibility) {
continue;
}
if self.settings.enabled.contains(&CheckCode::D200) {
pydocstyle::plugins::one_liner(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D201)
|| self.settings.enabled.contains(&CheckCode::D202)
{
pydocstyle::plugins::blank_before_after_function(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D203)
|| self.settings.enabled.contains(&CheckCode::D204)
|| self.settings.enabled.contains(&CheckCode::D211)
{
pydocstyle::plugins::blank_before_after_class(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D205) {
pydocstyle::plugins::blank_after_summary(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D206)
|| self.settings.enabled.contains(&CheckCode::D207)
|| self.settings.enabled.contains(&CheckCode::D208)
{
pydocstyle::plugins::indent(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D209) {
pydocstyle::plugins::newline_after_last_paragraph(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D210) {
pydocstyle::plugins::no_surrounding_whitespace(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D212)
|| self.settings.enabled.contains(&CheckCode::D213)
{
pydocstyle::plugins::multi_line_summary_start(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D300) {
pydocstyle::plugins::triple_quotes(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D301) {
pydocstyle::plugins::backslashes(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D400) {
pydocstyle::plugins::ends_with_period(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D402) {
pydocstyle::plugins::no_signature(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D403) {
pydocstyle::plugins::capitalized(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D404) {
pydocstyle::plugins::starts_with_this(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D415) {
pydocstyle::plugins::ends_with_punctuation(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D418) {
pydocstyle::plugins::if_needed(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D212)
|| self.settings.enabled.contains(&CheckCode::D214)
|| self.settings.enabled.contains(&CheckCode::D215)
|| self.settings.enabled.contains(&CheckCode::D405)
|| self.settings.enabled.contains(&CheckCode::D406)
|| self.settings.enabled.contains(&CheckCode::D407)
|| self.settings.enabled.contains(&CheckCode::D408)
|| self.settings.enabled.contains(&CheckCode::D409)
|| self.settings.enabled.contains(&CheckCode::D410)
|| self.settings.enabled.contains(&CheckCode::D411)
|| self.settings.enabled.contains(&CheckCode::D412)
|| self.settings.enabled.contains(&CheckCode::D413)
|| self.settings.enabled.contains(&CheckCode::D414)
|| self.settings.enabled.contains(&CheckCode::D416)
|| self.settings.enabled.contains(&CheckCode::D417)
{
pydocstyle::plugins::sections(self, &definition);
if enforce_docstrings {
if definition.docstring.is_none() {
pydocstyle::plugins::not_missing(self, &definition, &visibility);
continue;
}
// Extract a `Docstring` from a `Definition`.
let expr = definition.docstring.unwrap();
let content = self
.locator
.slice_source_code_range(&Range::from_located(expr));
let indentation = self.locator.slice_source_code_range(&Range {
location: Location::new(expr.location.row(), 0),
end_location: Location::new(expr.location.row(), expr.location.column()),
});
let body = pydocstyle::helpers::raw_contents(&content);
let docstring = Docstring {
kind: definition.kind,
expr,
contents: &content,
indentation: &indentation,
body,
};
if !pydocstyle::plugins::not_empty(self, &docstring) {
continue;
}
if self.settings.enabled.contains(&CheckCode::D200) {
pydocstyle::plugins::one_liner(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D201)
|| self.settings.enabled.contains(&CheckCode::D202)
{
pydocstyle::plugins::blank_before_after_function(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D203)
|| self.settings.enabled.contains(&CheckCode::D204)
|| self.settings.enabled.contains(&CheckCode::D211)
{
pydocstyle::plugins::blank_before_after_class(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D205) {
pydocstyle::plugins::blank_after_summary(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D206)
|| self.settings.enabled.contains(&CheckCode::D207)
|| self.settings.enabled.contains(&CheckCode::D208)
{
pydocstyle::plugins::indent(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D209) {
pydocstyle::plugins::newline_after_last_paragraph(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D210) {
pydocstyle::plugins::no_surrounding_whitespace(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D212)
|| self.settings.enabled.contains(&CheckCode::D213)
{
pydocstyle::plugins::multi_line_summary_start(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D300) {
pydocstyle::plugins::triple_quotes(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D301) {
pydocstyle::plugins::backslashes(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D400) {
pydocstyle::plugins::ends_with_period(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D402) {
pydocstyle::plugins::no_signature(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D403) {
pydocstyle::plugins::capitalized(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D404) {
pydocstyle::plugins::starts_with_this(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D415) {
pydocstyle::plugins::ends_with_punctuation(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D418) {
pydocstyle::plugins::if_needed(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D212)
|| self.settings.enabled.contains(&CheckCode::D214)
|| self.settings.enabled.contains(&CheckCode::D215)
|| self.settings.enabled.contains(&CheckCode::D405)
|| self.settings.enabled.contains(&CheckCode::D406)
|| self.settings.enabled.contains(&CheckCode::D407)
|| self.settings.enabled.contains(&CheckCode::D408)
|| self.settings.enabled.contains(&CheckCode::D409)
|| self.settings.enabled.contains(&CheckCode::D410)
|| self.settings.enabled.contains(&CheckCode::D411)
|| self.settings.enabled.contains(&CheckCode::D412)
|| self.settings.enabled.contains(&CheckCode::D413)
|| self.settings.enabled.contains(&CheckCode::D414)
|| self.settings.enabled.contains(&CheckCode::D416)
|| self.settings.enabled.contains(&CheckCode::D417)
{
pydocstyle::plugins::sections(self, &docstring);
}
}
}
}

View File

@@ -99,12 +99,15 @@ pub enum CheckCode {
PLC0414,
PLC2201,
PLC3002,
PLE0117,
PLE0118,
PLE1142,
PLR0206,
PLR0402,
PLR1701,
PLR1722,
PLW0120,
PLW0602,
// flake8-builtins
A001,
A002,
@@ -635,15 +638,18 @@ pub enum CheckKind {
UnusedVariable(String),
YieldOutsideFunction(DeferralKeyword),
// pylint
ConsiderMergingIsinstance(String, Vec<String>),
UselessImportAlias,
MisplacedComparisonConstant(String),
UnnecessaryDirectLambdaCall,
PropertyWithParameters,
ConsiderUsingFromImport(String, String),
AwaitOutsideAsync,
UselessElseOnLoop,
ConsiderMergingIsinstance(String, Vec<String>),
ConsiderUsingFromImport(String, String),
GlobalVariableNotAssigned(String),
MisplacedComparisonConstant(String),
NonlocalWithoutBinding(String),
PropertyWithParameters,
UnnecessaryDirectLambdaCall,
UseSysExit(String),
UsedPriorGlobalDeclaration(String, usize),
UselessElseOnLoop,
UselessImportAlias,
// flake8-builtins
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
@@ -950,6 +956,8 @@ impl CheckCode {
CheckCode::PLC0414 => CheckKind::UselessImportAlias,
CheckCode::PLC2201 => CheckKind::MisplacedComparisonConstant("...".to_string()),
CheckCode::PLC3002 => CheckKind::UnnecessaryDirectLambdaCall,
CheckCode::PLE0117 => CheckKind::NonlocalWithoutBinding("...".to_string()),
CheckCode::PLE0118 => CheckKind::UsedPriorGlobalDeclaration("...".to_string(), 1),
CheckCode::PLE1142 => CheckKind::AwaitOutsideAsync,
CheckCode::PLR0402 => {
CheckKind::ConsiderUsingFromImport("...".to_string(), "...".to_string())
@@ -960,6 +968,7 @@ impl CheckCode {
}
CheckCode::PLR1722 => CheckKind::UseSysExit("exit".to_string()),
CheckCode::PLW0120 => CheckKind::UselessElseOnLoop,
CheckCode::PLW0602 => CheckKind::GlobalVariableNotAssigned("...".to_string()),
// flake8-builtins
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
@@ -1402,12 +1411,15 @@ impl CheckCode {
CheckCode::PLC0414 => CheckCategory::Pylint,
CheckCode::PLC2201 => CheckCategory::Pylint,
CheckCode::PLC3002 => CheckCategory::Pylint,
CheckCode::PLE0117 => CheckCategory::Pylint,
CheckCode::PLE0118 => CheckCategory::Pylint,
CheckCode::PLE1142 => CheckCategory::Pylint,
CheckCode::PLR0206 => CheckCategory::Pylint,
CheckCode::PLR0402 => CheckCategory::Pylint,
CheckCode::PLR1701 => CheckCategory::Pylint,
CheckCode::PLR1722 => CheckCategory::Pylint,
CheckCode::PLW0120 => CheckCategory::Pylint,
CheckCode::PLW0602 => CheckCategory::Pylint,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
@@ -1530,15 +1542,18 @@ impl CheckKind {
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
CheckKind::InvalidEscapeSequence(_) => &CheckCode::W605,
// pylint
CheckKind::UselessImportAlias => &CheckCode::PLC0414,
CheckKind::MisplacedComparisonConstant(..) => &CheckCode::PLC2201,
CheckKind::UnnecessaryDirectLambdaCall => &CheckCode::PLC3002,
CheckKind::AwaitOutsideAsync => &CheckCode::PLE1142,
CheckKind::ConsiderMergingIsinstance(..) => &CheckCode::PLR1701,
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
CheckKind::ConsiderUsingFromImport(..) => &CheckCode::PLR0402,
CheckKind::GlobalVariableNotAssigned(..) => &CheckCode::PLW0602,
CheckKind::MisplacedComparisonConstant(..) => &CheckCode::PLC2201,
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
CheckKind::UnnecessaryDirectLambdaCall => &CheckCode::PLC3002,
CheckKind::UseSysExit(_) => &CheckCode::PLR1722,
CheckKind::NonlocalWithoutBinding(..) => &CheckCode::PLE0117,
CheckKind::UsedPriorGlobalDeclaration(..) => &CheckCode::PLE0118,
CheckKind::UselessElseOnLoop => &CheckCode::PLW0120,
CheckKind::UselessImportAlias => &CheckCode::PLC0414,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
@@ -1947,6 +1962,9 @@ impl CheckKind {
CheckKind::MisplacedComparisonConstant(comparison) => {
format!("Comparison should be {comparison}")
}
CheckKind::NonlocalWithoutBinding(name) => {
format!("Nonlocal name `{name}` found without binding")
}
CheckKind::UnnecessaryDirectLambdaCall => "Lambda expression called directly. Execute \
the expression inline instead."
.to_string(),
@@ -1956,6 +1974,12 @@ impl CheckKind {
CheckKind::ConsiderUsingFromImport(module, name) => {
format!("Use `from {module} import {name}` in lieu of alias")
}
CheckKind::UsedPriorGlobalDeclaration(name, line) => {
format!("Name `{name}` is used prior to global declaration on line {line}")
}
CheckKind::GlobalVariableNotAssigned(name) => {
format!("Using global for `{name}` but no assignment is done")
}
CheckKind::AwaitOutsideAsync => {
"`await` should be used within an async function".to_string()
}

View File

@@ -315,6 +315,11 @@ pub enum CheckCodePrefix {
PLC300,
PLC3002,
PLE,
PLE0,
PLE01,
PLE011,
PLE0117,
PLE0118,
PLE1,
PLE11,
PLE114,
@@ -338,6 +343,9 @@ pub enum CheckCodePrefix {
PLW01,
PLW012,
PLW0120,
PLW06,
PLW060,
PLW0602,
Q,
Q0,
Q00,
@@ -1343,7 +1351,14 @@ impl CheckCodePrefix {
CheckCodePrefix::PLC30 => vec![CheckCode::PLC3002],
CheckCodePrefix::PLC300 => vec![CheckCode::PLC3002],
CheckCodePrefix::PLC3002 => vec![CheckCode::PLC3002],
CheckCodePrefix::PLE => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE => {
vec![CheckCode::PLE0117, CheckCode::PLE0118, CheckCode::PLE1142]
}
CheckCodePrefix::PLE0 => vec![CheckCode::PLE0117, CheckCode::PLE0118],
CheckCodePrefix::PLE01 => vec![CheckCode::PLE0117, CheckCode::PLE0118],
CheckCodePrefix::PLE011 => vec![CheckCode::PLE0117, CheckCode::PLE0118],
CheckCodePrefix::PLE0117 => vec![CheckCode::PLE0117],
CheckCodePrefix::PLE0118 => vec![CheckCode::PLE0118],
CheckCodePrefix::PLE1 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE11 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE114 => vec![CheckCode::PLE1142],
@@ -1367,11 +1382,14 @@ impl CheckCodePrefix {
CheckCodePrefix::PLR1701 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR172 => vec![CheckCode::PLR1722],
CheckCodePrefix::PLR1722 => vec![CheckCode::PLR1722],
CheckCodePrefix::PLW => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW0 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW => vec![CheckCode::PLW0120, CheckCode::PLW0602],
CheckCodePrefix::PLW0 => vec![CheckCode::PLW0120, CheckCode::PLW0602],
CheckCodePrefix::PLW01 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW012 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW0120 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW06 => vec![CheckCode::PLW0602],
CheckCodePrefix::PLW060 => vec![CheckCode::PLW0602],
CheckCodePrefix::PLW0602 => vec![CheckCode::PLW0602],
CheckCodePrefix::Q => vec![
CheckCode::Q000,
CheckCode::Q001,
@@ -2118,6 +2136,11 @@ impl CheckCodePrefix {
CheckCodePrefix::PLC300 => SuffixLength::Three,
CheckCodePrefix::PLC3002 => SuffixLength::Four,
CheckCodePrefix::PLE => SuffixLength::Zero,
CheckCodePrefix::PLE0 => SuffixLength::One,
CheckCodePrefix::PLE01 => SuffixLength::Two,
CheckCodePrefix::PLE011 => SuffixLength::Three,
CheckCodePrefix::PLE0117 => SuffixLength::Four,
CheckCodePrefix::PLE0118 => SuffixLength::Four,
CheckCodePrefix::PLE1 => SuffixLength::One,
CheckCodePrefix::PLE11 => SuffixLength::Two,
CheckCodePrefix::PLE114 => SuffixLength::Three,
@@ -2141,6 +2164,9 @@ impl CheckCodePrefix {
CheckCodePrefix::PLW01 => SuffixLength::Two,
CheckCodePrefix::PLW012 => SuffixLength::Three,
CheckCodePrefix::PLW0120 => SuffixLength::Four,
CheckCodePrefix::PLW06 => SuffixLength::Two,
CheckCodePrefix::PLW060 => SuffixLength::Three,
CheckCodePrefix::PLW0602 => SuffixLength::Four,
CheckCodePrefix::Q => SuffixLength::Zero,
CheckCodePrefix::Q0 => SuffixLength::One,
CheckCodePrefix::Q00 => SuffixLength::Two,

View File

@@ -1,6 +1,8 @@
use std::borrow::Cow;
use rustpython_ast::{Expr, Stmt};
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum DefinitionKind<'a> {
Module,
Package,
@@ -17,6 +19,15 @@ pub struct Definition<'a> {
pub docstring: Option<&'a Expr>,
}
#[derive(Debug)]
pub struct Docstring<'a> {
pub kind: DefinitionKind<'a>,
pub expr: &'a Expr,
pub contents: &'a Cow<'a, str>,
pub body: &'a str,
pub indentation: &'a Cow<'a, str>,
}
pub enum Documentable {
Class,
Function,

View File

@@ -3,7 +3,7 @@ use crate::docstrings::styles::SectionStyle;
#[derive(Debug)]
pub(crate) struct SectionContext<'a> {
pub(crate) section_name: String,
pub(crate) section_name: &'a str,
pub(crate) previous_line: &'a str,
pub(crate) line: &'a str,
pub(crate) following_lines: &'a [&'a str],
@@ -22,7 +22,7 @@ fn is_docstring_section(context: &SectionContext) -> bool {
let section_name_suffix = context
.line
.trim()
.strip_prefix(&context.section_name)
.strip_prefix(context.section_name)
.unwrap()
.trim();
let this_looks_like_a_section_name =

View File

@@ -19,14 +19,12 @@ fn extract_range(body: &[&Stmt]) -> Range {
}
}
fn extract_indentation(body: &[&Stmt], locator: &SourceCodeLocator) -> String {
fn extract_indentation_range(body: &[&Stmt]) -> Range {
let location = body.first().unwrap().location;
let range = Range {
Range {
location: Location::new(location.row(), 0),
end_location: location,
};
let existing = locator.slice_source_code_range(&range);
leading_space(&existing)
}
}
/// I001
@@ -36,8 +34,10 @@ pub fn check_imports(
settings: &Settings,
autofix: bool,
) -> Option<Check> {
let indentation = locator.slice_source_code_range(&extract_indentation_range(&block.imports));
let indentation = leading_space(&indentation);
let range = extract_range(&block.imports);
let indentation = extract_indentation(&block.imports, locator);
// Extract comments. Take care to grab any inline comments from the last line.
let comments = comments::collect_comments(
@@ -77,7 +77,7 @@ pub fn check_imports(
if has_leading_content {
content.push('\n');
}
content.push_str(&indent(&expected, &indentation));
content.push_str(&indent(&expected, indentation));
check.amend(Fix::replacement(
content,
// Preserve leading prefix (but put the imports on a new line).
@@ -104,7 +104,7 @@ pub fn check_imports(
let mut check = Check::new(CheckKind::UnsortedImports, range);
if autofix && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::replacement(
indent(&expected, &indentation),
indent(&expected, indentation),
range.location,
range.end_location,
));

View File

@@ -298,11 +298,11 @@ pub fn do_not_assign_lambda(checker: &mut Checker, target: &Expr, value: &Expr,
{
match function(id, args, body) {
Ok(content) => {
let indentation =
&leading_space(&checker.locator.slice_source_code_range(&Range {
location: Location::new(stmt.location.row(), 0),
end_location: Location::new(stmt.location.row() + 1, 0),
}));
let first_line = checker.locator.slice_source_code_range(&Range {
location: Location::new(stmt.location.row(), 0),
end_location: Location::new(stmt.location.row() + 1, 0),
});
let indentation = &leading_space(&first_line);
let mut indented = String::new();
for (idx, line) in content.lines().enumerate() {
if idx == 0 {

View File

@@ -1,8 +1,4 @@
use rustpython_ast::Expr;
use crate::ast::types::Range;
use crate::docstrings::constants;
use crate::SourceCodeLocator;
/// Strip the leading and trailing quotes from a docstring.
pub fn raw_contents(contents: &str) -> &str {
@@ -20,12 +16,8 @@ pub fn raw_contents(contents: &str) -> &str {
}
/// Return the leading quote string for a docstring (e.g., `"""`).
pub fn leading_quote<'a>(docstring: &Expr, locator: &'a SourceCodeLocator) -> Option<&'a str> {
if let Some(first_line) = locator
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
{
pub fn leading_quote(content: &str) -> Option<&str> {
if let Some(first_line) = content.lines().next() {
for pattern in constants::TRIPLE_QUOTE_PREFIXES
.iter()
.chain(constants::SINGLE_QUOTE_PREFIXES)

View File

@@ -1,4 +1,4 @@
mod helpers;
pub mod helpers;
pub mod plugins;
#[cfg(test)]

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@

View File

@@ -1,7 +1,6 @@
pub mod cformat;
pub mod checks;
pub mod fixes;
mod foo;
pub mod format;
pub mod plugins;
@@ -405,14 +404,13 @@ mod tests {
"#,
&[],
)?;
// Pyflakes allows this, but it causes other issues.
// flakes(
// r#"
// def c(): bar
// def b(): global bar; bar = 1
// "#,
// &[],
// )?;
flakes(
r#"
def c(): bar
def b(): global bar; bar = 1
"#,
&[],
)?;
Ok(())
}
@@ -1105,11 +1103,11 @@ mod tests {
fn aliased_import() -> Result<()> {
flakes(
"import fu as FU, bar as FU",
&[CheckCode::F811, CheckCode::F401],
&[CheckCode::F401, CheckCode::F811, CheckCode::F401],
)?;
flakes(
"from moo import fu as FU, bar as FU",
&[CheckCode::F811, CheckCode::F401],
&[CheckCode::F401, CheckCode::F811, CheckCode::F401],
)?;
Ok(())
@@ -1146,9 +1144,15 @@ mod tests {
#[test]
fn redefined_while_unused() -> Result<()> {
flakes("import fu; fu = 3", &[CheckCode::F811])?;
flakes("import fu; fu, bar = 3", &[CheckCode::F811])?;
flakes("import fu; [fu, bar] = 3", &[CheckCode::F811])?;
flakes("import fu; fu = 3", &[CheckCode::F401, CheckCode::F811])?;
flakes(
"import fu; fu, bar = 3",
&[CheckCode::F401, CheckCode::F811],
)?;
flakes(
"import fu; [fu, bar] = 3",
&[CheckCode::F401, CheckCode::F811],
)?;
Ok(())
}
@@ -1165,7 +1169,7 @@ mod tests {
import os
os.path
"#,
&[CheckCode::F811],
&[CheckCode::F401, CheckCode::F811],
)?;
Ok(())
@@ -1203,7 +1207,7 @@ mod tests {
pass
os.path
"#,
&[CheckCode::F811],
&[CheckCode::F401, CheckCode::F811],
)?;
Ok(())
@@ -1279,7 +1283,7 @@ mod tests {
from bb import mixer
mixer(123)
"#,
&[CheckCode::F811],
&[CheckCode::F401, CheckCode::F811],
)?;
Ok(())
@@ -1351,7 +1355,7 @@ mod tests {
def fu():
pass
"#,
&[CheckCode::F811],
&[CheckCode::F401, CheckCode::F811],
)?;
Ok(())
@@ -1429,7 +1433,7 @@ mod tests {
class fu:
pass
"#,
&[CheckCode::F811],
&[CheckCode::F401, CheckCode::F811],
)?;
Ok(())
@@ -1476,7 +1480,7 @@ mod tests {
class bar:
import fu
"#,
&[CheckCode::F401],
&[],
)?;
flakes(
@@ -1486,7 +1490,7 @@ mod tests {
fu
"#,
&[CheckCode::F401, CheckCode::F821],
&[CheckCode::F821],
)?;
Ok(())
@@ -1693,7 +1697,7 @@ mod tests {
for fu in range(2):
pass
"#,
&[CheckCode::F402],
&[CheckCode::F401, CheckCode::F402],
)?;
Ok(())
@@ -1850,7 +1854,7 @@ mod tests {
try: pass
except Exception as fu: pass
"#,
&[CheckCode::F811, CheckCode::F841],
&[CheckCode::F401, CheckCode::F811, CheckCode::F841],
)?;
Ok(())
@@ -2091,7 +2095,7 @@ mod tests {
def fun(self):
fu
"#,
&[CheckCode::F401, CheckCode::F821],
&[CheckCode::F821],
)?;
Ok(())
@@ -2160,7 +2164,7 @@ mod tests {
import fu.bar, fu.bar
fu.bar
"#,
&[CheckCode::F811],
&[CheckCode::F401, CheckCode::F811],
)?;
flakes(
r#"
@@ -2168,7 +2172,7 @@ mod tests {
import fu.bar
fu.bar
"#,
&[CheckCode::F811],
&[CheckCode::F401, CheckCode::F811],
)?;
Ok(())
@@ -2324,7 +2328,7 @@ mod tests {
fu
fu
"#,
&[CheckCode::F811],
&[CheckCode::F401, CheckCode::F811],
)?;
Ok(())
@@ -2337,18 +2341,17 @@ mod tests {
Ok(())
}
#[ignore]
#[test]
fn imported_in_class() -> Result<()> {
// Imports in class scope can be used through self.
flakes(
r#"
class c:
class C:
import i
def __init__(self):
self.i
"#,
&[CheckCode::F401],
&[],
)?;
Ok(())

View File

@@ -14,6 +14,8 @@ mod tests {
#[test_case(CheckCode::PLC0414, Path::new("import_aliasing.py"); "PLC0414")]
#[test_case(CheckCode::PLC2201, Path::new("misplaced_comparison_constant.py"); "PLC2201")]
#[test_case(CheckCode::PLC3002, Path::new("unnecessary_direct_lambda_call.py"); "PLC3002")]
#[test_case(CheckCode::PLE0117, Path::new("nonlocal_without_binding.py"); "PLE0117")]
#[test_case(CheckCode::PLE0118, Path::new("used_prior_global_declaration.py"); "PLE0118")]
#[test_case(CheckCode::PLE1142, Path::new("await_outside_async.py"); "PLE1142")]
#[test_case(CheckCode::PLR0206, Path::new("property_with_parameters.py"); "PLR0206")]
#[test_case(CheckCode::PLR0402, Path::new("import_aliasing.py"); "PLR0402")]
@@ -26,6 +28,7 @@ mod tests {
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_5.py"); "PLR1722_5")]
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_6.py"); "PLR1722_6")]
#[test_case(CheckCode::PLW0120, Path::new("useless_else_on_loop.py"); "PLW0120")]
#[test_case(CheckCode::PLW0602, Path::new("global_variable_not_assigned.py"); "PLW0602")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(

View File

@@ -5,6 +5,7 @@ pub use property_with_parameters::property_with_parameters;
pub use unnecessary_direct_lambda_call::unnecessary_direct_lambda_call;
pub use use_from_import::use_from_import;
pub use use_sys_exit::use_sys_exit;
pub use used_prior_global_declaration::used_prior_global_declaration;
pub use useless_else_on_loop::useless_else_on_loop;
pub use useless_import_alias::useless_import_alias;
@@ -15,5 +16,6 @@ mod property_with_parameters;
mod unnecessary_direct_lambda_call;
mod use_from_import;
mod use_sys_exit;
mod used_prior_global_declaration;
mod useless_else_on_loop;
mod useless_import_alias;

View File

@@ -0,0 +1,23 @@
use rustpython_ast::Expr;
use crate::ast::types::{Range, ScopeKind};
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLE0118
pub fn used_prior_global_declaration(checker: &mut Checker, name: &str, expr: &Expr) {
let globals = match &checker.current_scope().kind {
ScopeKind::Class(class_def) => &class_def.globals,
ScopeKind::Function(function_def) => &function_def.globals,
_ => return,
};
if let Some(stmt) = globals.get(name) {
if expr.location < stmt.location {
checker.add_check(Check::new(
CheckKind::UsedPriorGlobalDeclaration(name.to_string(), stmt.location.row()),
Range::from_located(expr),
));
}
}
}

View File

@@ -0,0 +1,32 @@
---
source: src/pylint/mod.rs
expression: checks
---
- kind:
NonlocalWithoutBinding: x
location:
row: 5
column: 4
end_location:
row: 5
column: 14
fix: ~
- kind:
NonlocalWithoutBinding: y
location:
row: 9
column: 4
end_location:
row: 9
column: 14
fix: ~
- kind:
NonlocalWithoutBinding: y
location:
row: 19
column: 8
end_location:
row: 19
column: 18
fix: ~

View File

@@ -0,0 +1,137 @@
---
source: src/pylint/mod.rs
expression: checks
---
- kind:
UsedPriorGlobalDeclaration:
- x
- 7
location:
row: 5
column: 10
end_location:
row: 5
column: 11
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 17
location:
row: 15
column: 10
end_location:
row: 15
column: 11
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 25
location:
row: 23
column: 10
end_location:
row: 23
column: 11
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 35
location:
row: 33
column: 10
end_location:
row: 33
column: 11
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 43
location:
row: 41
column: 4
end_location:
row: 41
column: 5
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 53
location:
row: 51
column: 4
end_location:
row: 51
column: 5
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 61
location:
row: 59
column: 8
end_location:
row: 59
column: 9
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 71
location:
row: 69
column: 8
end_location:
row: 69
column: 9
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 79
location:
row: 77
column: 8
end_location:
row: 77
column: 9
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 89
location:
row: 87
column: 8
end_location:
row: 87
column: 9
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 97
location:
row: 95
column: 8
end_location:
row: 95
column: 9
fix: ~
- kind:
UsedPriorGlobalDeclaration:
- x
- 107
location:
row: 105
column: 8
end_location:
row: 105
column: 9
fix: ~

View File

@@ -0,0 +1,23 @@
---
source: src/pylint/mod.rs
expression: checks
---
- kind:
GlobalVariableNotAssigned: x
location:
row: 5
column: 4
end_location:
row: 5
column: 12
fix: ~
- kind:
GlobalVariableNotAssigned: x
location:
row: 9
column: 4
end_location:
row: 9
column: 12
fix: ~