Move binding and scope tracking into a separate ast::Context struct (#3298)

This commit is contained in:
Charlie Marsh 2023-03-04 14:01:20 -05:00 committed by GitHub
parent 376ef929b1
commit 40d3b40c14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
128 changed files with 1425 additions and 1174 deletions

View file

@ -0,0 +1,292 @@
use std::path::Path;
use nohash_hasher::IntMap;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Expr, Stmt};
use smallvec::smallvec;
use ruff_python::typing::TYPING_EXTENSIONS;
use crate::ast::helpers::{collect_call_path, from_relative_import, Exceptions};
use crate::ast::types::{Binding, BindingKind, CallPath, ExecutionContext, RefEquality, Scope};
use crate::resolver::is_interface_definition_path;
use crate::visibility::{module_visibility, Modifier, VisibleScope};
#[allow(clippy::struct_excessive_bools)]
pub struct Context<'a> {
pub typing_modules: &'a [String],
pub module_path: Option<Vec<String>>,
// Retain all scopes and parent nodes, along with a stack of indexes to track which are active
// at various points in time.
pub parents: Vec<RefEquality<'a, Stmt>>,
pub depths: FxHashMap<RefEquality<'a, Stmt>, usize>,
pub child_to_parent: FxHashMap<RefEquality<'a, Stmt>, RefEquality<'a, Stmt>>,
// A stack of all bindings created in any scope, at any point in execution.
pub bindings: Vec<Binding<'a>>,
// Map from binding index to indexes of bindings that redefine it in other scopes.
pub redefinitions: IntMap<usize, Vec<usize>>,
pub exprs: Vec<RefEquality<'a, Expr>>,
pub scopes: Vec<Scope<'a>>,
pub scope_stack: Vec<usize>,
pub dead_scopes: Vec<(usize, Vec<usize>)>,
// Body iteration; used to peek at siblings.
pub body: &'a [Stmt],
pub body_index: usize,
// Internal, derivative state.
pub visible_scope: VisibleScope,
pub in_annotation: bool,
pub in_type_definition: bool,
pub in_deferred_string_type_definition: bool,
pub in_deferred_type_definition: bool,
pub in_exception_handler: bool,
pub in_literal: bool,
pub in_subscript: bool,
pub in_type_checking_block: bool,
pub seen_import_boundary: bool,
pub futures_allowed: bool,
pub annotations_future_enabled: bool,
pub handled_exceptions: Vec<Exceptions>,
}
impl<'a> Context<'a> {
pub fn new(
typing_modules: &'a [String],
path: &'a Path,
module_path: Option<Vec<String>>,
) -> Self {
Self {
typing_modules,
module_path,
parents: Vec::default(),
depths: FxHashMap::default(),
child_to_parent: FxHashMap::default(),
bindings: Vec::default(),
redefinitions: IntMap::default(),
exprs: Vec::default(),
scopes: Vec::default(),
scope_stack: Vec::default(),
dead_scopes: Vec::default(),
body: &[],
body_index: 0,
visible_scope: VisibleScope {
modifier: Modifier::Module,
visibility: module_visibility(path),
},
in_annotation: false,
in_type_definition: false,
in_deferred_string_type_definition: false,
in_deferred_type_definition: false,
in_exception_handler: false,
in_literal: false,
in_subscript: false,
in_type_checking_block: false,
seen_import_boundary: false,
futures_allowed: true,
annotations_future_enabled: is_interface_definition_path(path),
handled_exceptions: Vec::default(),
}
}
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
pub fn match_typing_expr(&self, expr: &Expr, target: &str) -> bool {
self.resolve_call_path(expr).map_or(false, |call_path| {
self.match_typing_call_path(&call_path, target)
})
}
/// Return `true` if the call path is a reference to `typing.${target}`.
pub fn match_typing_call_path(&self, call_path: &CallPath, target: &str) -> bool {
if call_path.as_slice() == ["typing", target] {
return true;
}
if TYPING_EXTENSIONS.contains(target) {
if call_path.as_slice() == ["typing_extensions", target] {
return true;
}
}
if self.typing_modules.iter().any(|module| {
let mut module: CallPath = module.split('.').collect();
module.push(target);
*call_path == module
}) {
return true;
}
false
}
/// Return the current `Binding` for a given `name`.
pub fn find_binding(&self, member: &str) -> Option<&Binding> {
self.current_scopes()
.find_map(|scope| scope.bindings.get(member))
.map(|index| &self.bindings[*index])
}
/// Return `true` if `member` is bound as a builtin.
pub fn is_builtin(&self, member: &str) -> bool {
self.find_binding(member)
.map_or(false, |binding| binding.kind.is_builtin())
}
/// Resolves the call path, e.g. if you have a file
///
/// ```python
/// from sys import version_info as python_version
/// print(python_version)
/// ```
///
/// then `python_version` from the print statement will resolve to `sys.version_info`.
pub fn resolve_call_path<'b>(&'a self, value: &'b Expr) -> Option<CallPath<'a>>
where
'b: 'a,
{
let call_path = collect_call_path(value);
let Some(head) = call_path.first() else {
return None;
};
let Some(binding) = self.find_binding(head) else {
return None;
};
match &binding.kind {
BindingKind::Importation(.., name) | BindingKind::SubmoduleImportation(name, ..) => {
if name.starts_with('.') {
if let Some(module) = &self.module_path {
let mut source_path = from_relative_import(module, name);
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
} else {
None
}
} else {
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
}
}
BindingKind::FromImportation(.., name) => {
if name.starts_with('.') {
if let Some(module) = &self.module_path {
let mut source_path = from_relative_import(module, name);
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
} else {
None
}
} else {
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
}
}
BindingKind::Builtin => {
let mut source_path: CallPath = smallvec![];
source_path.push("");
source_path.extend(call_path);
Some(source_path)
}
_ => None,
}
}
pub fn push_parent(&mut self, parent: &'a Stmt) {
let num_existing = self.parents.len();
self.parents.push(RefEquality(parent));
self.depths
.insert(self.parents[num_existing].clone(), num_existing);
if num_existing > 0 {
self.child_to_parent.insert(
self.parents[num_existing].clone(),
self.parents[num_existing - 1].clone(),
);
}
}
pub fn pop_parent(&mut self) {
self.parents.pop().expect("Attempted to pop without parent");
}
pub fn push_expr(&mut self, expr: &'a Expr) {
self.exprs.push(RefEquality(expr));
}
pub fn pop_expr(&mut self) {
self.exprs
.pop()
.expect("Attempted to pop without expression");
}
pub fn push_scope(&mut self, scope: Scope<'a>) {
self.scope_stack.push(self.scopes.len());
self.scopes.push(scope);
}
pub fn pop_scope(&mut self) {
self.dead_scopes.push((
self.scope_stack
.pop()
.expect("Attempted to pop without scope"),
self.scope_stack.clone(),
));
}
/// Return the current `Stmt`.
pub fn current_stmt(&self) -> &RefEquality<'a, Stmt> {
self.parents.iter().rev().next().expect("No parent found")
}
/// Return the parent `Stmt` of the current `Stmt`, if any.
pub fn current_stmt_parent(&self) -> Option<&RefEquality<'a, Stmt>> {
self.parents.iter().rev().nth(1)
}
/// Return the parent `Expr` of the current `Expr`.
pub fn current_expr_parent(&self) -> Option<&RefEquality<'a, Expr>> {
self.exprs.iter().rev().nth(1)
}
/// Return the grandparent `Expr` of the current `Expr`.
pub fn current_expr_grandparent(&self) -> Option<&RefEquality<'a, Expr>> {
self.exprs.iter().rev().nth(2)
}
/// Return the `Stmt` that immediately follows the current `Stmt`, if any.
pub fn current_sibling_stmt(&self) -> Option<&'a Stmt> {
self.body.get(self.body_index + 1)
}
pub fn current_scope(&self) -> &Scope {
&self.scopes[*(self.scope_stack.last().expect("No current scope found"))]
}
pub fn current_scope_parent(&self) -> Option<&Scope> {
self.scope_stack
.iter()
.rev()
.nth(1)
.map(|index| &self.scopes[*index])
}
pub fn current_scopes(&self) -> impl Iterator<Item = &Scope> {
self.scope_stack
.iter()
.rev()
.map(|index| &self.scopes[*index])
}
pub const fn in_exception_handler(&self) -> bool {
self.in_exception_handler
}
pub const fn execution_context(&self) -> ExecutionContext {
if self.in_type_checking_block
|| self.in_annotation
|| self.in_deferred_string_type_definition
{
ExecutionContext::Typing
} else {
ExecutionContext::Runtime
}
}
}

View file

@ -1,8 +1,8 @@
use rustpython_parser::ast::Expr; use rustpython_parser::ast::Expr;
use crate::ast::context::Context;
use crate::ast::helpers::{map_callable, to_call_path}; use crate::ast::helpers::{map_callable, to_call_path};
use crate::ast::types::{Scope, ScopeKind}; use crate::ast::types::{Scope, ScopeKind};
use crate::checkers::ast::Checker;
const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"]; const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"];
const METACLASS_BASES: [(&str, &str); 2] = [("", "type"), ("abc", "ABCMeta")]; const METACLASS_BASES: [(&str, &str); 2] = [("", "type"), ("abc", "ABCMeta")];
@ -16,7 +16,7 @@ pub enum FunctionType {
/// Classify a function based on its scope, name, and decorators. /// Classify a function based on its scope, name, and decorators.
pub fn classify( pub fn classify(
checker: &Checker, ctx: &Context,
scope: &Scope, scope: &Scope,
name: &str, name: &str,
decorator_list: &[Expr], decorator_list: &[Expr],
@ -29,8 +29,7 @@ pub fn classify(
if decorator_list.iter().any(|expr| { if decorator_list.iter().any(|expr| {
// The method is decorated with a static method decorator (like // The method is decorated with a static method decorator (like
// `@staticmethod`). // `@staticmethod`).
checker ctx.resolve_call_path(map_callable(expr))
.resolve_call_path(map_callable(expr))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["", "staticmethod"] call_path.as_slice() == ["", "staticmethod"]
|| staticmethod_decorators || staticmethod_decorators
@ -43,7 +42,7 @@ pub fn classify(
// Special-case class method, like `__new__`. // Special-case class method, like `__new__`.
|| scope.bases.iter().any(|expr| { || scope.bases.iter().any(|expr| {
// The class itself extends a known metaclass, so all methods are class methods. // The class itself extends a known metaclass, so all methods are class methods.
checker.resolve_call_path(map_callable(expr)).map_or(false, |call_path| { ctx.resolve_call_path(map_callable(expr)).map_or(false, |call_path| {
METACLASS_BASES METACLASS_BASES
.iter() .iter()
.any(|(module, member)| call_path.as_slice() == [*module, *member]) .any(|(module, member)| call_path.as_slice() == [*module, *member])
@ -51,7 +50,7 @@ pub fn classify(
}) })
|| decorator_list.iter().any(|expr| { || decorator_list.iter().any(|expr| {
// The method is decorated with a class method decorator (like `@classmethod`). // The method is decorated with a class method decorator (like `@classmethod`).
checker.resolve_call_path(map_callable(expr)).map_or(false, |call_path| { ctx.resolve_call_path(map_callable(expr)).map_or(false, |call_path| {
call_path.as_slice() == ["", "classmethod"] || call_path.as_slice() == ["", "classmethod"] ||
classmethod_decorators classmethod_decorators
.iter() .iter()

View file

@ -13,10 +13,10 @@ use rustpython_parser::ast::{
use rustpython_parser::{lexer, Mode, StringKind, Tok}; use rustpython_parser::{lexer, Mode, StringKind, Tok};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use crate::ast::context::Context;
use crate::ast::types::{Binding, BindingKind, CallPath, Range}; use crate::ast::types::{Binding, BindingKind, CallPath, Range};
use crate::ast::visitor; use crate::ast::visitor;
use crate::ast::visitor::Visitor; use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
use crate::source_code::{Generator, Indexer, Locator, Stylist}; use crate::source_code::{Generator, Indexer, Locator, Stylist};
/// Create an `Expr` with default location from an `ExprKind`. /// Create an `Expr` with default location from an `ExprKind`.
@ -99,17 +99,16 @@ pub fn format_call_path(call_path: &[&str]) -> String {
} }
/// Return `true` if the `Expr` contains a reference to `${module}.${target}`. /// Return `true` if the `Expr` contains a reference to `${module}.${target}`.
pub fn contains_call_path(checker: &Checker, expr: &Expr, target: &[&str]) -> bool { pub fn contains_call_path(ctx: &Context, expr: &Expr, target: &[&str]) -> bool {
any_over_expr(expr, &|expr| { any_over_expr(expr, &|expr| {
checker ctx.resolve_call_path(expr)
.resolve_call_path(expr)
.map_or(false, |call_path| call_path.as_slice() == target) .map_or(false, |call_path| call_path.as_slice() == target)
}) })
} }
/// Return `true` if the `Expr` contains an expression that appears to include a /// Return `true` if the `Expr` contains an expression that appears to include a
/// side-effect (like a function call). /// side-effect (like a function call).
pub fn contains_effect(checker: &Checker, expr: &Expr) -> bool { pub fn contains_effect(ctx: &Context, expr: &Expr) -> bool {
any_over_expr(expr, &|expr| { any_over_expr(expr, &|expr| {
// Accept empty initializers. // Accept empty initializers.
if let ExprKind::Call { if let ExprKind::Call {
@ -125,7 +124,7 @@ pub fn contains_effect(checker: &Checker, expr: &Expr) -> bool {
|| id == "tuple" || id == "tuple"
|| id == "dict" || id == "dict"
|| id == "frozenset") || id == "frozenset")
&& checker.is_builtin(id); && ctx.is_builtin(id);
return !is_empty_initializer; return !is_empty_initializer;
} }
} }
@ -669,10 +668,10 @@ pub fn has_comments_in(range: Range, locator: &Locator) -> bool {
} }
/// Return `true` if the body uses `locals()`, `globals()`, `vars()`, `eval()`. /// Return `true` if the body uses `locals()`, `globals()`, `vars()`, `eval()`.
pub fn uses_magic_variable_access(checker: &Checker, body: &[Stmt]) -> bool { pub fn uses_magic_variable_access(ctx: &Context, body: &[Stmt]) -> bool {
any_over_body(body, &|expr| { any_over_body(body, &|expr| {
if let ExprKind::Call { func, .. } = &expr.node { if let ExprKind::Call { func, .. } = &expr.node {
checker.resolve_call_path(func).map_or(false, |call_path| { ctx.resolve_call_path(func).map_or(false, |call_path| {
call_path.as_slice() == ["", "locals"] call_path.as_slice() == ["", "locals"]
|| call_path.as_slice() == ["", "globals"] || call_path.as_slice() == ["", "globals"]
|| call_path.as_slice() == ["", "vars"] || call_path.as_slice() == ["", "vars"]

View file

@ -1,6 +1,7 @@
pub mod branch_detection; pub mod branch_detection;
pub mod cast; pub mod cast;
pub mod comparable; pub mod comparable;
pub mod context;
pub mod function_type; pub mod function_type;
pub mod hashable; pub mod hashable;
pub mod helpers; pub mod helpers;

View file

@ -3,11 +3,11 @@ use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind, Located, Stmt, StmtKind}; use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind, Located, Stmt, StmtKind};
use rustpython_parser::{lexer, Mode, Tok}; use rustpython_parser::{lexer, Mode, Tok};
use crate::ast::context::Context;
use crate::ast::helpers::any_over_expr; use crate::ast::helpers::any_over_expr;
use crate::ast::types::{BindingKind, Scope}; use crate::ast::types::{BindingKind, Scope};
use crate::ast::visitor; use crate::ast::visitor;
use crate::ast::visitor::Visitor; use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
bitflags! { bitflags! {
#[derive(Default)] #[derive(Default)]
@ -19,7 +19,7 @@ bitflags! {
/// Extract the names bound to a given __all__ assignment. /// Extract the names bound to a given __all__ assignment.
pub fn extract_all_names( pub fn extract_all_names(
checker: &Checker, ctx: &Context,
stmt: &Stmt, stmt: &Stmt,
scope: &Scope, scope: &Scope,
) -> (Vec<String>, AllNamesFlags) { ) -> (Vec<String>, AllNamesFlags) {
@ -38,7 +38,7 @@ pub fn extract_all_names(
} }
fn extract_elts<'a>( fn extract_elts<'a>(
checker: &'a Checker, ctx: &'a Context,
expr: &'a Expr, expr: &'a Expr,
) -> (Option<&'a Vec<Expr>>, AllNamesFlags) { ) -> (Option<&'a Vec<Expr>>, AllNamesFlags) {
match &expr.node { match &expr.node {
@ -60,7 +60,7 @@ pub fn extract_all_names(
} => { } => {
// Allow `tuple()` and `list()` calls. // Allow `tuple()` and `list()` calls.
if keywords.is_empty() && args.len() <= 1 { if keywords.is_empty() && args.len() <= 1 {
if checker.resolve_call_path(func).map_or(false, |call_path| { if ctx.resolve_call_path(func).map_or(false, |call_path| {
call_path.as_slice() == ["", "tuple"] call_path.as_slice() == ["", "tuple"]
|| call_path.as_slice() == ["", "list"] || call_path.as_slice() == ["", "list"]
}) { }) {
@ -96,7 +96,7 @@ pub fn extract_all_names(
// Grab the existing bound __all__ values. // Grab the existing bound __all__ values.
if let StmtKind::AugAssign { .. } = &stmt.node { if let StmtKind::AugAssign { .. } = &stmt.node {
if let Some(index) = scope.bindings.get("__all__") { if let Some(index) = scope.bindings.get("__all__") {
if let BindingKind::Export(existing) = &checker.bindings[*index].kind { if let BindingKind::Export(existing) = &ctx.bindings[*index].kind {
names.extend_from_slice(existing); names.extend_from_slice(existing);
} }
} }
@ -113,7 +113,7 @@ pub fn extract_all_names(
let mut current_right = right; let mut current_right = right;
loop { loop {
// Process the right side, which should be a "real" value. // Process the right side, which should be a "real" value.
let (elts, new_flags) = extract_elts(checker, current_right); let (elts, new_flags) = extract_elts(ctx, current_right);
flags |= new_flags; flags |= new_flags;
if let Some(elts) = elts { if let Some(elts) = elts {
add_to_names(&mut names, elts, &mut flags); add_to_names(&mut names, elts, &mut flags);
@ -125,7 +125,7 @@ pub fn extract_all_names(
current_left = left; current_left = left;
current_right = right; current_right = right;
} else { } else {
let (elts, new_flags) = extract_elts(checker, current_left); let (elts, new_flags) = extract_elts(ctx, current_left);
flags |= new_flags; flags |= new_flags;
if let Some(elts) = elts { if let Some(elts) = elts {
add_to_names(&mut names, elts, &mut flags); add_to_names(&mut names, elts, &mut flags);
@ -134,7 +134,7 @@ pub fn extract_all_names(
} }
} }
} else { } else {
let (elts, new_flags) = extract_elts(checker, value); let (elts, new_flags) = extract_elts(ctx, value);
flags |= new_flags; flags |= new_flags;
if let Some(elts) = elts { if let Some(elts) = elts {
add_to_names(&mut names, elts, &mut flags); add_to_names(&mut names, elts, &mut flags);

View file

@ -0,0 +1,23 @@
use rustpython_parser::ast::{Expr, Stmt};
use crate::ast::types::RefEquality;
use crate::checkers::ast::AnnotationContext;
use crate::docstrings::definition::Definition;
use crate::visibility::{Visibility, VisibleScope};
use crate::Range;
type Context<'a> = (Vec<usize>, Vec<RefEquality<'a, Stmt>>);
/// A collection of AST nodes that are deferred for later analysis.
/// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all
/// module-level definitions have been analyzed.
#[derive(Default)]
pub struct Deferred<'a> {
pub definitions: Vec<(Definition<'a>, Visibility, Context<'a>)>,
pub string_type_definitions: Vec<(Range, &'a str, AnnotationContext, Context<'a>)>,
pub type_definitions: Vec<(&'a Expr, AnnotationContext, Context<'a>)>,
pub functions: Vec<(&'a Stmt, Context<'a>, VisibleScope)>,
pub lambdas: Vec<(&'a Expr, Context<'a>)>,
pub for_loops: Vec<(&'a Stmt, Context<'a>)>,
pub assignments: Vec<Context<'a>>,
}

View file

@ -115,6 +115,7 @@ impl Violation for SysVersionSlice1Referenced {
fn is_sys(checker: &Checker, expr: &Expr, target: &str) -> bool { fn is_sys(checker: &Checker, expr: &Expr, target: &str) -> bool {
checker checker
.ctx
.resolve_call_path(expr) .resolve_call_path(expr)
.map_or(false, |call_path| call_path.as_slice() == ["sys", target]) .map_or(false, |call_path| call_path.as_slice() == ["sys", target])
} }
@ -306,6 +307,7 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
/// YTT202 /// YTT202
pub fn name_or_attribute(checker: &mut Checker, expr: &Expr) { pub fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
if checker if checker
.ctx
.resolve_call_path(expr) .resolve_call_path(expr)
.map_or(false, |call_path| call_path.as_slice() == ["six", "PY3"]) .map_or(false, |call_path| call_path.as_slice() == ["six", "PY3"])
{ {

View file

@ -33,7 +33,7 @@ pub fn overloaded_name(checker: &Checker, definition: &Definition) -> Option<Str
| DefinitionKind::NestedFunction(stmt) | DefinitionKind::NestedFunction(stmt)
| DefinitionKind::Method(stmt) = definition.kind | DefinitionKind::Method(stmt) = definition.kind
{ {
if visibility::is_overload(checker, cast::decorator_list(stmt)) { if visibility::is_overload(&checker.ctx, cast::decorator_list(stmt)) {
let (name, ..) = match_function_def(stmt); let (name, ..) = match_function_def(stmt);
Some(name.to_string()) Some(name.to_string())
} else { } else {
@ -51,7 +51,7 @@ pub fn is_overload_impl(checker: &Checker, definition: &Definition, overloaded_n
| DefinitionKind::NestedFunction(stmt) | DefinitionKind::NestedFunction(stmt)
| DefinitionKind::Method(stmt) = definition.kind | DefinitionKind::Method(stmt) = definition.kind
{ {
if visibility::is_overload(checker, cast::decorator_list(stmt)) { if visibility::is_overload(&checker.ctx, cast::decorator_list(stmt)) {
false false
} else { } else {
let (name, ..) = match_function_def(stmt); let (name, ..) = match_function_def(stmt);

View file

@ -441,7 +441,7 @@ fn check_dynamically_typed<F>(
) where ) where
F: FnOnce() -> String, F: FnOnce() -> String,
{ {
if checker.match_typing_expr(annotation, "Any") { if checker.ctx.match_typing_expr(annotation, "Any") {
diagnostics.push(Diagnostic::new( diagnostics.push(Diagnostic::new(
AnyType { name: func() }, AnyType { name: func() },
Range::from_located(annotation), Range::from_located(annotation),
@ -482,7 +482,8 @@ pub fn definition(
.skip( .skip(
// If this is a non-static method, skip `cls` or `self`. // If this is a non-static method, skip `cls` or `self`.
usize::from( usize::from(
is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)), is_method
&& !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)),
), ),
) )
{ {
@ -580,10 +581,10 @@ pub fn definition(
} }
// ANN101, ANN102 // ANN101, ANN102
if is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)) { if is_method && !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)) {
if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) { if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) {
if arg.node.annotation.is_none() { if arg.node.annotation.is_none() {
if visibility::is_classmethod(checker, cast::decorator_list(stmt)) { if visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
if checker.settings.rules.enabled(&Rule::MissingTypeCls) { if checker.settings.rules.enabled(&Rule::MissingTypeCls) {
diagnostics.push(Diagnostic::new( diagnostics.push(Diagnostic::new(
MissingTypeCls { MissingTypeCls {
@ -619,7 +620,7 @@ pub fn definition(
// (explicitly or implicitly). // (explicitly or implicitly).
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body) checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
) { ) {
if is_method && visibility::is_classmethod(checker, cast::decorator_list(stmt)) { if is_method && visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
if checker if checker
.settings .settings
.rules .rules
@ -632,7 +633,8 @@ pub fn definition(
helpers::identifier_range(stmt, checker.locator), helpers::identifier_range(stmt, checker.locator),
)); ));
} }
} else if is_method && visibility::is_staticmethod(checker, cast::decorator_list(stmt)) } else if is_method
&& visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt))
{ {
if checker if checker
.settings .settings

View file

@ -26,16 +26,22 @@ pub fn is_untyped_exception(type_: Option<&Expr>, checker: &Checker) -> bool {
type_.map_or(true, |type_| { type_.map_or(true, |type_| {
if let ExprKind::Tuple { elts, .. } = &type_.node { if let ExprKind::Tuple { elts, .. } = &type_.node {
elts.iter().any(|type_| { elts.iter().any(|type_| {
checker.resolve_call_path(type_).map_or(false, |call_path| { checker
.ctx
.resolve_call_path(type_)
.map_or(false, |call_path| {
call_path.as_slice() == ["", "Exception"]
|| call_path.as_slice() == ["", "BaseException"]
})
})
} else {
checker
.ctx
.resolve_call_path(type_)
.map_or(false, |call_path| {
call_path.as_slice() == ["", "Exception"] call_path.as_slice() == ["", "Exception"]
|| call_path.as_slice() == ["", "BaseException"] || call_path.as_slice() == ["", "BaseException"]
}) })
})
} else {
checker.resolve_call_path(type_).map_or(false, |call_path| {
call_path.as_slice() == ["", "Exception"]
|| call_path.as_slice() == ["", "BaseException"]
})
} }
}) })
} }

View file

@ -103,6 +103,7 @@ pub fn bad_file_permissions(
keywords: &[Keyword], keywords: &[Keyword],
) { ) {
if checker if checker
.ctx
.resolve_call_path(func) .resolve_call_path(func)
.map_or(false, |call_path| call_path.as_slice() == ["os", "chmod"]) .map_or(false, |call_path| call_path.as_slice() == ["os", "chmod"])
{ {

View file

@ -60,7 +60,7 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio
op: Operator::Add | Operator::Mod, op: Operator::Add | Operator::Mod,
.. ..
} => { } => {
let Some(parent) = checker.current_expr_parent() else { let Some(parent) = checker.ctx.current_expr_parent() else {
if any_over_expr(expr, &has_string_literal) { if any_over_expr(expr, &has_string_literal) {
return Some(unparse_expr(expr, checker.stylist)); return Some(unparse_expr(expr, checker.stylist));
} }

View file

@ -48,7 +48,7 @@ pub fn hashlib_insecure_hash_functions(
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
) { ) {
if let Some(hashlib_call) = checker.resolve_call_path(func).and_then(|call_path| { if let Some(hashlib_call) = checker.ctx.resolve_call_path(func).and_then(|call_path| {
if call_path.as_slice() == ["hashlib", "new"] { if call_path.as_slice() == ["hashlib", "new"] {
Some(HashlibCall::New) Some(HashlibCall::New)
} else { } else {

View file

@ -37,9 +37,13 @@ pub fn jinja2_autoescape_false(
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
) { ) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["jinja2", "Environment"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["jinja2", "Environment"]
})
{
let call_args = SimpleCallArgs::new(args, keywords); let call_args = SimpleCallArgs::new(args, keywords);
if let Some(autoescape_arg) = call_args.get_argument("autoescape", None) { if let Some(autoescape_arg) = call_args.get_argument("autoescape", None) {

View file

@ -24,9 +24,13 @@ pub fn logging_config_insecure_listen(
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
) { ) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["logging", "config", "listen"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["logging", "config", "listen"]
})
{
let call_args = SimpleCallArgs::new(args, keywords); let call_args = SimpleCallArgs::new(args, keywords);
if call_args.get_argument("verify", None).is_none() { if call_args.get_argument("verify", None).is_none() {

View file

@ -44,7 +44,7 @@ pub fn request_with_no_cert_validation(
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
) { ) {
if let Some(target) = checker.resolve_call_path(func).and_then(|call_path| { if let Some(target) = checker.ctx.resolve_call_path(func).and_then(|call_path| {
if call_path.len() == 2 { if call_path.len() == 2 {
if call_path[0] == "requests" && REQUESTS_HTTP_VERBS.contains(&call_path[1]) { if call_path[0] == "requests" && REQUESTS_HTTP_VERBS.contains(&call_path[1]) {
return Some("requests"); return Some("requests");

View file

@ -34,11 +34,15 @@ pub fn request_without_timeout(
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
) { ) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
HTTP_VERBS .ctx
.iter() .resolve_call_path(func)
.any(|func_name| call_path.as_slice() == ["requests", func_name]) .map_or(false, |call_path| {
}) { HTTP_VERBS
.iter()
.any(|func_name| call_path.as_slice() == ["requests", func_name])
})
{
let call_args = SimpleCallArgs::new(args, keywords); let call_args = SimpleCallArgs::new(args, keywords);
if let Some(timeout_arg) = call_args.get_argument("timeout", None) { if let Some(timeout_arg) = call_args.get_argument("timeout", None) {
if let Some(timeout) = match &timeout_arg.node { if let Some(timeout) = match &timeout_arg.node {

View file

@ -25,9 +25,13 @@ pub fn snmp_insecure_version(
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
) { ) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["pysnmp", "hlapi", "CommunityData"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["pysnmp", "hlapi", "CommunityData"]
})
{
let call_args = SimpleCallArgs::new(args, keywords); let call_args = SimpleCallArgs::new(args, keywords);
if let Some(mp_model_arg) = call_args.get_argument("mpModel", None) { if let Some(mp_model_arg) = call_args.get_argument("mpModel", None) {
if let ExprKind::Constant { if let ExprKind::Constant {

View file

@ -27,9 +27,13 @@ pub fn snmp_weak_cryptography(
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
) { ) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["pysnmp", "hlapi", "UsmUserData"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["pysnmp", "hlapi", "UsmUserData"]
})
{
let call_args = SimpleCallArgs::new(args, keywords); let call_args = SimpleCallArgs::new(args, keywords);
if call_args.len() < 3 { if call_args.len() < 3 {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -34,12 +34,14 @@ impl Violation for UnsafeYAMLLoad {
/// S506 /// S506
pub fn unsafe_yaml_load(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: &[Keyword]) { pub fn unsafe_yaml_load(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: &[Keyword]) {
if checker if checker
.ctx
.resolve_call_path(func) .resolve_call_path(func)
.map_or(false, |call_path| call_path.as_slice() == ["yaml", "load"]) .map_or(false, |call_path| call_path.as_slice() == ["yaml", "load"])
{ {
let call_args = SimpleCallArgs::new(args, keywords); let call_args = SimpleCallArgs::new(args, keywords);
if let Some(loader_arg) = call_args.get_argument("Loader", Some(1)) { if let Some(loader_arg) = call_args.get_argument("Loader", Some(1)) {
if !checker if !checker
.ctx
.resolve_call_path(loader_arg) .resolve_call_path(loader_arg)
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["yaml", "SafeLoader"] call_path.as_slice() == ["yaml", "SafeLoader"]

View file

@ -35,7 +35,7 @@ pub fn blind_except(
return; return;
}; };
for exception in ["BaseException", "Exception"] { for exception in ["BaseException", "Exception"] {
if id == exception && checker.is_builtin(exception) { if id == exception && checker.ctx.is_builtin(exception) {
// If the exception is re-raised, don't flag an error. // If the exception is re-raised, don't flag an error.
if body.iter().any(|stmt| { if body.iter().any(|stmt| {
if let StmtKind::Raise { exc, .. } = &stmt.node { if let StmtKind::Raise { exc, .. } = &stmt.node {

View file

@ -42,12 +42,14 @@ fn is_abc_class(checker: &Checker, bases: &[Expr], keywords: &[Keyword]) -> bool
.as_ref() .as_ref()
.map_or(false, |arg| arg == "metaclass") .map_or(false, |arg| arg == "metaclass")
&& checker && checker
.ctx
.resolve_call_path(&keyword.node.value) .resolve_call_path(&keyword.node.value)
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["abc", "ABCMeta"] call_path.as_slice() == ["abc", "ABCMeta"]
}) })
}) || bases.iter().any(|base| { }) || bases.iter().any(|base| {
checker checker
.ctx
.resolve_call_path(base) .resolve_call_path(base)
.map_or(false, |call_path| call_path.as_slice() == ["abc", "ABC"]) .map_or(false, |call_path| call_path.as_slice() == ["abc", "ABC"])
}) })
@ -106,7 +108,7 @@ pub fn abstract_base_class(
continue; continue;
}; };
let has_abstract_decorator = is_abstract(checker, decorator_list); let has_abstract_decorator = is_abstract(&checker.ctx, decorator_list);
has_abstract_method |= has_abstract_decorator; has_abstract_method |= has_abstract_decorator;
if !checker if !checker
@ -117,7 +119,10 @@ pub fn abstract_base_class(
continue; continue;
} }
if !has_abstract_decorator && is_empty_body(body) && !is_overload(checker, decorator_list) { if !has_abstract_decorator
&& is_empty_body(body)
&& !is_overload(&checker.ctx, decorator_list)
{
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
EmptyMethodWithoutAbstractDecorator { EmptyMethodWithoutAbstractDecorator {
name: format!("{name}.{method_name}"), name: format!("{name}.{method_name}"),

View file

@ -54,6 +54,7 @@ pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[With
return; return;
} }
if !checker if !checker
.ctx
.resolve_call_path(args.first().unwrap()) .resolve_call_path(args.first().unwrap())
.map_or(false, |call_path| call_path.as_slice() == ["", "Exception"]) .map_or(false, |call_path| call_path.as_slice() == ["", "Exception"])
{ {

View file

@ -19,15 +19,18 @@ impl Violation for CachedInstanceMethod {
} }
fn is_cache_func(checker: &Checker, expr: &Expr) -> bool { fn is_cache_func(checker: &Checker, expr: &Expr) -> bool {
checker.resolve_call_path(expr).map_or(false, |call_path| { checker
call_path.as_slice() == ["functools", "lru_cache"] .ctx
|| call_path.as_slice() == ["functools", "cache"] .resolve_call_path(expr)
}) .map_or(false, |call_path| {
call_path.as_slice() == ["functools", "lru_cache"]
|| call_path.as_slice() == ["functools", "cache"]
})
} }
/// B019 /// B019
pub fn cached_instance_method(checker: &mut Checker, decorator_list: &[Expr]) { pub fn cached_instance_method(checker: &mut Checker, decorator_list: &[Expr]) {
if !matches!(checker.current_scope().kind, ScopeKind::Class(_)) { if !matches!(checker.ctx.current_scope().kind, ScopeKind::Class(_)) {
return; return;
} }
for decorator in decorator_list { for decorator in decorator_list {

View file

@ -38,14 +38,17 @@ const IMMUTABLE_FUNCS: &[&[&str]] = &[
]; ];
fn is_immutable_func(checker: &Checker, func: &Expr, extend_immutable_calls: &[CallPath]) -> bool { fn is_immutable_func(checker: &Checker, func: &Expr, extend_immutable_calls: &[CallPath]) -> bool {
checker.resolve_call_path(func).map_or(false, |call_path| { checker
IMMUTABLE_FUNCS .ctx
.iter() .resolve_call_path(func)
.any(|target| call_path.as_slice() == *target) .map_or(false, |call_path| {
|| extend_immutable_calls IMMUTABLE_FUNCS
.iter() .iter()
.any(|target| call_path == *target) .any(|target| call_path.as_slice() == *target)
}) || extend_immutable_calls
.iter()
.any(|target| call_path == *target)
})
} }
struct ArgumentDefaultVisitor<'a> { struct ArgumentDefaultVisitor<'a> {

View file

@ -67,11 +67,14 @@ const IMMUTABLE_GENERIC_TYPES: &[&[&str]] = &[
]; ];
pub fn is_mutable_func(checker: &Checker, func: &Expr) -> bool { pub fn is_mutable_func(checker: &Checker, func: &Expr) -> bool {
checker.resolve_call_path(func).map_or(false, |call_path| { checker
MUTABLE_FUNCS .ctx
.iter() .resolve_call_path(func)
.any(|target| call_path.as_slice() == *target) .map_or(false, |call_path| {
}) MUTABLE_FUNCS
.iter()
.any(|target| call_path.as_slice() == *target)
})
} }
fn is_mutable_expr(checker: &Checker, expr: &Expr) -> bool { fn is_mutable_expr(checker: &Checker, expr: &Expr) -> bool {
@ -89,40 +92,44 @@ fn is_mutable_expr(checker: &Checker, expr: &Expr) -> bool {
fn is_immutable_annotation(checker: &Checker, expr: &Expr) -> bool { fn is_immutable_annotation(checker: &Checker, expr: &Expr) -> bool {
match &expr.node { match &expr.node {
ExprKind::Name { .. } | ExprKind::Attribute { .. } => { ExprKind::Name { .. } | ExprKind::Attribute { .. } => checker
checker.resolve_call_path(expr).map_or(false, |call_path| { .ctx
.resolve_call_path(expr)
.map_or(false, |call_path| {
IMMUTABLE_TYPES IMMUTABLE_TYPES
.iter() .iter()
.chain(IMMUTABLE_GENERIC_TYPES) .chain(IMMUTABLE_GENERIC_TYPES)
.any(|target| call_path.as_slice() == *target) .any(|target| call_path.as_slice() == *target)
}) }),
}
ExprKind::Subscript { value, slice, .. } => { ExprKind::Subscript { value, slice, .. } => {
checker.resolve_call_path(value).map_or(false, |call_path| { checker
if IMMUTABLE_GENERIC_TYPES .ctx
.iter() .resolve_call_path(value)
.any(|target| call_path.as_slice() == *target) .map_or(false, |call_path| {
{ if IMMUTABLE_GENERIC_TYPES
true .iter()
} else if call_path.as_slice() == ["typing", "Union"] { .any(|target| call_path.as_slice() == *target)
if let ExprKind::Tuple { elts, .. } = &slice.node { {
elts.iter().all(|elt| is_immutable_annotation(checker, elt)) true
} else if call_path.as_slice() == ["typing", "Union"] {
if let ExprKind::Tuple { elts, .. } = &slice.node {
elts.iter().all(|elt| is_immutable_annotation(checker, elt))
} else {
false
}
} else if call_path.as_slice() == ["typing", "Optional"] {
is_immutable_annotation(checker, slice)
} else if call_path.as_slice() == ["typing", "Annotated"] {
if let ExprKind::Tuple { elts, .. } = &slice.node {
elts.first()
.map_or(false, |elt| is_immutable_annotation(checker, elt))
} else {
false
}
} else { } else {
false false
} }
} else if call_path.as_slice() == ["typing", "Optional"] { })
is_immutable_annotation(checker, slice)
} else if call_path.as_slice() == ["typing", "Annotated"] {
if let ExprKind::Tuple { elts, .. } = &slice.node {
elts.first()
.map_or(false, |elt| is_immutable_annotation(checker, elt))
} else {
false
}
} else {
false
}
})
} }
ExprKind::BinOp { ExprKind::BinOp {
left, left,

View file

@ -75,7 +75,7 @@ pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
// We can only replace a `setattr` call (which is an `Expr`) with an assignment // We can only replace a `setattr` call (which is an `Expr`) with an assignment
// (which is a `Stmt`) if the `Expr` is already being used as a `Stmt` // (which is a `Stmt`) if the `Expr` is already being used as a `Stmt`
// (i.e., it's directly within an `StmtKind::Expr`). // (i.e., it's directly within an `StmtKind::Expr`).
if let StmtKind::Expr { value: child } = &checker.current_stmt().node { if let StmtKind::Expr { value: child } = &checker.ctx.current_stmt().node {
if expr == child.as_ref() { if expr == child.as_ref() {
let mut diagnostic = Diagnostic::new(SetAttrWithConstant, Range::from_located(expr)); let mut diagnostic = Diagnostic::new(SetAttrWithConstant, Range::from_located(expr));

View file

@ -140,7 +140,7 @@ pub fn unused_loop_control_variable(
} }
// Avoid fixing any variables that _may_ be used, but undetectably so. // Avoid fixing any variables that _may_ be used, but undetectably so.
let certainty = Certainty::from(!helpers::uses_magic_variable_access(checker, body)); let certainty = Certainty::from(!helpers::uses_magic_variable_access(&checker.ctx, body));
// Attempt to rename the variable by prepending an underscore, but avoid // Attempt to rename the variable by prepending an underscore, but avoid
// applying the fix if doing so wouldn't actually cause us to ignore the // applying the fix if doing so wouldn't actually cause us to ignore the
@ -163,14 +163,14 @@ pub fn unused_loop_control_variable(
if let Some(rename) = rename { if let Some(rename) = rename {
if certainty.into() && checker.patch(diagnostic.kind.rule()) { if certainty.into() && checker.patch(diagnostic.kind.rule()) {
// Find the `BindingKind::LoopVar` corresponding to the name. // Find the `BindingKind::LoopVar` corresponding to the name.
let scope = checker.current_scope(); let scope = checker.ctx.current_scope();
let binding = scope let binding = scope
.bindings .bindings
.get(name) .get(name)
.into_iter() .into_iter()
.chain(scope.rebounds.get(name).into_iter().flatten()) .chain(scope.rebounds.get(name).into_iter().flatten())
.find_map(|index| { .find_map(|index| {
let binding = &checker.bindings[*index]; let binding = &checker.ctx.bindings[*index];
binding binding
.source .source
.as_ref() .as_ref()

View file

@ -22,9 +22,12 @@ impl Violation for UselessContextlibSuppress {
/// B022 /// B022
pub fn useless_contextlib_suppress(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) { pub fn useless_contextlib_suppress(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if args.is_empty() if args.is_empty()
&& checker.resolve_call_path(func).map_or(false, |call_path| { && checker
call_path.as_slice() == ["contextlib", "suppress"] .ctx
}) .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["contextlib", "suppress"]
})
{ {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
UselessContextlibSuppress, UselessContextlibSuppress,

View file

@ -25,7 +25,7 @@ pub fn zip_without_explicit_strict(
) { ) {
if let ExprKind::Name { id, .. } = &func.node { if let ExprKind::Name { id, .. } = &func.node {
if id == "zip" if id == "zip"
&& checker.is_builtin("zip") && checker.ctx.is_builtin("zip")
&& !kwargs.iter().any(|keyword| { && !kwargs.iter().any(|keyword| {
keyword keyword
.node .node

View file

@ -75,7 +75,7 @@ pub fn unnecessary_call_around_sorted(
if inner != "sorted" { if inner != "sorted" {
return; return;
} }
if !checker.is_builtin(inner) || !checker.is_builtin(outer) { if !checker.ctx.is_builtin(inner) || !checker.ctx.is_builtin(outer) {
return; return;
} }
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(

View file

@ -55,7 +55,7 @@ pub fn unnecessary_collection_call(
} }
_ => return, _ => return,
}; };
if !checker.is_builtin(id) { if !checker.ctx.is_builtin(id) {
return; return;
} }
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(

View file

@ -56,7 +56,7 @@ pub fn unnecessary_comprehension(
ExprKind::SetComp { .. } => "set", ExprKind::SetComp { .. } => "set",
_ => return, _ => return,
}; };
if !checker.is_builtin(id) { if !checker.ctx.is_builtin(id) {
return; return;
} }
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(

View file

@ -100,7 +100,7 @@ pub fn unnecessary_double_cast_or_process(
let Some(inner) = helpers::function_name(func) else { let Some(inner) = helpers::function_name(func) else {
return; return;
}; };
if !checker.is_builtin(inner) || !checker.is_builtin(outer) { if !checker.ctx.is_builtin(inner) || !checker.ctx.is_builtin(outer) {
return; return;
} }

View file

@ -52,7 +52,7 @@ pub fn unnecessary_generator_list(
let Some(argument) = helpers::exactly_one_argument_with_matching_function("list", func, args, keywords) else { let Some(argument) = helpers::exactly_one_argument_with_matching_function("list", func, args, keywords) else {
return; return;
}; };
if !checker.is_builtin("list") { if !checker.ctx.is_builtin("list") {
return; return;
} }
if let ExprKind::GeneratorExp { .. } = argument { if let ExprKind::GeneratorExp { .. } = argument {

View file

@ -53,7 +53,7 @@ pub fn unnecessary_generator_set(
let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else { let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else {
return; return;
}; };
if !checker.is_builtin("set") { if !checker.ctx.is_builtin("set") {
return; return;
} }
if let ExprKind::GeneratorExp { .. } = argument { if let ExprKind::GeneratorExp { .. } = argument {

View file

@ -28,7 +28,7 @@ pub fn unnecessary_list_call(checker: &mut Checker, expr: &Expr, func: &Expr, ar
let Some(argument) = helpers::first_argument_with_matching_function("list", func, args) else { let Some(argument) = helpers::first_argument_with_matching_function("list", func, args) else {
return; return;
}; };
if !checker.is_builtin("list") { if !checker.ctx.is_builtin("list") {
return; return;
} }
if !matches!(argument, ExprKind::ListComp { .. }) { if !matches!(argument, ExprKind::ListComp { .. }) {

View file

@ -34,7 +34,7 @@ pub fn unnecessary_list_comprehension_dict(
let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else { let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else {
return; return;
}; };
if !checker.is_builtin("dict") { if !checker.ctx.is_builtin("dict") {
return; return;
} }
let ExprKind::ListComp { elt, .. } = &argument else { let ExprKind::ListComp { elt, .. } = &argument else {

View file

@ -34,7 +34,7 @@ pub fn unnecessary_list_comprehension_set(
let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else { let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else {
return; return;
}; };
if !checker.is_builtin("set") { if !checker.ctx.is_builtin("set") {
return; return;
} }
if let ExprKind::ListComp { .. } = &argument { if let ExprKind::ListComp { .. } = &argument {

View file

@ -37,7 +37,7 @@ pub fn unnecessary_literal_dict(
let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else { let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else {
return; return;
}; };
if !checker.is_builtin("dict") { if !checker.ctx.is_builtin("dict") {
return; return;
} }
let (kind, elts) = match argument { let (kind, elts) = match argument {

View file

@ -37,7 +37,7 @@ pub fn unnecessary_literal_set(
let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else { let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else {
return; return;
}; };
if !checker.is_builtin("set") { if !checker.ctx.is_builtin("set") {
return; return;
} }
let kind = match argument { let kind = match argument {

View file

@ -52,7 +52,7 @@ pub fn unnecessary_literal_within_list_call(
let Some(argument) = helpers::first_argument_with_matching_function("list", func, args) else { let Some(argument) = helpers::first_argument_with_matching_function("list", func, args) else {
return; return;
}; };
if !checker.is_builtin("list") { if !checker.ctx.is_builtin("list") {
return; return;
} }
let argument_kind = match argument { let argument_kind = match argument {

View file

@ -53,7 +53,7 @@ pub fn unnecessary_literal_within_tuple_call(
let Some(argument) = helpers::first_argument_with_matching_function("tuple", func, args) else { let Some(argument) = helpers::first_argument_with_matching_function("tuple", func, args) else {
return; return;
}; };
if !checker.is_builtin("tuple") { if !checker.ctx.is_builtin("tuple") {
return; return;
} }
let argument_kind = match argument { let argument_kind = match argument {

View file

@ -88,7 +88,7 @@ pub fn unnecessary_map(
}; };
match id { match id {
"map" => { "map" => {
if !checker.is_builtin(id) { if !checker.ctx.is_builtin(id) {
return; return;
} }
@ -123,7 +123,7 @@ pub fn unnecessary_map(
} }
} }
"list" | "set" => { "list" | "set" => {
if !checker.is_builtin(id) { if !checker.ctx.is_builtin(id) {
return; return;
} }
@ -157,7 +157,7 @@ pub fn unnecessary_map(
} }
} }
"dict" => { "dict" => {
if !checker.is_builtin(id) { if !checker.ctx.is_builtin(id) {
return; return;
} }

View file

@ -37,7 +37,7 @@ pub fn unnecessary_subscript_reversal(
if !(id == "set" || id == "sorted" || id == "reversed") { if !(id == "set" || id == "sorted" || id == "reversed") {
return; return;
} }
if !checker.is_builtin(id) { if !checker.ctx.is_builtin(id) {
return; return;
} }
let ExprKind::Subscript { slice, .. } = &first_arg.node else { let ExprKind::Subscript { slice, .. } = &first_arg.node else {

View file

@ -124,9 +124,13 @@ pub fn call_datetime_without_tzinfo(
keywords: &[Keyword], keywords: &[Keyword],
location: Range, location: Range,
) { ) {
if !checker.resolve_call_path(func).map_or(false, |call_path| { if !checker
call_path.as_slice() == ["datetime", "datetime"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime"]
})
{
return; return;
} }
@ -153,9 +157,13 @@ pub fn call_datetime_without_tzinfo(
/// It uses the system local timezone. /// It uses the system local timezone.
/// Use `datetime.datetime.now(tz=)` instead. /// Use `datetime.datetime.now(tz=)` instead.
pub fn call_datetime_today(checker: &mut Checker, func: &Expr, location: Range) { pub fn call_datetime_today(checker: &mut Checker, func: &Expr, location: Range) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["datetime", "datetime", "today"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "today"]
})
{
checker checker
.diagnostics .diagnostics
.push(Diagnostic::new(CallDatetimeToday, location)); .push(Diagnostic::new(CallDatetimeToday, location));
@ -171,9 +179,13 @@ pub fn call_datetime_today(checker: &mut Checker, func: &Expr, location: Range)
/// UTC. As such, the recommended way to create an object representing the /// UTC. As such, the recommended way to create an object representing the
/// current time in UTC is by calling `datetime.now(timezone.utc)`. /// current time in UTC is by calling `datetime.now(timezone.utc)`.
pub fn call_datetime_utcnow(checker: &mut Checker, func: &Expr, location: Range) { pub fn call_datetime_utcnow(checker: &mut Checker, func: &Expr, location: Range) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["datetime", "datetime", "utcnow"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "utcnow"]
})
{
checker checker
.diagnostics .diagnostics
.push(Diagnostic::new(CallDatetimeUtcnow, location)); .push(Diagnostic::new(CallDatetimeUtcnow, location));
@ -190,9 +202,13 @@ pub fn call_datetime_utcnow(checker: &mut Checker, func: &Expr, location: Range)
/// specific timestamp in UTC is by calling `datetime.fromtimestamp(timestamp, /// specific timestamp in UTC is by calling `datetime.fromtimestamp(timestamp,
/// tz=timezone.utc)`. /// tz=timezone.utc)`.
pub fn call_datetime_utcfromtimestamp(checker: &mut Checker, func: &Expr, location: Range) { pub fn call_datetime_utcfromtimestamp(checker: &mut Checker, func: &Expr, location: Range) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["datetime", "datetime", "utcfromtimestamp"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "utcfromtimestamp"]
})
{
checker checker
.diagnostics .diagnostics
.push(Diagnostic::new(CallDatetimeUtcfromtimestamp, location)); .push(Diagnostic::new(CallDatetimeUtcfromtimestamp, location));
@ -207,9 +223,13 @@ pub fn call_datetime_now_without_tzinfo(
keywords: &[Keyword], keywords: &[Keyword],
location: Range, location: Range,
) { ) {
if !checker.resolve_call_path(func).map_or(false, |call_path| { if !checker
call_path.as_slice() == ["datetime", "datetime", "now"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "now"]
})
{
return; return;
} }
@ -245,9 +265,13 @@ pub fn call_datetime_fromtimestamp(
keywords: &[Keyword], keywords: &[Keyword],
location: Range, location: Range,
) { ) {
if !checker.resolve_call_path(func).map_or(false, |call_path| { if !checker
call_path.as_slice() == ["datetime", "datetime", "fromtimestamp"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "fromtimestamp"]
})
{
return; return;
} }
@ -282,9 +306,13 @@ pub fn call_datetime_strptime_without_zone(
args: &[Expr], args: &[Expr],
location: Range, location: Range,
) { ) {
if !checker.resolve_call_path(func).map_or(false, |call_path| { if !checker
call_path.as_slice() == ["datetime", "datetime", "strptime"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "strptime"]
})
{
return; return;
} }
@ -299,7 +327,7 @@ pub fn call_datetime_strptime_without_zone(
} }
}; };
let (Some(grandparent), Some(parent)) = (checker.current_expr_grandparent(), checker.current_expr_parent()) else { let (Some(grandparent), Some(parent)) = (checker.ctx.current_expr_grandparent(), checker.ctx.current_expr_parent()) else {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
CallDatetimeStrptimeWithoutZone, CallDatetimeStrptimeWithoutZone,
location, location,
@ -335,9 +363,13 @@ pub fn call_datetime_strptime_without_zone(
/// It uses the system local timezone. /// It uses the system local timezone.
/// Use `datetime.datetime.now(tz=).date()` instead. /// Use `datetime.datetime.now(tz=).date()` instead.
pub fn call_date_today(checker: &mut Checker, func: &Expr, location: Range) { pub fn call_date_today(checker: &mut Checker, func: &Expr, location: Range) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["datetime", "date", "today"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "date", "today"]
})
{
checker checker
.diagnostics .diagnostics
.push(Diagnostic::new(CallDateToday, location)); .push(Diagnostic::new(CallDateToday, location));
@ -351,9 +383,13 @@ pub fn call_date_today(checker: &mut Checker, func: &Expr, location: Range) {
/// It uses the system local timezone. /// It uses the system local timezone.
/// Use `datetime.datetime.fromtimestamp(, tz=).date()` instead. /// Use `datetime.datetime.fromtimestamp(, tz=).date()` instead.
pub fn call_date_fromtimestamp(checker: &mut Checker, func: &Expr, location: Range) { pub fn call_date_fromtimestamp(checker: &mut Checker, func: &Expr, location: Range) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["datetime", "date", "fromtimestamp"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "date", "fromtimestamp"]
})
{
checker checker
.diagnostics .diagnostics
.push(Diagnostic::new(CallDateFromtimestamp, location)); .push(Diagnostic::new(CallDateFromtimestamp, location));

View file

@ -46,7 +46,7 @@ const DEBUGGERS: &[&[&str]] = &[
/// Checks for the presence of a debugger call. /// Checks for the presence of a debugger call.
pub fn debugger_call(checker: &mut Checker, expr: &Expr, func: &Expr) { pub fn debugger_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
if let Some(target) = checker.resolve_call_path(func).and_then(|call_path| { if let Some(target) = checker.ctx.resolve_call_path(func).and_then(|call_path| {
DEBUGGERS DEBUGGERS
.iter() .iter()
.find(|target| call_path.as_slice() == **target) .find(|target| call_path.as_slice() == **target)

View file

@ -4,22 +4,28 @@ use crate::checkers::ast::Checker;
/// Return `true` if a Python class appears to be a Django model, based on its base classes. /// Return `true` if a Python class appears to be a Django model, based on its base classes.
pub fn is_model(checker: &Checker, base: &Expr) -> bool { pub fn is_model(checker: &Checker, base: &Expr) -> bool {
checker.resolve_call_path(base).map_or(false, |call_path| { checker
call_path.as_slice() == ["django", "db", "models", "Model"] .ctx
}) .resolve_call_path(base)
.map_or(false, |call_path| {
call_path.as_slice() == ["django", "db", "models", "Model"]
})
} }
/// Return `true` if a Python class appears to be a Django model form, based on its base classes. /// Return `true` if a Python class appears to be a Django model form, based on its base classes.
pub fn is_model_form(checker: &Checker, base: &Expr) -> bool { pub fn is_model_form(checker: &Checker, base: &Expr) -> bool {
checker.resolve_call_path(base).map_or(false, |call_path| { checker
call_path.as_slice() == ["django", "forms", "ModelForm"] .ctx
|| call_path.as_slice() == ["django", "forms", "models", "ModelForm"] .resolve_call_path(base)
}) .map_or(false, |call_path| {
call_path.as_slice() == ["django", "forms", "ModelForm"]
|| call_path.as_slice() == ["django", "forms", "models", "ModelForm"]
})
} }
/// Return the name of the field type, if the expression is constructor for a Django model field. /// Return the name of the field type, if the expression is constructor for a Django model field.
pub fn get_model_field_name<'a>(checker: &'a Checker, expr: &'a Expr) -> Option<&'a str> { pub fn get_model_field_name<'a>(checker: &'a Checker, expr: &'a Expr) -> Option<&'a str> {
checker.resolve_call_path(expr).and_then(|call_path| { checker.ctx.resolve_call_path(expr).and_then(|call_path| {
let call_path = call_path.as_slice(); let call_path = call_path.as_slice();
if !call_path.starts_with(&["django", "db", "models"]) { if !call_path.starts_with(&["django", "db", "models"]) {
return None; return None;

View file

@ -46,9 +46,13 @@ pub fn locals_in_render_function(
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
) { ) {
if !checker.resolve_call_path(func).map_or(false, |call_path| { if !checker
call_path.as_slice() == ["django", "shortcuts", "render"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["django", "shortcuts", "render"]
})
{
return; return;
} }
@ -83,6 +87,7 @@ fn is_locals_call(checker: &Checker, expr: &Expr) -> bool {
return false return false
}; };
checker checker
.ctx
.resolve_call_path(func) .resolve_call_path(func)
.map_or(false, |call_path| call_path.as_slice() == ["", "locals"]) .map_or(false, |call_path| call_path.as_slice() == ["", "locals"])
} }

View file

@ -107,6 +107,7 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) {
} }
ExprKind::Call { func, keywords, .. } => { ExprKind::Call { func, keywords, .. } => {
if checker if checker
.ctx
.resolve_call_path(func) .resolve_call_path(func)
.map_or(false, |call_path| call_path.as_slice() == ["", "dict"]) .map_or(false, |call_path| call_path.as_slice() == ["", "dict"])
{ {
@ -180,7 +181,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
.rules .rules
.enabled(&Rule::LoggingRedundantExcInfo) .enabled(&Rule::LoggingRedundantExcInfo)
{ {
if !checker.in_exception_handler() { if !checker.ctx.in_exception_handler() {
return; return;
} }
if let Some(exc_info) = find_keyword(keywords, "exc_info") { if let Some(exc_info) = find_keyword(keywords, "exc_info") {
@ -193,9 +194,12 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
.. ..
} }
) || if let ExprKind::Call { func, .. } = &exc_info.node.value.node { ) || if let ExprKind::Call { func, .. } = &exc_info.node.value.node {
checker.resolve_call_path(func).map_or(false, |call_path| { checker
call_path.as_slice() == ["sys", "exc_info"] .ctx
}) .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["sys", "exc_info"]
})
} else { } else {
false false
}) { }) {

View file

@ -242,11 +242,7 @@ pub fn dupe_class_field_definitions<'a, 'b>(
Range::from_located(stmt), Range::from_located(stmt),
); );
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
let deleted: Vec<&Stmt> = checker let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
.deletions
.iter()
.map(std::convert::Into::into)
.collect();
let locator = checker.locator; let locator = checker.locator;
match delete_stmt( match delete_stmt(
stmt, stmt,
@ -281,6 +277,7 @@ where
if !bases.iter().any(|expr| { if !bases.iter().any(|expr| {
checker checker
.ctx
.resolve_call_path(expr) .resolve_call_path(expr)
.map_or(false, |call_path| call_path.as_slice() == ["enum", "Enum"]) .map_or(false, |call_path| call_path.as_slice() == ["enum", "Enum"])
}) { }) {
@ -295,6 +292,7 @@ where
if let ExprKind::Call { func, .. } = &value.node { if let ExprKind::Call { func, .. } = &value.node {
if checker if checker
.ctx
.resolve_call_path(func) .resolve_call_path(func)
.map_or(false, |call_path| call_path.as_slice() == ["enum", "auto"]) .map_or(false, |call_path| call_path.as_slice() == ["enum", "auto"])
{ {
@ -337,7 +335,7 @@ pub fn unnecessary_comprehension_any_all(
) { ) {
if let ExprKind::Name { id, .. } = &func.node { if let ExprKind::Name { id, .. } = &func.node {
if (id == "all" || id == "any") && args.len() == 1 { if (id == "all" || id == "any") && args.len() == 1 {
if !checker.is_builtin(id) { if !checker.ctx.is_builtin(id) {
return; return;
} }
if let ExprKind::ListComp { .. } = args[0].node { if let ExprKind::ListComp { .. } = args[0].node {

View file

@ -31,7 +31,7 @@ impl Violation for PPrintFound {
/// T201, T203 /// T201, T203
pub fn print_call(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) { pub fn print_call(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
let diagnostic = { let diagnostic = {
let call_path = checker.resolve_call_path(func); let call_path = checker.ctx.resolve_call_path(func);
if call_path if call_path
.as_ref() .as_ref()
.map_or(false, |call_path| *call_path.as_slice() == ["", "print"]) .map_or(false, |call_path| *call_path.as_slice() == ["", "print"])
@ -43,13 +43,13 @@ pub fn print_call(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
.find(|keyword| keyword.node.arg.as_ref().map_or(false, |arg| arg == "file")) .find(|keyword| keyword.node.arg.as_ref().map_or(false, |arg| arg == "file"))
{ {
if !is_const_none(&keyword.node.value) { if !is_const_none(&keyword.node.value) {
if checker if checker.ctx.resolve_call_path(&keyword.node.value).map_or(
.resolve_call_path(&keyword.node.value) true,
.map_or(true, |call_path| { |call_path| {
call_path.as_slice() != ["sys", "stdout"] call_path.as_slice() != ["sys", "stdout"]
&& call_path.as_slice() != ["sys", "stderr"] && call_path.as_slice() != ["sys", "stderr"]
}) },
{ ) {
return; return;
} }
} }

View file

@ -70,9 +70,13 @@ pub fn bad_version_info_comparison(
return; return;
}; };
if !checker.resolve_call_path(left).map_or(false, |call_path| { if !checker
call_path.as_slice() == ["sys", "version_info"] .ctx
}) { .resolve_call_path(left)
.map_or(false, |call_path| {
call_path.as_slice() == ["sys", "version_info"]
})
{
return; return;
} }

View file

@ -71,12 +71,12 @@ pub fn prefix_type_params(checker: &mut Checker, value: &Expr, targets: &[Expr])
}; };
if let ExprKind::Call { func, .. } = &value.node { if let ExprKind::Call { func, .. } = &value.node {
let Some(kind) = checker.resolve_call_path(func).and_then(|call_path| { let Some(kind) = checker.ctx.resolve_call_path(func).and_then(|call_path| {
if checker.match_typing_call_path(&call_path, "ParamSpec") { if checker.ctx.match_typing_call_path(&call_path, "ParamSpec") {
Some(VarKind::ParamSpec) Some(VarKind::ParamSpec)
} else if checker.match_typing_call_path(&call_path, "TypeVar") { } else if checker.ctx.match_typing_call_path(&call_path, "TypeVar") {
Some(VarKind::TypeVar) Some(VarKind::TypeVar)
} else if checker.match_typing_call_path(&call_path, "TypeVarTuple") { } else if checker.ctx.match_typing_call_path(&call_path, "TypeVarTuple") {
Some(VarKind::TypeVarTuple) Some(VarKind::TypeVarTuple)
} else { } else {
None None

View file

@ -122,6 +122,7 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) ->
// `sys.stdin`, etc. // `sys.stdin`, etc.
ExprKind::Attribute { .. } => { ExprKind::Attribute { .. } => {
if checker if checker
.ctx
.resolve_call_path(default) .resolve_call_path(default)
.map_or(false, |call_path| { .map_or(false, |call_path| {
ALLOWED_ATTRIBUTES_IN_DEFAULTS ALLOWED_ATTRIBUTES_IN_DEFAULTS

View file

@ -103,9 +103,13 @@ pub fn unrecognized_platform(
let diagnostic_unrecognized_platform_check = let diagnostic_unrecognized_platform_check =
Diagnostic::new(UnrecognizedPlatformCheck, Range::from_located(expr)); Diagnostic::new(UnrecognizedPlatformCheck, Range::from_located(expr));
if !checker.resolve_call_path(left).map_or(false, |call_path| { if !checker
call_path.as_slice() == ["sys", "platform"] .ctx
}) { .resolve_call_path(left)
.map_or(false, |call_path| {
call_path.as_slice() == ["sys", "platform"]
})
{
return; return;
} }

View file

@ -195,8 +195,8 @@ pub fn unittest_assertion(
if let Ok(unittest_assert) = UnittestAssert::try_from(attr.as_str()) { if let Ok(unittest_assert) = UnittestAssert::try_from(attr.as_str()) {
// We're converting an expression to a statement, so avoid applying the fix if // We're converting an expression to a statement, so avoid applying the fix if
// the assertion is part of a larger expression. // the assertion is part of a larger expression.
let fixable = checker.current_expr_parent().is_none() let fixable = checker.ctx.current_expr_parent().is_none()
&& matches!(checker.current_stmt().node, StmtKind::Expr { .. }) && matches!(checker.ctx.current_stmt().node, StmtKind::Expr { .. })
&& !has_comments_in(Range::from_located(expr), checker.locator); && !has_comments_in(Range::from_located(expr), checker.locator);
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
UnittestAssertion { UnittestAssertion {

View file

@ -17,13 +17,17 @@ pub fn get_mark_name(decorator: &Expr) -> &str {
} }
pub fn is_pytest_fail(call: &Expr, checker: &Checker) -> bool { pub fn is_pytest_fail(call: &Expr, checker: &Checker) -> bool {
checker.resolve_call_path(call).map_or(false, |call_path| { checker
call_path.as_slice() == ["pytest", "fail"] .ctx
}) .resolve_call_path(call)
.map_or(false, |call_path| {
call_path.as_slice() == ["pytest", "fail"]
})
} }
pub fn is_pytest_fixture(decorator: &Expr, checker: &Checker) -> bool { pub fn is_pytest_fixture(decorator: &Expr, checker: &Checker) -> bool {
checker checker
.ctx
.resolve_call_path(if let ExprKind::Call { func, .. } = &decorator.node { .resolve_call_path(if let ExprKind::Call { func, .. } = &decorator.node {
func func
} else { } else {
@ -45,6 +49,7 @@ pub fn is_pytest_mark(decorator: &Expr) -> bool {
pub fn is_pytest_yield_fixture(decorator: &Expr, checker: &Checker) -> bool { pub fn is_pytest_yield_fixture(decorator: &Expr, checker: &Checker) -> bool {
checker checker
.ctx
.resolve_call_path(map_callable(decorator)) .resolve_call_path(map_callable(decorator))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["pytest", "yield_fixture"] call_path.as_slice() == ["pytest", "yield_fixture"]
@ -53,6 +58,7 @@ pub fn is_pytest_yield_fixture(decorator: &Expr, checker: &Checker) -> bool {
pub fn is_abstractmethod_decorator(decorator: &Expr, checker: &Checker) -> bool { pub fn is_abstractmethod_decorator(decorator: &Expr, checker: &Checker) -> bool {
checker checker
.ctx
.resolve_call_path(decorator) .resolve_call_path(decorator)
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["abc", "abstractmethod"] call_path.as_slice() == ["abc", "abstractmethod"]
@ -103,6 +109,7 @@ pub fn is_falsy_constant(expr: &Expr) -> bool {
pub fn is_pytest_parametrize(decorator: &Expr, checker: &Checker) -> bool { pub fn is_pytest_parametrize(decorator: &Expr, checker: &Checker) -> bool {
checker checker
.ctx
.resolve_call_path(map_callable(decorator)) .resolve_call_path(map_callable(decorator))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["pytest", "mark", "parametrize"] call_path.as_slice() == ["pytest", "mark", "parametrize"]

View file

@ -45,9 +45,12 @@ impl Violation for RaisesWithoutException {
} }
fn is_pytest_raises(checker: &Checker, func: &Expr) -> bool { fn is_pytest_raises(checker: &Checker, func: &Expr) -> bool {
checker.resolve_call_path(func).map_or(false, |call_path| { checker
call_path.as_slice() == ["pytest", "raises"] .ctx
}) .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["pytest", "raises"]
})
} }
const fn is_non_trivial_with_body(body: &[Stmt]) -> bool { const fn is_non_trivial_with_body(body: &[Stmt]) -> bool {
@ -136,25 +139,29 @@ pub fn complex_raises(checker: &mut Checker, stmt: &Stmt, items: &[Withitem], bo
/// PT011 /// PT011
fn exception_needs_match(checker: &mut Checker, exception: &Expr) { fn exception_needs_match(checker: &mut Checker, exception: &Expr) {
if let Some(call_path) = checker.resolve_call_path(exception).and_then(|call_path| { if let Some(call_path) = checker
let is_broad_exception = checker .ctx
.settings .resolve_call_path(exception)
.flake8_pytest_style .and_then(|call_path| {
.raises_require_match_for let is_broad_exception = checker
.iter() .settings
.chain( .flake8_pytest_style
&checker .raises_require_match_for
.settings .iter()
.flake8_pytest_style .chain(
.raises_extend_require_match_for, &checker
) .settings
.any(|target| call_path == to_call_path(target)); .flake8_pytest_style
if is_broad_exception { .raises_extend_require_match_for,
Some(format_call_path(&call_path)) )
} else { .any(|target| call_path == to_call_path(target));
None if is_broad_exception {
} Some(format_call_path(&call_path))
}) { } else {
None
}
})
{
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
RaisesTooBroad { RaisesTooBroad {
exception: call_path, exception: call_path,

View file

@ -192,11 +192,14 @@ const NORETURN_FUNCS: &[&[&str]] = &[
/// Return `true` if the `func` is a known function that never returns. /// Return `true` if the `func` is a known function that never returns.
fn is_noreturn_func(checker: &Checker, func: &Expr) -> bool { fn is_noreturn_func(checker: &Checker, func: &Expr) -> bool {
checker.resolve_call_path(func).map_or(false, |call_path| { checker
NORETURN_FUNCS .ctx
.iter() .resolve_call_path(func)
.any(|target| call_path.as_slice() == *target) .map_or(false, |call_path| {
}) NORETURN_FUNCS
.iter()
.any(|target| call_path.as_slice() == *target)
})
} }
/// RET503 /// RET503

View file

@ -89,6 +89,7 @@ pub fn private_member_access(checker: &mut Checker, expr: &Expr) {
// Ignore accesses on class members from _within_ the class. // Ignore accesses on class members from _within_ the class.
if checker if checker
.ctx
.scopes .scopes
.iter() .iter()
.rev() .rev()
@ -99,6 +100,7 @@ pub fn private_member_access(checker: &mut Checker, expr: &Expr) {
.map_or(false, |class_def| { .map_or(false, |class_def| {
if call_path.as_slice() == [class_def.name] { if call_path.as_slice() == [class_def.name] {
checker checker
.ctx
.find_binding(class_def.name) .find_binding(class_def.name)
.map_or(false, |binding| { .map_or(false, |binding| {
// TODO(charlie): Could the name ever be bound to a _different_ // TODO(charlie): Could the name ever be bound to a _different_

View file

@ -172,7 +172,7 @@ pub fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
if func_name != "isinstance" { if func_name != "isinstance" {
continue; continue;
} }
if !checker.is_builtin("isinstance") { if !checker.ctx.is_builtin("isinstance") {
continue; continue;
} }
@ -313,7 +313,7 @@ pub fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
// Avoid rewriting (e.g.) `a == "foo" or a == f()`. // Avoid rewriting (e.g.) `a == "foo" or a == f()`.
if comparators if comparators
.iter() .iter()
.any(|expr| contains_effect(checker, expr)) .any(|expr| contains_effect(&checker.ctx, expr))
{ {
continue; continue;
} }
@ -395,7 +395,7 @@ pub fn expr_and_not_expr(checker: &mut Checker, expr: &Expr) {
return; return;
} }
if contains_effect(checker, expr) { if contains_effect(&checker.ctx, expr) {
return; return;
} }
@ -449,7 +449,7 @@ pub fn expr_or_not_expr(checker: &mut Checker, expr: &Expr) {
return; return;
} }
if contains_effect(checker, expr) { if contains_effect(&checker.ctx, expr) {
return; return;
} }
@ -480,7 +480,7 @@ pub fn expr_or_true(checker: &mut Checker, expr: &Expr) {
let ExprKind::BoolOp { op: Boolop::Or, values, } = &expr.node else { let ExprKind::BoolOp { op: Boolop::Or, values, } = &expr.node else {
return; return;
}; };
if contains_effect(checker, expr) { if contains_effect(&checker.ctx, expr) {
return; return;
} }
for value in values { for value in values {
@ -507,7 +507,7 @@ pub fn expr_and_false(checker: &mut Checker, expr: &Expr) {
let ExprKind::BoolOp { op: Boolop::And, values, } = &expr.node else { let ExprKind::BoolOp { op: Boolop::And, values, } = &expr.node else {
return; return;
}; };
if contains_effect(checker, expr) { if contains_effect(&checker.ctx, expr) {
return; return;
} }
for value in values { for value in values {

View file

@ -45,9 +45,14 @@ pub fn use_capital_environment_variables(checker: &mut Checker, expr: &Expr) {
let ExprKind::Constant { value: Constant::Str(env_var), kind } = &arg.node else { let ExprKind::Constant { value: Constant::Str(env_var), kind } = &arg.node else {
return; return;
}; };
if !checker.resolve_call_path(func).map_or(false, |call_path| { if !checker
call_path.as_slice() == ["os", "environ", "get"] || call_path.as_slice() == ["os", "getenv"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["os", "environ", "get"]
|| call_path.as_slice() == ["os", "getenv"]
})
{
return; return;
} }

View file

@ -347,7 +347,7 @@ pub fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
let fixable = matches!(if_return, Bool::True) let fixable = matches!(if_return, Bool::True)
&& matches!(else_return, Bool::False) && matches!(else_return, Bool::False)
&& !has_comments(stmt, checker.locator) && !has_comments(stmt, checker.locator)
&& (matches!(test.node, ExprKind::Compare { .. }) || checker.is_builtin("bool")); && (matches!(test.node, ExprKind::Compare { .. }) || checker.ctx.is_builtin("bool"));
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
NeedlessBool { condition, fixable }, NeedlessBool { condition, fixable },
@ -431,13 +431,13 @@ pub fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt, parent: Option<&
} }
// Avoid suggesting ternary for `if sys.version_info >= ...`-style checks. // Avoid suggesting ternary for `if sys.version_info >= ...`-style checks.
if contains_call_path(checker, test, &["sys", "version_info"]) { if contains_call_path(&checker.ctx, test, &["sys", "version_info"]) {
return; return;
} }
// Avoid suggesting ternary for `if sys.platform.startswith("...")`-style // Avoid suggesting ternary for `if sys.platform.startswith("...")`-style
// checks. // checks.
if contains_call_path(checker, test, &["sys", "platform"]) { if contains_call_path(&checker.ctx, test, &["sys", "platform"]) {
return; return;
} }
@ -629,7 +629,7 @@ pub fn manual_dict_lookup(
}; };
if value if value
.as_ref() .as_ref()
.map_or(false, |value| contains_effect(checker, value)) .map_or(false, |value| contains_effect(&checker.ctx, value))
{ {
return; return;
} }
@ -702,7 +702,7 @@ pub fn manual_dict_lookup(
}; };
if value if value
.as_ref() .as_ref()
.map_or(false, |value| contains_effect(checker, value)) .map_or(false, |value| contains_effect(&checker.ctx, value))
{ {
return; return;
}; };
@ -784,7 +784,7 @@ pub fn use_dict_get_with_default(
} }
// Check that the default value is not "complex". // Check that the default value is not "complex".
if contains_effect(checker, default_val) { if contains_effect(&checker.ctx, default_val) {
return; return;
} }

View file

@ -106,7 +106,7 @@ pub fn explicit_true_false_in_ifexpr(
expr.location, expr.location,
expr.end_location.unwrap(), expr.end_location.unwrap(),
)); ));
} else if checker.is_builtin("bool") { } else if checker.ctx.is_builtin("bool") {
diagnostic.amend(Fix::replacement( diagnostic.amend(Fix::replacement(
unparse_expr( unparse_expr(
&create_expr(ExprKind::Call { &create_expr(ExprKind::Call {

View file

@ -88,12 +88,12 @@ pub fn negation_with_equal_op(checker: &mut Checker, expr: &Expr, op: &Unaryop,
if !matches!(&ops[..], [Cmpop::Eq]) { if !matches!(&ops[..], [Cmpop::Eq]) {
return; return;
} }
if is_exception_check(checker.current_stmt()) { if is_exception_check(checker.ctx.current_stmt()) {
return; return;
} }
// Avoid flagging issues in dunder implementations. // Avoid flagging issues in dunder implementations.
if let ScopeKind::Function(def) = &checker.current_scope().kind { if let ScopeKind::Function(def) = &checker.ctx.current_scope().kind {
if DUNDER_METHODS.contains(&def.name) { if DUNDER_METHODS.contains(&def.name) {
return; return;
} }
@ -139,12 +139,12 @@ pub fn negation_with_not_equal_op(
if !matches!(&ops[..], [Cmpop::NotEq]) { if !matches!(&ops[..], [Cmpop::NotEq]) {
return; return;
} }
if is_exception_check(checker.current_stmt()) { if is_exception_check(checker.ctx.current_stmt()) {
return; return;
} }
// Avoid flagging issues in dunder implementations. // Avoid flagging issues in dunder implementations.
if let ScopeKind::Function(def) = &checker.current_scope().kind { if let ScopeKind::Function(def) = &checker.ctx.current_scope().kind {
if DUNDER_METHODS.contains(&def.name) { if DUNDER_METHODS.contains(&def.name) {
return; return;
} }

View file

@ -19,7 +19,7 @@ impl Violation for OpenFileWithContextHandler {
/// Return `true` if the current expression is nested in an `await /// Return `true` if the current expression is nested in an `await
/// exit_stack.enter_async_context` call. /// exit_stack.enter_async_context` call.
fn match_async_exit_stack(checker: &Checker) -> bool { fn match_async_exit_stack(checker: &Checker) -> bool {
let Some(expr) = checker.current_expr_grandparent() else { let Some(expr) = checker.ctx.current_expr_grandparent() else {
return false; return false;
}; };
let ExprKind::Await { value } = &expr.node else { let ExprKind::Await { value } = &expr.node else {
@ -34,13 +34,17 @@ fn match_async_exit_stack(checker: &Checker) -> bool {
if attr != "enter_async_context" { if attr != "enter_async_context" {
return false; return false;
} }
for parent in &checker.parents { for parent in &checker.ctx.parents {
if let StmtKind::With { items, .. } = &parent.node { if let StmtKind::With { items, .. } = &parent.node {
for item in items { for item in items {
if let ExprKind::Call { func, .. } = &item.context_expr.node { if let ExprKind::Call { func, .. } = &item.context_expr.node {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["contextlib", "AsyncExitStack"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["contextlib", "AsyncExitStack"]
})
{
return true; return true;
} }
} }
@ -53,7 +57,7 @@ fn match_async_exit_stack(checker: &Checker) -> bool {
/// Return `true` if the current expression is nested in an /// Return `true` if the current expression is nested in an
/// `exit_stack.enter_context` call. /// `exit_stack.enter_context` call.
fn match_exit_stack(checker: &Checker) -> bool { fn match_exit_stack(checker: &Checker) -> bool {
let Some(expr) = checker.current_expr_parent() else { let Some(expr) = checker.ctx.current_expr_parent() else {
return false; return false;
}; };
let ExprKind::Call { func, .. } = &expr.node else { let ExprKind::Call { func, .. } = &expr.node else {
@ -65,13 +69,17 @@ fn match_exit_stack(checker: &Checker) -> bool {
if attr != "enter_context" { if attr != "enter_context" {
return false; return false;
} }
for parent in &checker.parents { for parent in &checker.ctx.parents {
if let StmtKind::With { items, .. } = &parent.node { if let StmtKind::With { items, .. } = &parent.node {
for item in items { for item in items {
if let ExprKind::Call { func, .. } = &item.context_expr.node { if let ExprKind::Call { func, .. } = &item.context_expr.node {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["contextlib", "ExitStack"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["contextlib", "ExitStack"]
})
{
return true; return true;
} }
} }
@ -84,12 +92,13 @@ fn match_exit_stack(checker: &Checker) -> bool {
/// SIM115 /// SIM115
pub fn open_file_with_context_handler(checker: &mut Checker, func: &Expr) { pub fn open_file_with_context_handler(checker: &mut Checker, func: &Expr) {
if checker if checker
.ctx
.resolve_call_path(func) .resolve_call_path(func)
.map_or(false, |call_path| call_path.as_slice() == ["", "open"]) .map_or(false, |call_path| call_path.as_slice() == ["", "open"])
{ {
if checker.is_builtin("open") { if checker.ctx.is_builtin("open") {
// Ex) `with open("foo.txt") as f: ...` // Ex) `with open("foo.txt") as f: ...`
if matches!(checker.current_stmt().node, StmtKind::With { .. }) { if matches!(checker.ctx.current_stmt().node, StmtKind::With { .. }) {
return; return;
} }

View file

@ -221,7 +221,7 @@ pub fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt, sibling:
}, },
Range::from_located(stmt), Range::from_located(stmt),
); );
if checker.patch(diagnostic.kind.rule()) && checker.is_builtin("any") { if checker.patch(diagnostic.kind.rule()) && checker.ctx.is_builtin("any") {
diagnostic.amend(Fix::replacement( diagnostic.amend(Fix::replacement(
contents, contents,
stmt.location, stmt.location,
@ -298,7 +298,7 @@ pub fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt, sibling:
}, },
Range::from_located(stmt), Range::from_located(stmt),
); );
if checker.patch(diagnostic.kind.rule()) && checker.is_builtin("all") { if checker.patch(diagnostic.kind.rule()) && checker.ctx.is_builtin("all") {
diagnostic.amend(Fix::replacement( diagnostic.amend(Fix::replacement(
contents, contents,
stmt.location, stmt.location,

View file

@ -96,7 +96,7 @@ pub fn name_or_parent_is_banned<T>(
/// TID251 /// TID251
pub fn banned_attribute_access(checker: &mut Checker, expr: &Expr) { pub fn banned_attribute_access(checker: &mut Checker, expr: &Expr) {
if let Some((banned_path, ban)) = checker.resolve_call_path(expr).and_then(|call_path| { if let Some((banned_path, ban)) = checker.ctx.resolve_call_path(expr).and_then(|call_path| {
checker checker
.settings .settings
.flake8_tidy_imports .flake8_tidy_imports

View file

@ -29,9 +29,13 @@ pub fn is_type_checking_block(checker: &Checker, test: &Expr) -> bool {
} }
// Ex) `if typing.TYPE_CHECKING:` // Ex) `if typing.TYPE_CHECKING:`
if checker.resolve_call_path(test).map_or(false, |call_path| { if checker
call_path.as_slice() == ["typing", "TYPE_CHECKING"] .ctx
}) { .resolve_call_path(test)
.map_or(false, |call_path| {
call_path.as_slice() == ["typing", "TYPE_CHECKING"]
})
{
return true; return true;
} }

View file

@ -36,14 +36,11 @@ pub fn empty_type_checking_block<'a, 'b>(
// Delete the entire type-checking block. // Delete the entire type-checking block.
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
let parent = checker let parent = checker
.ctx
.child_to_parent .child_to_parent
.get(&RefEquality(stmt)) .get(&RefEquality(stmt))
.map(std::convert::Into::into); .map(Into::into);
let deleted: Vec<&Stmt> = checker let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
.deletions
.iter()
.map(std::convert::Into::into)
.collect();
match delete_stmt( match delete_stmt(
stmt, stmt,
parent, parent,

View file

@ -178,7 +178,7 @@ pub fn unused_arguments(
.. ..
}) => { }) => {
match function_type::classify( match function_type::classify(
checker, &checker.ctx,
parent, parent,
name, name,
decorator_list, decorator_list,
@ -190,7 +190,7 @@ pub fn unused_arguments(
.settings .settings
.rules .rules
.enabled(Argumentable::Function.rule_code()) .enabled(Argumentable::Function.rule_code())
&& !visibility::is_overload(checker, decorator_list) && !visibility::is_overload(&checker.ctx, decorator_list)
{ {
function( function(
&Argumentable::Function, &Argumentable::Function,
@ -217,9 +217,9 @@ pub fn unused_arguments(
|| visibility::is_init(name) || visibility::is_init(name)
|| visibility::is_new(name) || visibility::is_new(name)
|| visibility::is_call(name)) || visibility::is_call(name))
&& !visibility::is_abstract(checker, decorator_list) && !visibility::is_abstract(&checker.ctx, decorator_list)
&& !visibility::is_override(checker, decorator_list) && !visibility::is_override(&checker.ctx, decorator_list)
&& !visibility::is_overload(checker, decorator_list) && !visibility::is_overload(&checker.ctx, decorator_list)
{ {
method( method(
&Argumentable::Method, &Argumentable::Method,
@ -246,9 +246,9 @@ pub fn unused_arguments(
|| visibility::is_init(name) || visibility::is_init(name)
|| visibility::is_new(name) || visibility::is_new(name)
|| visibility::is_call(name)) || visibility::is_call(name))
&& !visibility::is_abstract(checker, decorator_list) && !visibility::is_abstract(&checker.ctx, decorator_list)
&& !visibility::is_override(checker, decorator_list) && !visibility::is_override(&checker.ctx, decorator_list)
&& !visibility::is_overload(checker, decorator_list) && !visibility::is_overload(&checker.ctx, decorator_list)
{ {
method( method(
&Argumentable::ClassMethod, &Argumentable::ClassMethod,
@ -275,9 +275,9 @@ pub fn unused_arguments(
|| visibility::is_init(name) || visibility::is_init(name)
|| visibility::is_new(name) || visibility::is_new(name)
|| visibility::is_call(name)) || visibility::is_call(name))
&& !visibility::is_abstract(checker, decorator_list) && !visibility::is_abstract(&checker.ctx, decorator_list)
&& !visibility::is_override(checker, decorator_list) && !visibility::is_override(&checker.ctx, decorator_list)
&& !visibility::is_overload(checker, decorator_list) && !visibility::is_overload(&checker.ctx, decorator_list)
{ {
function( function(
&Argumentable::StaticMethod, &Argumentable::StaticMethod,

View file

@ -15,6 +15,7 @@ use crate::settings::types::PythonVersion;
pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) { pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) {
if let Some(diagnostic_kind) = if let Some(diagnostic_kind) =
checker checker
.ctx
.resolve_call_path(expr) .resolve_call_path(expr)
.and_then(|call_path| match call_path.as_slice() { .and_then(|call_path| match call_path.as_slice() {
["os", "path", "abspath"] => Some(PathlibAbspath.into()), ["os", "path", "abspath"] => Some(PathlibAbspath.into()),

View file

@ -49,7 +49,7 @@ impl AlwaysAutofixableViolation for NumpyDeprecatedTypeAlias {
/// NPY001 /// NPY001
pub fn deprecated_type_alias(checker: &mut Checker, expr: &Expr) { pub fn deprecated_type_alias(checker: &mut Checker, expr: &Expr) {
if let Some(type_name) = checker.resolve_call_path(expr).and_then(|call_path| { if let Some(type_name) = checker.ctx.resolve_call_path(expr).and_then(|call_path| {
if call_path.as_slice() == ["numpy", "bool"] if call_path.as_slice() == ["numpy", "bool"]
|| call_path.as_slice() == ["numpy", "int"] || call_path.as_slice() == ["numpy", "int"]
|| call_path.as_slice() == ["numpy", "float"] || call_path.as_slice() == ["numpy", "float"]

View file

@ -59,7 +59,7 @@ impl Violation for NumpyLegacyRandom {
/// NPY002 /// NPY002
pub fn numpy_legacy_random(checker: &mut Checker, expr: &Expr) { pub fn numpy_legacy_random(checker: &mut Checker, expr: &Expr) {
if let Some(method_name) = checker.resolve_call_path(expr).and_then(|call_path| { if let Some(method_name) = checker.ctx.resolve_call_path(expr).and_then(|call_path| {
// seeding state // seeding state
if call_path.as_slice() == ["numpy", "random", "seed"] if call_path.as_slice() == ["numpy", "random", "seed"]
|| call_path.as_slice() == ["numpy", "random", "get_state"] || call_path.as_slice() == ["numpy", "random", "get_state"]

View file

@ -59,7 +59,7 @@ pub fn check_attr(checker: &mut Checker, attr: &str, value: &Expr, attr_expr: &E
}; };
// Avoid flagging on function calls (e.g., `df.values()`). // Avoid flagging on function calls (e.g., `df.values()`).
if let Some(parent) = checker.current_expr_parent() { if let Some(parent) = checker.ctx.current_expr_parent() {
if matches!(parent.node, ExprKind::Call { .. }) { if matches!(parent.node, ExprKind::Call { .. }) {
return; return;
} }
@ -72,7 +72,7 @@ pub fn check_attr(checker: &mut Checker, attr: &str, value: &Expr, attr_expr: &E
// If the target is a named variable, avoid triggering on // If the target is a named variable, avoid triggering on
// irrelevant bindings (like imports). // irrelevant bindings (like imports).
if let ExprKind::Name { id, .. } = &value.node { if let ExprKind::Name { id, .. } = &value.node {
if checker.find_binding(id).map_or(true, |binding| { if checker.ctx.find_binding(id).map_or(true, |binding| {
matches!( matches!(
binding.kind, binding.kind,
BindingKind::Builtin BindingKind::Builtin

View file

@ -81,7 +81,7 @@ pub fn check_call(checker: &mut Checker, func: &Expr) {
// If the target is a named variable, avoid triggering on // If the target is a named variable, avoid triggering on
// irrelevant bindings (like non-Pandas imports). // irrelevant bindings (like non-Pandas imports).
if let ExprKind::Name { id, .. } = &value.node { if let ExprKind::Name { id, .. } = &value.node {
if checker.find_binding(id).map_or(true, |binding| { if checker.ctx.find_binding(id).map_or(true, |binding| {
if let BindingKind::Importation(.., module) = &binding.kind { if let BindingKind::Importation(.., module) = &binding.kind {
module != &"pandas" module != &"pandas"
} else { } else {

View file

@ -29,10 +29,13 @@ pub fn is_namedtuple_assignment(checker: &Checker, stmt: &Stmt) -> bool {
let ExprKind::Call {func, ..} = &value.node else { let ExprKind::Call {func, ..} = &value.node else {
return false; return false;
}; };
checker.resolve_call_path(func).map_or(false, |call_path| { checker
call_path.as_slice() == ["collections", "namedtuple"] .ctx
|| call_path.as_slice() == ["typing", "NamedTuple"] .resolve_call_path(func)
}) .map_or(false, |call_path| {
call_path.as_slice() == ["collections", "namedtuple"]
|| call_path.as_slice() == ["typing", "NamedTuple"]
})
} }
pub fn is_typeddict_assignment(checker: &Checker, stmt: &Stmt) -> bool { pub fn is_typeddict_assignment(checker: &Checker, stmt: &Stmt) -> bool {
@ -42,9 +45,12 @@ pub fn is_typeddict_assignment(checker: &Checker, stmt: &Stmt) -> bool {
let ExprKind::Call {func, ..} = &value.node else { let ExprKind::Call {func, ..} = &value.node else {
return false; return false;
}; };
checker.resolve_call_path(func).map_or(false, |call_path| { checker
call_path.as_slice() == ["typing", "TypedDict"] .ctx
}) .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["typing", "TypedDict"]
})
} }
pub fn is_type_var_assignment(checker: &Checker, stmt: &Stmt) -> bool { pub fn is_type_var_assignment(checker: &Checker, stmt: &Stmt) -> bool {
@ -54,10 +60,13 @@ pub fn is_type_var_assignment(checker: &Checker, stmt: &Stmt) -> bool {
let ExprKind::Call {func, ..} = &value.node else { let ExprKind::Call {func, ..} = &value.node else {
return false; return false;
}; };
checker.resolve_call_path(func).map_or(false, |call_path| { checker
call_path.as_slice() == ["typing", "TypeVar"] .ctx
|| call_path.as_slice() == ["typing", "NewType"] .resolve_call_path(func)
}) .map_or(false, |call_path| {
call_path.as_slice() == ["typing", "TypeVar"]
|| call_path.as_slice() == ["typing", "NewType"]
})
} }
#[cfg(test)] #[cfg(test)]

View file

@ -64,7 +64,7 @@ pub fn invalid_first_argument_name_for_class_method(
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
if !matches!( if !matches!(
function_type::classify( function_type::classify(
checker, &checker.ctx,
scope, scope,
name, name,
decorator_list, decorator_list,

View file

@ -61,7 +61,7 @@ pub fn invalid_first_argument_name_for_method(
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
if !matches!( if !matches!(
function_type::classify( function_type::classify(
checker, &checker.ctx,
scope, scope,
name, name,
decorator_list, decorator_list,

View file

@ -36,7 +36,7 @@ pub fn multiple_imports_on_one_line(checker: &mut Checker, stmt: &Stmt, names: &
} }
pub fn module_import_not_at_top_of_file(checker: &mut Checker, stmt: &Stmt) { pub fn module_import_not_at_top_of_file(checker: &mut Checker, stmt: &Stmt) {
if checker.seen_import_boundary && stmt.location.column() == 0 { if checker.ctx.seen_import_boundary && stmt.location.column() == 0 {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
ModuleImportNotAtTopOfFile, ModuleImportNotAtTopOfFile,
Range::from_located(stmt), Range::from_located(stmt),

View file

@ -40,7 +40,7 @@ pub fn lambda_assignment(checker: &mut Checker, target: &Expr, value: &Expr, stm
// package like dataclasses, which wouldn't consider the // package like dataclasses, which wouldn't consider the
// rewritten function definition to be equivalent. // rewritten function definition to be equivalent.
// See https://github.com/charliermarsh/ruff/issues/3046 // See https://github.com/charliermarsh/ruff/issues/3046
let fixable = !matches!(checker.current_scope().kind, ScopeKind::Class(_)); let fixable = !matches!(checker.ctx.current_scope().kind, ScopeKind::Class(_));
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
LambdaAssignment { LambdaAssignment {

View file

@ -85,7 +85,7 @@ pub fn should_ignore_definition(
| DefinitionKind::Method(parent) = definition.kind | DefinitionKind::Method(parent) = definition.kind
{ {
for decorator in cast::decorator_list(parent) { for decorator in cast::decorator_list(parent) {
if let Some(call_path) = checker.resolve_call_path(map_callable(decorator)) { if let Some(call_path) = checker.ctx.resolve_call_path(map_callable(decorator)) {
if ignore_decorators if ignore_decorators
.iter() .iter()
.any(|decorator| to_call_path(decorator) == call_path) .any(|decorator| to_call_path(decorator) == call_path)

View file

@ -27,7 +27,7 @@ pub fn if_needed(checker: &mut Checker, docstring: &Docstring) {
) = docstring.kind else { ) = docstring.kind else {
return return
}; };
if !is_overload(checker, cast::decorator_list(stmt)) { if !is_overload(&checker.ctx, cast::decorator_list(stmt)) {
return; return;
} }
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -36,7 +36,11 @@ pub fn non_imperative_mood(
.collect::<Vec<CallPath>>(); .collect::<Vec<CallPath>>();
if is_test(cast::name(parent)) if is_test(cast::name(parent))
|| is_property(checker, cast::decorator_list(parent), &property_decorators) || is_property(
&checker.ctx,
cast::decorator_list(parent),
&property_decorators,
)
{ {
return; return;
} }

View file

@ -138,7 +138,7 @@ pub fn not_missing(
false false
} }
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => { DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
if is_overload(checker, cast::decorator_list(stmt)) { if is_overload(&checker.ctx, cast::decorator_list(stmt)) {
true true
} else { } else {
if checker.settings.rules.enabled(&Rule::PublicFunction) { if checker.settings.rules.enabled(&Rule::PublicFunction) {
@ -151,8 +151,8 @@ pub fn not_missing(
} }
} }
DefinitionKind::Method(stmt) => { DefinitionKind::Method(stmt) => {
if is_overload(checker, cast::decorator_list(stmt)) if is_overload(&checker.ctx, cast::decorator_list(stmt))
|| is_override(checker, cast::decorator_list(stmt)) || is_override(&checker.ctx, cast::decorator_list(stmt))
{ {
true true
} else if is_init(cast::name(stmt)) { } else if is_init(cast::name(stmt)) {

View file

@ -820,7 +820,7 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
// If this is a non-static method, skip `cls` or `self`. // If this is a non-static method, skip `cls` or `self`.
usize::from( usize::from(
matches!(docstring.kind, DefinitionKind::Method(_)) matches!(docstring.kind, DefinitionKind::Method(_))
&& !is_staticmethod(checker, cast::decorator_list(parent)), && !is_staticmethod(&checker.ctx, cast::decorator_list(parent)),
), ),
) )
{ {

View file

@ -24,7 +24,7 @@ pub fn invalid_print_syntax(checker: &mut Checker, left: &Expr) {
if id != "print" { if id != "print" {
return; return;
} }
if !checker.is_builtin("print") { if !checker.ctx.is_builtin("print") {
return; return;
}; };
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -17,9 +17,9 @@ impl Violation for ReturnOutsideFunction {
} }
pub fn return_outside_function(checker: &mut Checker, stmt: &Stmt) { pub fn return_outside_function(checker: &mut Checker, stmt: &Stmt) {
if let Some(&index) = checker.scope_stack.last() { if let Some(&index) = checker.ctx.scope_stack.last() {
if matches!( if matches!(
checker.scopes[index].kind, checker.ctx.scopes[index].kind,
ScopeKind::Class(_) | ScopeKind::Module ScopeKind::Class(_) | ScopeKind::Module
) { ) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -19,11 +19,11 @@ impl Violation for UnusedAnnotation {
/// F842 /// F842
pub fn unused_annotation(checker: &mut Checker, scope: usize) { pub fn unused_annotation(checker: &mut Checker, scope: usize) {
let scope = &checker.scopes[scope]; let scope = &checker.ctx.scopes[scope];
for (name, binding) in scope for (name, binding) in scope
.bindings .bindings
.iter() .iter()
.map(|(name, index)| (name, &checker.bindings[*index])) .map(|(name, index)| (name, &checker.ctx.bindings[*index]))
{ {
if !binding.used() if !binding.used()
&& binding.kind.is_annotation() && binding.kind.is_annotation()

View file

@ -200,7 +200,7 @@ fn remove_unused_variable(
range.location == target.location && range.end_location == target.end_location.unwrap() range.location == target.location && range.end_location == target.end_location.unwrap()
}) { }) {
if matches!(target.node, ExprKind::Name { .. }) { if matches!(target.node, ExprKind::Name { .. }) {
return if targets.len() > 1 || contains_effect(checker, value) { return if targets.len() > 1 || contains_effect(&checker.ctx, value) {
// If the expression is complex (`x = foo()`), remove the assignment, // If the expression is complex (`x = foo()`), remove the assignment,
// but preserve the right-hand side. // but preserve the right-hand side.
Some(( Some((
@ -214,14 +214,11 @@ fn remove_unused_variable(
} else { } else {
// If (e.g.) assigning to a constant (`x = 1`), delete the entire statement. // If (e.g.) assigning to a constant (`x = 1`), delete the entire statement.
let parent = checker let parent = checker
.ctx
.child_to_parent .child_to_parent
.get(&RefEquality(stmt)) .get(&RefEquality(stmt))
.map(std::convert::Into::into); .map(Into::into);
let deleted: Vec<&Stmt> = checker let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
.deletions
.iter()
.map(std::convert::Into::into)
.collect();
match delete_stmt( match delete_stmt(
stmt, stmt,
parent, parent,
@ -249,7 +246,7 @@ fn remove_unused_variable(
} = &stmt.node } = &stmt.node
{ {
if matches!(target.node, ExprKind::Name { .. }) { if matches!(target.node, ExprKind::Name { .. }) {
return if contains_effect(checker, value) { return if contains_effect(&checker.ctx, value) {
// If the expression is complex (`x = foo()`), remove the assignment, // If the expression is complex (`x = foo()`), remove the assignment,
// but preserve the right-hand side. // but preserve the right-hand side.
Some(( Some((
@ -262,14 +259,11 @@ fn remove_unused_variable(
} else { } else {
// If assigning to a constant (`x = 1`), delete the entire statement. // If assigning to a constant (`x = 1`), delete the entire statement.
let parent = checker let parent = checker
.ctx
.child_to_parent .child_to_parent
.get(&RefEquality(stmt)) .get(&RefEquality(stmt))
.map(std::convert::Into::into); .map(Into::into);
let deleted: Vec<&Stmt> = checker let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
.deletions
.iter()
.map(std::convert::Into::into)
.collect();
match delete_stmt( match delete_stmt(
stmt, stmt,
parent, parent,
@ -319,7 +313,7 @@ fn remove_unused_variable(
/// F841 /// F841
pub fn unused_variable(checker: &mut Checker, scope: usize) { pub fn unused_variable(checker: &mut Checker, scope: usize) {
let scope = &checker.scopes[scope]; let scope = &checker.ctx.scopes[scope];
if scope.uses_locals && matches!(scope.kind, ScopeKind::Function(..)) { if scope.uses_locals && matches!(scope.kind, ScopeKind::Function(..)) {
return; return;
} }
@ -327,7 +321,7 @@ pub fn unused_variable(checker: &mut Checker, scope: usize) {
for (name, binding) in scope for (name, binding) in scope
.bindings .bindings
.iter() .iter()
.map(|(name, index)| (name, &checker.bindings[*index])) .map(|(name, index)| (name, &checker.ctx.bindings[*index]))
{ {
if !binding.used() if !binding.used()
&& binding.kind.is_assignment() && binding.kind.is_assignment()
@ -343,7 +337,7 @@ pub fn unused_variable(checker: &mut Checker, scope: usize) {
binding.range, binding.range,
); );
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
if let Some(stmt) = binding.source.as_ref().map(std::convert::Into::into) { if let Some(stmt) = binding.source.as_ref().map(Into::into) {
if let Some((kind, fix)) = remove_unused_variable(stmt, &binding.range, checker) if let Some((kind, fix)) = remove_unused_variable(stmt, &binding.range, checker)
{ {
if matches!(kind, DeletionKind::Whole) { if matches!(kind, DeletionKind::Whole) {

View file

@ -41,7 +41,7 @@ impl Violation for YieldOutsideFunction {
pub fn yield_outside_function(checker: &mut Checker, expr: &Expr) { pub fn yield_outside_function(checker: &mut Checker, expr: &Expr) {
if matches!( if matches!(
checker.current_scope().kind, checker.ctx.current_scope().kind,
ScopeKind::Class(_) | ScopeKind::Module ScopeKind::Class(_) | ScopeKind::Module
) { ) {
let keyword = match expr.node { let keyword = match expr.node {

View file

@ -18,9 +18,13 @@ impl Violation for DeprecatedLogWarn {
/// PGH002 - deprecated use of logging.warn /// PGH002 - deprecated use of logging.warn
pub fn deprecated_log_warn(checker: &mut Checker, func: &Expr) { pub fn deprecated_log_warn(checker: &mut Checker, func: &Expr) {
if checker.resolve_call_path(func).map_or(false, |call_path| { if checker
call_path.as_slice() == ["logging", "warn"] .ctx
}) { .resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["logging", "warn"]
})
{
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
DeprecatedLogWarn, DeprecatedLogWarn,
Range::from_located(func), Range::from_located(func),

View file

@ -23,7 +23,7 @@ pub fn no_eval(checker: &mut Checker, func: &Expr) {
if id != "eval" { if id != "eval" {
return; return;
} }
if !checker.is_builtin("eval") { if !checker.ctx.is_builtin("eval") {
return; return;
} }
checker checker

View file

@ -5,8 +5,8 @@ use crate::{
checkers::ast::Checker, checkers::ast::Checker,
}; };
pub fn in_dunder_init(checker: &mut Checker) -> bool { pub fn in_dunder_init(checker: &Checker) -> bool {
let scope = checker.current_scope(); let scope = checker.ctx.current_scope();
let ScopeKind::Function(FunctionDef { let ScopeKind::Function(FunctionDef {
name, name,
decorator_list, decorator_list,
@ -17,13 +17,13 @@ pub fn in_dunder_init(checker: &mut Checker) -> bool {
if *name != "__init__" { if *name != "__init__" {
return false; return false;
} }
let Some(parent) = checker.current_scope_parent() else { let Some(parent) = checker.ctx.current_scope_parent() else {
return false; return false;
}; };
if !matches!( if !matches!(
function_type::classify( function_type::classify(
checker, &checker.ctx,
parent, parent,
name, name,
decorator_list, decorator_list,

View file

@ -19,6 +19,7 @@ impl Violation for AwaitOutsideAsync {
/// PLE1142 /// PLE1142
pub fn await_outside_async(checker: &mut Checker, expr: &Expr) { pub fn await_outside_async(checker: &mut Checker, expr: &Expr) {
if !checker if !checker
.ctx
.current_scopes() .current_scopes()
.find_map(|scope| { .find_map(|scope| {
if let ScopeKind::Function(FunctionDef { async_, .. }) = &scope.kind { if let ScopeKind::Function(FunctionDef { async_, .. }) = &scope.kind {

View file

@ -28,9 +28,9 @@ impl Violation for ConsiderUsingSysExit {
/// Return `true` if the `module` was imported using a star import (e.g., `from /// Return `true` if the `module` was imported using a star import (e.g., `from
/// sys import *`). /// sys import *`).
fn is_module_star_imported(checker: &Checker, module: &str) -> bool { fn is_module_star_imported(checker: &Checker, module: &str) -> bool {
checker.current_scopes().any(|scope| { checker.ctx.current_scopes().any(|scope| {
scope.bindings.values().any(|index| { scope.bindings.values().any(|index| {
if let BindingKind::StarImportation(_, name) = &checker.bindings[*index].kind { if let BindingKind::StarImportation(_, name) = &checker.ctx.bindings[*index].kind {
name.as_ref().map(|name| name == module).unwrap_or_default() name.as_ref().map(|name| name == module).unwrap_or_default()
} else { } else {
false false
@ -42,11 +42,11 @@ fn is_module_star_imported(checker: &Checker, module: &str) -> bool {
/// Return the appropriate `sys.exit` reference based on the current set of /// Return the appropriate `sys.exit` reference based on the current set of
/// imports, or `None` is `sys.exit` hasn't been imported. /// imports, or `None` is `sys.exit` hasn't been imported.
fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -> Option<String> { fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -> Option<String> {
checker.current_scopes().find_map(|scope| { checker.ctx.current_scopes().find_map(|scope| {
scope scope
.bindings .bindings
.values() .values()
.find_map(|index| match &checker.bindings[*index].kind { .find_map(|index| match &checker.ctx.bindings[*index].kind {
// e.g. module=sys object=exit // e.g. module=sys object=exit
// `import sys` -> `sys.exit` // `import sys` -> `sys.exit`
// `import sys as sys2` -> `sys2.exit` // `import sys as sys2` -> `sys2.exit`
@ -107,7 +107,7 @@ pub fn consider_using_sys_exit(checker: &mut Checker, func: &Expr) {
if name == "exit" && is_module_star_imported(checker, "sys") { if name == "exit" && is_module_star_imported(checker, "sys") {
continue; continue;
} }
if !checker.is_builtin(name) { if !checker.ctx.is_builtin(name) {
continue; continue;
} }
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(

View file

@ -51,9 +51,9 @@ impl Violation for GlobalStatement {
/// PLW0603 /// PLW0603
pub fn global_statement(checker: &mut Checker, name: &str) { pub fn global_statement(checker: &mut Checker, name: &str) {
let scope = checker.current_scope(); let scope = checker.ctx.current_scope();
if let Some(index) = scope.bindings.get(name) { if let Some(index) = scope.bindings.get(name) {
let binding = &checker.bindings[*index]; let binding = &checker.ctx.bindings[*index];
if binding.kind.is_global() { if binding.kind.is_global() {
let diagnostic = Diagnostic::new( let diagnostic = Diagnostic::new(
GlobalStatement { GlobalStatement {

View file

@ -27,7 +27,7 @@ impl Violation for ConsiderMergingIsinstance {
/// PLR1701 /// PLR1701
pub fn merge_isinstance(checker: &mut Checker, expr: &Expr, op: &Boolop, values: &[Expr]) { pub fn merge_isinstance(checker: &mut Checker, expr: &Expr, op: &Boolop, values: &[Expr]) {
if !matches!(op, Boolop::Or) || !checker.is_builtin("isinstance") { if !matches!(op, Boolop::Or) || !checker.ctx.is_builtin("isinstance") {
return; return;
} }

Some files were not shown because too many files have changed in this diff Show more