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 crate::ast::context::Context;
use crate::ast::helpers::{map_callable, to_call_path};
use crate::ast::types::{Scope, ScopeKind};
use crate::checkers::ast::Checker;
const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"];
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.
pub fn classify(
checker: &Checker,
ctx: &Context,
scope: &Scope,
name: &str,
decorator_list: &[Expr],
@ -29,8 +29,7 @@ pub fn classify(
if decorator_list.iter().any(|expr| {
// The method is decorated with a static method decorator (like
// `@staticmethod`).
checker
.resolve_call_path(map_callable(expr))
ctx.resolve_call_path(map_callable(expr))
.map_or(false, |call_path| {
call_path.as_slice() == ["", "staticmethod"]
|| staticmethod_decorators
@ -43,7 +42,7 @@ pub fn classify(
// Special-case class method, like `__new__`.
|| scope.bases.iter().any(|expr| {
// 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
.iter()
.any(|(module, member)| call_path.as_slice() == [*module, *member])
@ -51,7 +50,7 @@ pub fn classify(
})
|| decorator_list.iter().any(|expr| {
// 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"] ||
classmethod_decorators
.iter()

View file

@ -13,10 +13,10 @@ use rustpython_parser::ast::{
use rustpython_parser::{lexer, Mode, StringKind, Tok};
use smallvec::{smallvec, SmallVec};
use crate::ast::context::Context;
use crate::ast::types::{Binding, BindingKind, CallPath, Range};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
use crate::source_code::{Generator, Indexer, Locator, Stylist};
/// 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}`.
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| {
checker
.resolve_call_path(expr)
ctx.resolve_call_path(expr)
.map_or(false, |call_path| call_path.as_slice() == target)
})
}
/// Return `true` if the `Expr` contains an expression that appears to include a
/// 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| {
// Accept empty initializers.
if let ExprKind::Call {
@ -125,7 +124,7 @@ pub fn contains_effect(checker: &Checker, expr: &Expr) -> bool {
|| id == "tuple"
|| id == "dict"
|| id == "frozenset")
&& checker.is_builtin(id);
&& ctx.is_builtin(id);
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()`.
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| {
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() == ["", "globals"]
|| call_path.as_slice() == ["", "vars"]

View file

@ -1,6 +1,7 @@
pub mod branch_detection;
pub mod cast;
pub mod comparable;
pub mod context;
pub mod function_type;
pub mod hashable;
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::{lexer, Mode, Tok};
use crate::ast::context::Context;
use crate::ast::helpers::any_over_expr;
use crate::ast::types::{BindingKind, Scope};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
bitflags! {
#[derive(Default)]
@ -19,7 +19,7 @@ bitflags! {
/// Extract the names bound to a given __all__ assignment.
pub fn extract_all_names(
checker: &Checker,
ctx: &Context,
stmt: &Stmt,
scope: &Scope,
) -> (Vec<String>, AllNamesFlags) {
@ -38,7 +38,7 @@ pub fn extract_all_names(
}
fn extract_elts<'a>(
checker: &'a Checker,
ctx: &'a Context,
expr: &'a Expr,
) -> (Option<&'a Vec<Expr>>, AllNamesFlags) {
match &expr.node {
@ -60,7 +60,7 @@ pub fn extract_all_names(
} => {
// Allow `tuple()` and `list()` calls.
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() == ["", "list"]
}) {
@ -96,7 +96,7 @@ pub fn extract_all_names(
// Grab the existing bound __all__ values.
if let StmtKind::AugAssign { .. } = &stmt.node {
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);
}
}
@ -113,7 +113,7 @@ pub fn extract_all_names(
let mut current_right = right;
loop {
// 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;
if let Some(elts) = elts {
add_to_names(&mut names, elts, &mut flags);
@ -125,7 +125,7 @@ pub fn extract_all_names(
current_left = left;
current_right = right;
} else {
let (elts, new_flags) = extract_elts(checker, current_left);
let (elts, new_flags) = extract_elts(ctx, current_left);
flags |= new_flags;
if let Some(elts) = elts {
add_to_names(&mut names, elts, &mut flags);
@ -134,7 +134,7 @@ pub fn extract_all_names(
}
}
} else {
let (elts, new_flags) = extract_elts(checker, value);
let (elts, new_flags) = extract_elts(ctx, value);
flags |= new_flags;
if let Some(elts) = elts {
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 {
checker
.ctx
.resolve_call_path(expr)
.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
pub fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
if checker
.ctx
.resolve_call_path(expr)
.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::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);
Some(name.to_string())
} else {
@ -51,7 +51,7 @@ pub fn is_overload_impl(checker: &Checker, definition: &Definition, overloaded_n
| DefinitionKind::NestedFunction(stmt)
| 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
} else {
let (name, ..) = match_function_def(stmt);

View file

@ -441,7 +441,7 @@ fn check_dynamically_typed<F>(
) where
F: FnOnce() -> String,
{
if checker.match_typing_expr(annotation, "Any") {
if checker.ctx.match_typing_expr(annotation, "Any") {
diagnostics.push(Diagnostic::new(
AnyType { name: func() },
Range::from_located(annotation),
@ -482,7 +482,8 @@ pub fn definition(
.skip(
// If this is a non-static method, skip `cls` or `self`.
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
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 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) {
diagnostics.push(Diagnostic::new(
MissingTypeCls {
@ -619,7 +620,7 @@ pub fn definition(
// (explicitly or implicitly).
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
.settings
.rules
@ -632,7 +633,8 @@ pub fn definition(
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
.settings

View file

@ -26,13 +26,19 @@ pub fn is_untyped_exception(type_: Option<&Expr>, checker: &Checker) -> bool {
type_.map_or(true, |type_| {
if let ExprKind::Tuple { elts, .. } = &type_.node {
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.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"]
})

View file

@ -103,6 +103,7 @@ pub fn bad_file_permissions(
keywords: &[Keyword],
) {
if checker
.ctx
.resolve_call_path(func)
.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,
..
} => {
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) {
return Some(unparse_expr(expr, checker.stylist));
}

View file

@ -48,7 +48,7 @@ pub fn hashlib_insecure_hash_functions(
args: &[Expr],
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"] {
Some(HashlibCall::New)
} else {

View file

@ -37,9 +37,13 @@ pub fn jinja2_autoescape_false(
args: &[Expr],
keywords: &[Keyword],
) {
if checker.resolve_call_path(func).map_or(false, |call_path| {
if checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["jinja2", "Environment"]
}) {
})
{
let call_args = SimpleCallArgs::new(args, keywords);
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],
keywords: &[Keyword],
) {
if checker.resolve_call_path(func).map_or(false, |call_path| {
if checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["logging", "config", "listen"]
}) {
})
{
let call_args = SimpleCallArgs::new(args, keywords);
if call_args.get_argument("verify", None).is_none() {

View file

@ -44,7 +44,7 @@ pub fn request_with_no_cert_validation(
args: &[Expr],
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[0] == "requests" && REQUESTS_HTTP_VERBS.contains(&call_path[1]) {
return Some("requests");

View file

@ -34,11 +34,15 @@ pub fn request_without_timeout(
args: &[Expr],
keywords: &[Keyword],
) {
if checker.resolve_call_path(func).map_or(false, |call_path| {
if checker
.ctx
.resolve_call_path(func)
.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);
if let Some(timeout_arg) = call_args.get_argument("timeout", None) {
if let Some(timeout) = match &timeout_arg.node {

View file

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

View file

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

View file

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

View file

@ -35,7 +35,7 @@ pub fn blind_except(
return;
};
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 body.iter().any(|stmt| {
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()
.map_or(false, |arg| arg == "metaclass")
&& checker
.ctx
.resolve_call_path(&keyword.node.value)
.map_or(false, |call_path| {
call_path.as_slice() == ["abc", "ABCMeta"]
})
}) || bases.iter().any(|base| {
checker
.ctx
.resolve_call_path(base)
.map_or(false, |call_path| call_path.as_slice() == ["abc", "ABC"])
})
@ -106,7 +108,7 @@ pub fn abstract_base_class(
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;
if !checker
@ -117,7 +119,10 @@ pub fn abstract_base_class(
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(
EmptyMethodWithoutAbstractDecorator {
name: format!("{name}.{method_name}"),

View file

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

View file

@ -19,7 +19,10 @@ impl Violation for CachedInstanceMethod {
}
fn is_cache_func(checker: &Checker, expr: &Expr) -> bool {
checker.resolve_call_path(expr).map_or(false, |call_path| {
checker
.ctx
.resolve_call_path(expr)
.map_or(false, |call_path| {
call_path.as_slice() == ["functools", "lru_cache"]
|| call_path.as_slice() == ["functools", "cache"]
})
@ -27,7 +30,7 @@ fn is_cache_func(checker: &Checker, expr: &Expr) -> bool {
/// B019
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;
}
for decorator in decorator_list {

View file

@ -38,7 +38,10 @@ const IMMUTABLE_FUNCS: &[&[&str]] = &[
];
fn is_immutable_func(checker: &Checker, func: &Expr, extend_immutable_calls: &[CallPath]) -> bool {
checker.resolve_call_path(func).map_or(false, |call_path| {
checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
IMMUTABLE_FUNCS
.iter()
.any(|target| call_path.as_slice() == *target)

View file

@ -67,7 +67,10 @@ const IMMUTABLE_GENERIC_TYPES: &[&[&str]] = &[
];
pub fn is_mutable_func(checker: &Checker, func: &Expr) -> bool {
checker.resolve_call_path(func).map_or(false, |call_path| {
checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
MUTABLE_FUNCS
.iter()
.any(|target| call_path.as_slice() == *target)
@ -89,16 +92,20 @@ fn is_mutable_expr(checker: &Checker, expr: &Expr) -> bool {
fn is_immutable_annotation(checker: &Checker, expr: &Expr) -> bool {
match &expr.node {
ExprKind::Name { .. } | ExprKind::Attribute { .. } => {
checker.resolve_call_path(expr).map_or(false, |call_path| {
ExprKind::Name { .. } | ExprKind::Attribute { .. } => checker
.ctx
.resolve_call_path(expr)
.map_or(false, |call_path| {
IMMUTABLE_TYPES
.iter()
.chain(IMMUTABLE_GENERIC_TYPES)
.any(|target| call_path.as_slice() == *target)
})
}
}),
ExprKind::Subscript { value, slice, .. } => {
checker.resolve_call_path(value).map_or(false, |call_path| {
checker
.ctx
.resolve_call_path(value)
.map_or(false, |call_path| {
if IMMUTABLE_GENERIC_TYPES
.iter()
.any(|target| call_path.as_slice() == *target)

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
// (which is a `Stmt`) if the `Expr` is already being used as a `Stmt`
// (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() {
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.
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
// 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 certainty.into() && checker.patch(diagnostic.kind.rule()) {
// Find the `BindingKind::LoopVar` corresponding to the name.
let scope = checker.current_scope();
let scope = checker.ctx.current_scope();
let binding = scope
.bindings
.get(name)
.into_iter()
.chain(scope.rebounds.get(name).into_iter().flatten())
.find_map(|index| {
let binding = &checker.bindings[*index];
let binding = &checker.ctx.bindings[*index];
binding
.source
.as_ref()

View file

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

View file

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

View file

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

View file

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

View file

@ -56,7 +56,7 @@ pub fn unnecessary_comprehension(
ExprKind::SetComp { .. } => "set",
_ => return,
};
if !checker.is_builtin(id) {
if !checker.ctx.is_builtin(id) {
return;
}
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 {
return;
};
if !checker.is_builtin(inner) || !checker.is_builtin(outer) {
if !checker.ctx.is_builtin(inner) || !checker.ctx.is_builtin(outer) {
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 {
return;
};
if !checker.is_builtin("list") {
if !checker.ctx.is_builtin("list") {
return;
}
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 {
return;
};
if !checker.is_builtin("set") {
if !checker.ctx.is_builtin("set") {
return;
}
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 {
return;
};
if !checker.is_builtin("list") {
if !checker.ctx.is_builtin("list") {
return;
}
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 {
return;
};
if !checker.is_builtin("dict") {
if !checker.ctx.is_builtin("dict") {
return;
}
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 {
return;
};
if !checker.is_builtin("set") {
if !checker.ctx.is_builtin("set") {
return;
}
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 {
return;
};
if !checker.is_builtin("dict") {
if !checker.ctx.is_builtin("dict") {
return;
}
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 {
return;
};
if !checker.is_builtin("set") {
if !checker.ctx.is_builtin("set") {
return;
}
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 {
return;
};
if !checker.is_builtin("list") {
if !checker.ctx.is_builtin("list") {
return;
}
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 {
return;
};
if !checker.is_builtin("tuple") {
if !checker.ctx.is_builtin("tuple") {
return;
}
let argument_kind = match argument {

View file

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

View file

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

View file

@ -124,9 +124,13 @@ pub fn call_datetime_without_tzinfo(
keywords: &[Keyword],
location: Range,
) {
if !checker.resolve_call_path(func).map_or(false, |call_path| {
if !checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime"]
}) {
})
{
return;
}
@ -153,9 +157,13 @@ pub fn call_datetime_without_tzinfo(
/// It uses the system local timezone.
/// Use `datetime.datetime.now(tz=)` instead.
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
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "today"]
}) {
})
{
checker
.diagnostics
.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
/// current time in UTC is by calling `datetime.now(timezone.utc)`.
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
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "utcnow"]
}) {
})
{
checker
.diagnostics
.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,
/// tz=timezone.utc)`.
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
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "utcfromtimestamp"]
}) {
})
{
checker
.diagnostics
.push(Diagnostic::new(CallDatetimeUtcfromtimestamp, location));
@ -207,9 +223,13 @@ pub fn call_datetime_now_without_tzinfo(
keywords: &[Keyword],
location: Range,
) {
if !checker.resolve_call_path(func).map_or(false, |call_path| {
if !checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "now"]
}) {
})
{
return;
}
@ -245,9 +265,13 @@ pub fn call_datetime_fromtimestamp(
keywords: &[Keyword],
location: Range,
) {
if !checker.resolve_call_path(func).map_or(false, |call_path| {
if !checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "fromtimestamp"]
}) {
})
{
return;
}
@ -282,9 +306,13 @@ pub fn call_datetime_strptime_without_zone(
args: &[Expr],
location: Range,
) {
if !checker.resolve_call_path(func).map_or(false, |call_path| {
if !checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "datetime", "strptime"]
}) {
})
{
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(
CallDatetimeStrptimeWithoutZone,
location,
@ -335,9 +363,13 @@ pub fn call_datetime_strptime_without_zone(
/// It uses the system local timezone.
/// Use `datetime.datetime.now(tz=).date()` instead.
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
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "date", "today"]
}) {
})
{
checker
.diagnostics
.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.
/// Use `datetime.datetime.fromtimestamp(, tz=).date()` instead.
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
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["datetime", "date", "fromtimestamp"]
}) {
})
{
checker
.diagnostics
.push(Diagnostic::new(CallDateFromtimestamp, location));

View file

@ -46,7 +46,7 @@ const DEBUGGERS: &[&[&str]] = &[
/// Checks for the presence of a debugger call.
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
.iter()
.find(|target| call_path.as_slice() == **target)

View file

@ -4,14 +4,20 @@ use crate::checkers::ast::Checker;
/// 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 {
checker.resolve_call_path(base).map_or(false, |call_path| {
checker
.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.
pub fn is_model_form(checker: &Checker, base: &Expr) -> bool {
checker.resolve_call_path(base).map_or(false, |call_path| {
checker
.ctx
.resolve_call_path(base)
.map_or(false, |call_path| {
call_path.as_slice() == ["django", "forms", "ModelForm"]
|| call_path.as_slice() == ["django", "forms", "models", "ModelForm"]
})
@ -19,7 +25,7 @@ pub fn is_model_form(checker: &Checker, base: &Expr) -> bool {
/// 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> {
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();
if !call_path.starts_with(&["django", "db", "models"]) {
return None;

View file

@ -46,9 +46,13 @@ pub fn locals_in_render_function(
args: &[Expr],
keywords: &[Keyword],
) {
if !checker.resolve_call_path(func).map_or(false, |call_path| {
if !checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["django", "shortcuts", "render"]
}) {
})
{
return;
}
@ -83,6 +87,7 @@ fn is_locals_call(checker: &Checker, expr: &Expr) -> bool {
return false
};
checker
.ctx
.resolve_call_path(func)
.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, .. } => {
if checker
.ctx
.resolve_call_path(func)
.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
.enabled(&Rule::LoggingRedundantExcInfo)
{
if !checker.in_exception_handler() {
if !checker.ctx.in_exception_handler() {
return;
}
if let Some(exc_info) = find_keyword(keywords, "exc_info") {
@ -193,7 +194,10 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
..
}
) || if let ExprKind::Call { func, .. } = &exc_info.node.value.node {
checker.resolve_call_path(func).map_or(false, |call_path| {
checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["sys", "exc_info"]
})
} else {

View file

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

View file

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

View file

@ -70,9 +70,13 @@ pub fn bad_version_info_comparison(
return;
};
if !checker.resolve_call_path(left).map_or(false, |call_path| {
if !checker
.ctx
.resolve_call_path(left)
.map_or(false, |call_path| {
call_path.as_slice() == ["sys", "version_info"]
}) {
})
{
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 {
let Some(kind) = checker.resolve_call_path(func).and_then(|call_path| {
if checker.match_typing_call_path(&call_path, "ParamSpec") {
let Some(kind) = checker.ctx.resolve_call_path(func).and_then(|call_path| {
if checker.ctx.match_typing_call_path(&call_path, "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)
} else if checker.match_typing_call_path(&call_path, "TypeVarTuple") {
} else if checker.ctx.match_typing_call_path(&call_path, "TypeVarTuple") {
Some(VarKind::TypeVarTuple)
} else {
None

View file

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

View file

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

View file

@ -195,8 +195,8 @@ pub fn unittest_assertion(
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
// the assertion is part of a larger expression.
let fixable = checker.current_expr_parent().is_none()
&& matches!(checker.current_stmt().node, StmtKind::Expr { .. })
let fixable = checker.ctx.current_expr_parent().is_none()
&& matches!(checker.ctx.current_stmt().node, StmtKind::Expr { .. })
&& !has_comments_in(Range::from_located(expr), checker.locator);
let mut diagnostic = Diagnostic::new(
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 {
checker.resolve_call_path(call).map_or(false, |call_path| {
checker
.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 {
checker
.ctx
.resolve_call_path(if let ExprKind::Call { func, .. } = &decorator.node {
func
} else {
@ -45,6 +49,7 @@ pub fn is_pytest_mark(decorator: &Expr) -> bool {
pub fn is_pytest_yield_fixture(decorator: &Expr, checker: &Checker) -> bool {
checker
.ctx
.resolve_call_path(map_callable(decorator))
.map_or(false, |call_path| {
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 {
checker
.ctx
.resolve_call_path(decorator)
.map_or(false, |call_path| {
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 {
checker
.ctx
.resolve_call_path(map_callable(decorator))
.map_or(false, |call_path| {
call_path.as_slice() == ["pytest", "mark", "parametrize"]

View file

@ -45,7 +45,10 @@ impl Violation for RaisesWithoutException {
}
fn is_pytest_raises(checker: &Checker, func: &Expr) -> bool {
checker.resolve_call_path(func).map_or(false, |call_path| {
checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["pytest", "raises"]
})
}
@ -136,7 +139,10 @@ pub fn complex_raises(checker: &mut Checker, stmt: &Stmt, items: &[Withitem], bo
/// PT011
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
.ctx
.resolve_call_path(exception)
.and_then(|call_path| {
let is_broad_exception = checker
.settings
.flake8_pytest_style
@ -154,7 +160,8 @@ fn exception_needs_match(checker: &mut Checker, exception: &Expr) {
} else {
None
}
}) {
})
{
checker.diagnostics.push(Diagnostic::new(
RaisesTooBroad {
exception: call_path,

View file

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

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.
if checker
.ctx
.scopes
.iter()
.rev()
@ -99,6 +100,7 @@ pub fn private_member_access(checker: &mut Checker, expr: &Expr) {
.map_or(false, |class_def| {
if call_path.as_slice() == [class_def.name] {
checker
.ctx
.find_binding(class_def.name)
.map_or(false, |binding| {
// 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" {
continue;
}
if !checker.is_builtin("isinstance") {
if !checker.ctx.is_builtin("isinstance") {
continue;
}
@ -313,7 +313,7 @@ pub fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
// Avoid rewriting (e.g.) `a == "foo" or a == f()`.
if comparators
.iter()
.any(|expr| contains_effect(checker, expr))
.any(|expr| contains_effect(&checker.ctx, expr))
{
continue;
}
@ -395,7 +395,7 @@ pub fn expr_and_not_expr(checker: &mut Checker, expr: &Expr) {
return;
}
if contains_effect(checker, expr) {
if contains_effect(&checker.ctx, expr) {
return;
}
@ -449,7 +449,7 @@ pub fn expr_or_not_expr(checker: &mut Checker, expr: &Expr) {
return;
}
if contains_effect(checker, expr) {
if contains_effect(&checker.ctx, expr) {
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 {
return;
};
if contains_effect(checker, expr) {
if contains_effect(&checker.ctx, expr) {
return;
}
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 {
return;
};
if contains_effect(checker, expr) {
if contains_effect(&checker.ctx, expr) {
return;
}
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 {
return;
};
if !checker.resolve_call_path(func).map_or(false, |call_path| {
call_path.as_slice() == ["os", "environ", "get"] || call_path.as_slice() == ["os", "getenv"]
}) {
if !checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["os", "environ", "get"]
|| call_path.as_slice() == ["os", "getenv"]
})
{
return;
}

View file

@ -347,7 +347,7 @@ pub fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
let fixable = matches!(if_return, Bool::True)
&& matches!(else_return, Bool::False)
&& !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(
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.
if contains_call_path(checker, test, &["sys", "version_info"]) {
if contains_call_path(&checker.ctx, test, &["sys", "version_info"]) {
return;
}
// Avoid suggesting ternary for `if sys.platform.startswith("...")`-style
// checks.
if contains_call_path(checker, test, &["sys", "platform"]) {
if contains_call_path(&checker.ctx, test, &["sys", "platform"]) {
return;
}
@ -629,7 +629,7 @@ pub fn manual_dict_lookup(
};
if value
.as_ref()
.map_or(false, |value| contains_effect(checker, value))
.map_or(false, |value| contains_effect(&checker.ctx, value))
{
return;
}
@ -702,7 +702,7 @@ pub fn manual_dict_lookup(
};
if value
.as_ref()
.map_or(false, |value| contains_effect(checker, value))
.map_or(false, |value| contains_effect(&checker.ctx, value))
{
return;
};
@ -784,7 +784,7 @@ pub fn use_dict_get_with_default(
}
// Check that the default value is not "complex".
if contains_effect(checker, default_val) {
if contains_effect(&checker.ctx, default_val) {
return;
}

View file

@ -106,7 +106,7 @@ pub fn explicit_true_false_in_ifexpr(
expr.location,
expr.end_location.unwrap(),
));
} else if checker.is_builtin("bool") {
} else if checker.ctx.is_builtin("bool") {
diagnostic.amend(Fix::replacement(
unparse_expr(
&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]) {
return;
}
if is_exception_check(checker.current_stmt()) {
if is_exception_check(checker.ctx.current_stmt()) {
return;
}
// 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) {
return;
}
@ -139,12 +139,12 @@ pub fn negation_with_not_equal_op(
if !matches!(&ops[..], [Cmpop::NotEq]) {
return;
}
if is_exception_check(checker.current_stmt()) {
if is_exception_check(checker.ctx.current_stmt()) {
return;
}
// 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) {
return;
}

View file

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

View file

@ -96,7 +96,7 @@ pub fn name_or_parent_is_banned<T>(
/// TID251
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
.settings
.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:`
if checker.resolve_call_path(test).map_or(false, |call_path| {
if checker
.ctx
.resolve_call_path(test)
.map_or(false, |call_path| {
call_path.as_slice() == ["typing", "TYPE_CHECKING"]
}) {
})
{
return true;
}

View file

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

View file

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

View file

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

View file

@ -49,7 +49,7 @@ impl AlwaysAutofixableViolation for NumpyDeprecatedTypeAlias {
/// NPY001
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"]
|| call_path.as_slice() == ["numpy", "int"]
|| call_path.as_slice() == ["numpy", "float"]

View file

@ -59,7 +59,7 @@ impl Violation for NumpyLegacyRandom {
/// NPY002
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
if call_path.as_slice() == ["numpy", "random", "seed"]
|| 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()`).
if let Some(parent) = checker.current_expr_parent() {
if let Some(parent) = checker.ctx.current_expr_parent() {
if matches!(parent.node, ExprKind::Call { .. }) {
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
// irrelevant bindings (like imports).
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!(
binding.kind,
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
// irrelevant bindings (like non-Pandas imports).
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 {
module != &"pandas"
} else {

View file

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

View file

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

View file

@ -61,7 +61,7 @@ pub fn invalid_first_argument_name_for_method(
) -> Option<Diagnostic> {
if !matches!(
function_type::classify(
checker,
&checker.ctx,
scope,
name,
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) {
if checker.seen_import_boundary && stmt.location.column() == 0 {
if checker.ctx.seen_import_boundary && stmt.location.column() == 0 {
checker.diagnostics.push(Diagnostic::new(
ModuleImportNotAtTopOfFile,
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
// rewritten function definition to be equivalent.
// 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(
LambdaAssignment {

View file

@ -85,7 +85,7 @@ pub fn should_ignore_definition(
| DefinitionKind::Method(parent) = definition.kind
{
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
.iter()
.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 {
return
};
if !is_overload(checker, cast::decorator_list(stmt)) {
if !is_overload(&checker.ctx, cast::decorator_list(stmt)) {
return;
}
checker.diagnostics.push(Diagnostic::new(

View file

@ -36,7 +36,11 @@ pub fn non_imperative_mood(
.collect::<Vec<CallPath>>();
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;
}

View file

@ -138,7 +138,7 @@ pub fn not_missing(
false
}
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
if is_overload(checker, cast::decorator_list(stmt)) {
if is_overload(&checker.ctx, cast::decorator_list(stmt)) {
true
} else {
if checker.settings.rules.enabled(&Rule::PublicFunction) {
@ -151,8 +151,8 @@ pub fn not_missing(
}
}
DefinitionKind::Method(stmt) => {
if is_overload(checker, cast::decorator_list(stmt))
|| is_override(checker, cast::decorator_list(stmt))
if is_overload(&checker.ctx, cast::decorator_list(stmt))
|| is_override(&checker.ctx, cast::decorator_list(stmt))
{
true
} 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`.
usize::from(
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" {
return;
}
if !checker.is_builtin("print") {
if !checker.ctx.is_builtin("print") {
return;
};
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) {
if let Some(&index) = checker.scope_stack.last() {
if let Some(&index) = checker.ctx.scope_stack.last() {
if matches!(
checker.scopes[index].kind,
checker.ctx.scopes[index].kind,
ScopeKind::Class(_) | ScopeKind::Module
) {
checker.diagnostics.push(Diagnostic::new(

View file

@ -19,11 +19,11 @@ impl Violation for UnusedAnnotation {
/// F842
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
.bindings
.iter()
.map(|(name, index)| (name, &checker.bindings[*index]))
.map(|(name, index)| (name, &checker.ctx.bindings[*index]))
{
if !binding.used()
&& 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()
}) {
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,
// but preserve the right-hand side.
Some((
@ -214,14 +214,11 @@ fn remove_unused_variable(
} else {
// If (e.g.) assigning to a constant (`x = 1`), delete the entire statement.
let parent = checker
.ctx
.child_to_parent
.get(&RefEquality(stmt))
.map(std::convert::Into::into);
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(std::convert::Into::into)
.collect();
.map(Into::into);
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
match delete_stmt(
stmt,
parent,
@ -249,7 +246,7 @@ fn remove_unused_variable(
} = &stmt.node
{
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,
// but preserve the right-hand side.
Some((
@ -262,14 +259,11 @@ fn remove_unused_variable(
} else {
// If assigning to a constant (`x = 1`), delete the entire statement.
let parent = checker
.ctx
.child_to_parent
.get(&RefEquality(stmt))
.map(std::convert::Into::into);
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(std::convert::Into::into)
.collect();
.map(Into::into);
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
match delete_stmt(
stmt,
parent,
@ -319,7 +313,7 @@ fn remove_unused_variable(
/// F841
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(..)) {
return;
}
@ -327,7 +321,7 @@ pub fn unused_variable(checker: &mut Checker, scope: usize) {
for (name, binding) in scope
.bindings
.iter()
.map(|(name, index)| (name, &checker.bindings[*index]))
.map(|(name, index)| (name, &checker.ctx.bindings[*index]))
{
if !binding.used()
&& binding.kind.is_assignment()
@ -343,7 +337,7 @@ pub fn unused_variable(checker: &mut Checker, scope: usize) {
binding.range,
);
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 matches!(kind, DeletionKind::Whole) {

View file

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

View file

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

View file

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

View file

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

View file

@ -19,6 +19,7 @@ impl Violation for AwaitOutsideAsync {
/// PLE1142
pub fn await_outside_async(checker: &mut Checker, expr: &Expr) {
if !checker
.ctx
.current_scopes()
.find_map(|scope| {
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
/// sys import *`).
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| {
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()
} else {
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
/// imports, or `None` is `sys.exit` hasn't been imported.
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
.bindings
.values()
.find_map(|index| match &checker.bindings[*index].kind {
.find_map(|index| match &checker.ctx.bindings[*index].kind {
// e.g. module=sys object=exit
// `import sys` -> `sys.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") {
continue;
}
if !checker.is_builtin(name) {
if !checker.ctx.is_builtin(name) {
continue;
}
let mut diagnostic = Diagnostic::new(

View file

@ -51,9 +51,9 @@ impl Violation for GlobalStatement {
/// PLW0603
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) {
let binding = &checker.bindings[*index];
let binding = &checker.ctx.bindings[*index];
if binding.kind.is_global() {
let diagnostic = Diagnostic::new(
GlobalStatement {

View file

@ -27,7 +27,7 @@ impl Violation for ConsiderMergingIsinstance {
/// PLR1701
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;
}

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