Use SemanticModel in lieu of Checker in more methods (#4568)

This commit is contained in:
Charlie Marsh 2023-05-21 22:58:47 -04:00 committed by GitHub
parent 19c4b7bee6
commit d70f899f71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 199 additions and 199 deletions

View file

@ -778,7 +778,7 @@ where
if self.settings.rules.any_enabled(&[
Rule::MutableDataclassDefault,
Rule::FunctionCallInDataclassDefaultArgument,
]) && ruff::rules::is_dataclass(self, decorator_list)
]) && ruff::rules::is_dataclass(&self.model, decorator_list)
{
if self.settings.rules.enabled(Rule::MutableDataclassDefault) {
ruff::rules::mutable_dataclass_default(self, body);
@ -5729,7 +5729,7 @@ impl<'a> Checker<'a> {
// classes, etc.).
if !overloaded_name.map_or(false, |overloaded_name| {
flake8_annotations::helpers::is_overload_impl(
self,
&self.model,
definition,
&overloaded_name,
)
@ -5741,7 +5741,8 @@ impl<'a> Checker<'a> {
*visibility,
));
}
overloaded_name = flake8_annotations::helpers::overloaded_name(self, definition);
overloaded_name =
flake8_annotations::helpers::overloaded_name(&self.model, definition);
}
// flake8-pyi
@ -5756,7 +5757,7 @@ impl<'a> Checker<'a> {
// pydocstyle
if enforce_docstrings {
if pydocstyle::helpers::should_ignore_definition(
self,
&self.model,
definition,
&self.settings.pydocstyle.ignore_decorators,
) {

View file

@ -3,6 +3,7 @@ use rustpython_parser::ast::{self, Cmpop, Constant, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::model::SemanticModel;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
@ -113,16 +114,15 @@ impl Violation for SysVersionSlice1 {
}
}
fn is_sys(checker: &Checker, expr: &Expr, target: &str) -> bool {
checker
.model
fn is_sys(model: &SemanticModel, expr: &Expr, target: &str) -> bool {
model
.resolve_call_path(expr)
.map_or(false, |call_path| call_path.as_slice() == ["sys", target])
}
/// YTT101, YTT102, YTT301, YTT303
pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
if is_sys(checker, value, "version") {
if is_sys(&checker.model, value, "version") {
match slice {
Expr::Slice(ast::ExprSlice {
lower: None,
@ -176,7 +176,7 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &[Expr]) {
match left {
Expr::Subscript(ast::ExprSubscript { value, slice, .. })
if is_sys(checker, value, "version_info") =>
if is_sys(&checker.model, value, "version_info") =>
{
if let Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
@ -220,7 +220,7 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], compara
}
Expr::Attribute(ast::ExprAttribute { value, attr, .. })
if is_sys(checker, value, "version_info") && attr == "minor" =>
if is_sys(&checker.model, value, "version_info") && attr == "minor" =>
{
if let (
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
@ -245,7 +245,7 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], compara
_ => {}
}
if is_sys(checker, left, "version") {
if is_sys(&checker.model, left, "version") {
if let (
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
[Expr::Constant(ast::ExprConstant {

View file

@ -3,8 +3,7 @@ use rustpython_parser::ast::{self, Arguments, Expr, Stmt};
use ruff_python_ast::cast;
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
use crate::checkers::ast::Checker;
use ruff_python_semantic::model::SemanticModel;
pub(super) fn match_function_def(
stmt: &Stmt,
@ -37,14 +36,14 @@ pub(super) fn match_function_def(
}
/// Return the name of the function, if it's overloaded.
pub(crate) fn overloaded_name(checker: &Checker, definition: &Definition) -> Option<String> {
pub(crate) fn overloaded_name(model: &SemanticModel, definition: &Definition) -> Option<String> {
if let Definition::Member(Member {
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
stmt,
..
}) = definition
{
if visibility::is_overload(&checker.model, cast::decorator_list(stmt)) {
if visibility::is_overload(model, cast::decorator_list(stmt)) {
let (name, ..) = match_function_def(stmt);
Some(name.to_string())
} else {
@ -58,7 +57,7 @@ pub(crate) fn overloaded_name(checker: &Checker, definition: &Definition) -> Opt
/// Return `true` if the definition is the implementation for an overloaded
/// function.
pub(crate) fn is_overload_impl(
checker: &Checker,
model: &SemanticModel,
definition: &Definition,
overloaded_name: &str,
) -> bool {
@ -68,7 +67,7 @@ pub(crate) fn is_overload_impl(
..
}) = definition
{
if visibility::is_overload(&checker.model, cast::decorator_list(stmt)) {
if visibility::is_overload(model, cast::decorator_list(stmt)) {
false
} else {
let (name, ..) = match_function_def(stmt);

View file

@ -8,6 +8,7 @@ use ruff_python_ast::{cast, helpers};
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::analyze::visibility::Visibility;
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
use ruff_python_semantic::model::SemanticModel;
use ruff_python_stdlib::typing::SIMPLE_MAGIC_RETURN_TYPES;
use crate::checkers::ast::Checker;
@ -430,7 +431,7 @@ fn is_none_returning(body: &[Stmt]) -> bool {
/// ANN401
fn check_dynamically_typed<F>(
checker: &Checker,
model: &SemanticModel,
annotation: &Expr,
func: F,
diagnostics: &mut Vec<Diagnostic>,
@ -438,7 +439,7 @@ fn check_dynamically_typed<F>(
) where
F: FnOnce() -> String,
{
if !is_overridden && checker.model.match_typing_expr(annotation, "Any") {
if !is_overridden && model.match_typing_expr(annotation, "Any") {
diagnostics.push(Diagnostic::new(
AnyType { name: func() },
annotation.range(),
@ -500,7 +501,7 @@ pub(crate) fn definition(
has_any_typed_arg = true;
if checker.settings.rules.enabled(Rule::AnyType) {
check_dynamically_typed(
checker,
&checker.model,
annotation,
|| arg.arg.to_string(),
&mut diagnostics,
@ -535,7 +536,7 @@ pub(crate) fn definition(
if checker.settings.rules.enabled(Rule::AnyType) {
let name = &arg.arg;
check_dynamically_typed(
checker,
&checker.model,
expr,
|| format!("*{name}"),
&mut diagnostics,
@ -567,7 +568,7 @@ pub(crate) fn definition(
if checker.settings.rules.enabled(Rule::AnyType) {
let name = &arg.arg;
check_dynamically_typed(
checker,
&checker.model,
expr,
|| format!("**{name}"),
&mut diagnostics,
@ -625,7 +626,7 @@ pub(crate) fn definition(
has_typed_return = true;
if checker.settings.rules.enabled(Rule::AnyType) {
check_dynamically_typed(
checker,
&checker.model,
expr,
|| name.to_string(),
&mut diagnostics,

View file

@ -2,7 +2,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_parser::ast::{self, Constant, Expr};
use crate::checkers::ast::Checker;
use ruff_python_semantic::model::SemanticModel;
static PASSWORD_CANDIDATE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(^|_)(?i)(pas+wo?r?d|pass(phrase)?|pwd|token|secrete?)($|_)").unwrap()
@ -22,26 +22,20 @@ pub(crate) fn matches_password_name(string: &str) -> bool {
PASSWORD_CANDIDATE_REGEX.is_match(string)
}
pub(crate) fn is_untyped_exception(type_: Option<&Expr>, checker: &Checker) -> bool {
pub(crate) fn is_untyped_exception(type_: Option<&Expr>, model: &SemanticModel) -> bool {
type_.map_or(true, |type_| {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_ {
elts.iter().any(|type_| {
checker
.model
.resolve_call_path(type_)
.map_or(false, |call_path| {
call_path.as_slice() == ["", "Exception"]
|| call_path.as_slice() == ["", "BaseException"]
})
})
} else {
checker
.model
.resolve_call_path(type_)
.map_or(false, |call_path| {
model.resolve_call_path(type_).map_or(false, |call_path| {
call_path.as_slice() == ["", "Exception"]
|| call_path.as_slice() == ["", "BaseException"]
})
})
} else {
model.resolve_call_path(type_).map_or(false, |call_path| {
call_path.as_slice() == ["", "Exception"]
|| call_path.as_slice() == ["", "BaseException"]
})
}
})
}

View file

@ -27,7 +27,7 @@ pub(crate) fn try_except_continue(
) {
if body.len() == 1
&& body[0].is_continue_stmt()
&& (check_typed_exception || is_untyped_exception(type_, checker))
&& (check_typed_exception || is_untyped_exception(type_, &checker.model))
{
checker
.diagnostics

View file

@ -27,7 +27,7 @@ pub(crate) fn try_except_pass(
) {
if body.len() == 1
&& body[0].is_pass_stmt()
&& (check_typed_exception || is_untyped_exception(type_, checker))
&& (check_typed_exception || is_untyped_exception(type_, &checker.model))
{
checker
.diagnostics

View file

@ -58,7 +58,7 @@ pub(crate) fn blind_except(
if body.iter().any(|stmt| {
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
if let Expr::Call(ast::ExprCall { func, keywords, .. }) = value.as_ref() {
if logging::is_logger_candidate(&checker.model, func) {
if logging::is_logger_candidate(func, &checker.model) {
if let Some(attribute) = func.as_attribute_expr() {
let attr = attribute.attr.as_str();
if attr == "exception" {

View file

@ -2,6 +2,7 @@ use rustpython_parser::ast::{self, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::model::SemanticModel;
use ruff_python_semantic::scope::ScopeKind;
use crate::checkers::ast::Checker;
@ -18,14 +19,11 @@ impl Violation for CachedInstanceMethod {
}
}
fn is_cache_func(checker: &Checker, expr: &Expr) -> bool {
checker
.model
.resolve_call_path(expr)
.map_or(false, |call_path| {
call_path.as_slice() == ["functools", "lru_cache"]
|| call_path.as_slice() == ["functools", "cache"]
})
fn is_cache_func(model: &SemanticModel, expr: &Expr) -> bool {
model.resolve_call_path(expr).map_or(false, |call_path| {
call_path.as_slice() == ["functools", "lru_cache"]
|| call_path.as_slice() == ["functools", "cache"]
})
}
/// B019
@ -44,7 +42,7 @@ pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Ex
}
for decorator in decorator_list {
if is_cache_func(
checker,
&checker.model,
match decorator {
Expr::Call(ast::ExprCall { func, .. }) => func,
_ => decorator,

View file

@ -4,11 +4,11 @@ use rustpython_parser::ast::{self, Arguments, Constant, Expr, Ranged};
use ruff_diagnostics::Violation;
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::from_qualified_name;
use ruff_python_ast::call_path::{compose_call_path, CallPath};
use ruff_python_ast::call_path::{compose_call_path, from_qualified_name, CallPath};
use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor;
use ruff_python_semantic::analyze::typing::is_immutable_func;
use ruff_python_semantic::model::SemanticModel;
use crate::checkers::ast::Checker;
use crate::rules::flake8_bugbear::rules::mutable_argument_default::is_mutable_func;
@ -73,9 +73,19 @@ impl Violation for FunctionCallInDefaultArgument {
}
struct ArgumentDefaultVisitor<'a> {
checker: &'a Checker<'a>,
diagnostics: Vec<(DiagnosticKind, TextRange)>,
model: &'a SemanticModel<'a>,
extend_immutable_calls: Vec<CallPath<'a>>,
diagnostics: Vec<(DiagnosticKind, TextRange)>,
}
impl<'a> ArgumentDefaultVisitor<'a> {
fn new(model: &'a SemanticModel<'a>, extend_immutable_calls: Vec<CallPath<'a>>) -> Self {
Self {
model,
extend_immutable_calls,
diagnostics: Vec::new(),
}
}
}
impl<'a, 'b> Visitor<'b> for ArgumentDefaultVisitor<'b>
@ -85,8 +95,8 @@ where
fn visit_expr(&mut self, expr: &'b Expr) {
match expr {
Expr::Call(ast::ExprCall { func, args, .. }) => {
if !is_mutable_func(self.checker, func)
&& !is_immutable_func(&self.checker.model, func, &self.extend_immutable_calls)
if !is_mutable_func(self.model, func)
&& !is_immutable_func(self.model, func, &self.extend_immutable_calls)
&& !is_nan_or_infinity(func, args)
{
self.diagnostics.push((
@ -139,11 +149,7 @@ pub(crate) fn function_call_argument_default(checker: &mut Checker, arguments: &
.map(|target| from_qualified_name(target))
.collect();
let diagnostics = {
let mut visitor = ArgumentDefaultVisitor {
checker,
diagnostics: vec![],
extend_immutable_calls,
};
let mut visitor = ArgumentDefaultVisitor::new(&checker.model, extend_immutable_calls);
for expr in arguments
.defaults
.iter()

View file

@ -3,6 +3,7 @@ use rustpython_parser::ast::{self, Arguments, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::analyze::typing::is_immutable_annotation;
use ruff_python_semantic::model::SemanticModel;
use crate::checkers::ast::Checker;
@ -25,18 +26,15 @@ const MUTABLE_FUNCS: &[&[&str]] = &[
&["collections", "deque"],
];
pub(crate) fn is_mutable_func(checker: &Checker, func: &Expr) -> bool {
checker
.model
.resolve_call_path(func)
.map_or(false, |call_path| {
MUTABLE_FUNCS
.iter()
.any(|target| call_path.as_slice() == *target)
})
pub(crate) fn is_mutable_func(model: &SemanticModel, func: &Expr) -> bool {
model.resolve_call_path(func).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(model: &SemanticModel, expr: &Expr) -> bool {
match expr {
Expr::List(_)
| Expr::Dict(_)
@ -44,7 +42,7 @@ fn is_mutable_expr(checker: &Checker, expr: &Expr) -> bool {
| Expr::ListComp(_)
| Expr::DictComp(_)
| Expr::SetComp(_) => true,
Expr::Call(ast::ExprCall { func, .. }) => is_mutable_func(checker, func),
Expr::Call(ast::ExprCall { func, .. }) => is_mutable_func(model, func),
_ => false,
}
}
@ -66,7 +64,7 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, arguments: &Argume
.zip(arguments.defaults.iter().rev()),
)
{
if is_mutable_expr(checker, default)
if is_mutable_expr(&checker.model, default)
&& !arg
.annotation
.as_ref()

View file

@ -2,6 +2,7 @@ use rustpython_parser::ast::{self, Expr, Keyword, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::model::SemanticModel;
use crate::checkers::ast::Checker;
@ -60,7 +61,7 @@ pub(crate) fn locals_in_render_function(
}
let locals = if args.len() >= 3 {
if !is_locals_call(checker, &args[2]) {
if !is_locals_call(&checker.model, &args[2]) {
return;
}
&args[2]
@ -68,7 +69,7 @@ pub(crate) fn locals_in_render_function(
.iter()
.find(|keyword| keyword.arg.as_ref().map_or(false, |arg| arg == "context"))
{
if !is_locals_call(checker, &keyword.value) {
if !is_locals_call(&checker.model, &keyword.value) {
return;
}
&keyword.value
@ -82,12 +83,11 @@ pub(crate) fn locals_in_render_function(
));
}
fn is_locals_call(checker: &Checker, expr: &Expr) -> bool {
fn is_locals_call(model: &SemanticModel, expr: &Expr) -> bool {
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
return false
};
checker
.model
model
.resolve_call_path(func)
.map_or(false, |call_path| call_path.as_slice() == ["", "locals"])
}

View file

@ -2,6 +2,7 @@ use rustpython_parser::ast::{self, Constant, Expr, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::model::SemanticModel;
use crate::checkers::ast::Checker;
@ -55,7 +56,7 @@ pub(crate) fn model_without_dunder_str(
body: &[Stmt],
class_location: &Stmt,
) -> Option<Diagnostic> {
if !checker_applies(checker, bases, body) {
if !checker_applies(&checker.model, bases, body) {
return None;
}
if !has_dunder_method(body) {
@ -79,12 +80,12 @@ fn has_dunder_method(body: &[Stmt]) -> bool {
})
}
fn checker_applies(checker: &Checker, bases: &[Expr], body: &[Stmt]) -> bool {
fn checker_applies(model: &SemanticModel, bases: &[Expr], body: &[Stmt]) -> bool {
for base in bases.iter() {
if is_model_abstract(body) {
continue;
}
if helpers::is_model(&checker.model, base) {
if helpers::is_model(model, base) {
return true;
}
}

View file

@ -4,6 +4,7 @@ use rustpython_parser::ast::{self, Expr, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::model::SemanticModel;
use crate::checkers::ast::Checker;
@ -99,11 +100,11 @@ impl fmt::Display for ContentType {
}
}
fn get_element_type(checker: &Checker, element: &Stmt) -> Option<ContentType> {
fn get_element_type(model: &SemanticModel, element: &Stmt) -> Option<ContentType> {
match element {
Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
if helpers::is_model_field(&checker.model, func) {
if helpers::is_model_field(model, func) {
return Some(ContentType::FieldDeclaration);
}
}
@ -150,7 +151,7 @@ pub(crate) fn unordered_body_content_in_model(
}
let mut elements_type_found = Vec::new();
for element in body.iter() {
let Some(current_element_type) = get_element_type(checker, element) else {
let Some(current_element_type) = get_element_type(&checker.model, element) else {
continue;
};
let Some(&element_type) = elements_type_found

View file

@ -151,7 +151,7 @@ pub(crate) fn logging_call(
args: &[Expr],
keywords: &[Keyword],
) {
if !logging::is_logger_candidate(&checker.model, func) {
if !logging::is_logger_candidate(func, &checker.model) {
return;
}

View file

@ -2,6 +2,7 @@ use rustpython_parser::ast::{self, Arguments, Constant, Expr, Operator, Ranged,
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::source_code::Locator;
use ruff_python_semantic::model::SemanticModel;
use ruff_python_semantic::scope::{ClassDef, ScopeKind};
@ -90,8 +91,9 @@ const ALLOWED_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[
fn is_valid_default_value_with_annotation(
default: &Expr,
checker: &Checker,
allow_container: bool,
locator: &Locator,
model: &SemanticModel,
) -> bool {
match default {
Expr::List(ast::ExprList { elts, .. })
@ -101,7 +103,7 @@ fn is_valid_default_value_with_annotation(
&& elts.len() <= 10
&& elts
.iter()
.all(|e| is_valid_default_value_with_annotation(e, checker, false));
.all(|e| is_valid_default_value_with_annotation(e, false, locator, model));
}
Expr::Dict(ast::ExprDict {
keys,
@ -112,8 +114,8 @@ fn is_valid_default_value_with_annotation(
&& keys.len() <= 10
&& keys.iter().zip(values).all(|(k, v)| {
k.as_ref().map_or(false, |k| {
is_valid_default_value_with_annotation(k, checker, false)
}) && is_valid_default_value_with_annotation(v, checker, false)
is_valid_default_value_with_annotation(k, false, locator, model)
}) && is_valid_default_value_with_annotation(v, false, locator, model)
});
}
Expr::Constant(ast::ExprConstant {
@ -125,17 +127,17 @@ fn is_valid_default_value_with_annotation(
Expr::Constant(ast::ExprConstant {
value: Constant::Str(..),
..
}) => return checker.locator.slice(default.range()).len() <= 50,
}) => return locator.slice(default.range()).len() <= 50,
Expr::Constant(ast::ExprConstant {
value: Constant::Bytes(..),
..
}) => return checker.locator.slice(default.range()).len() <= 50,
}) => return locator.slice(default.range()).len() <= 50,
// Ex) `123`, `True`, `False`, `3.14`
Expr::Constant(ast::ExprConstant {
value: Constant::Int(..) | Constant::Bool(..) | Constant::Float(..),
..
}) => {
return checker.locator.slice(default.range()).len() <= 10;
return locator.slice(default.range()).len() <= 10;
}
// Ex) `2j`
Expr::Constant(ast::ExprConstant {
@ -143,7 +145,7 @@ fn is_valid_default_value_with_annotation(
..
}) => {
if *real == 0.0 {
return checker.locator.slice(default.range()).len() <= 10;
return locator.slice(default.range()).len() <= 10;
}
}
Expr::UnaryOp(ast::ExprUnaryOp {
@ -157,7 +159,7 @@ fn is_valid_default_value_with_annotation(
..
}) = operand.as_ref()
{
return checker.locator.slice(operand.range()).len() <= 10;
return locator.slice(operand.range()).len() <= 10;
}
// Ex) `-2j`
if let Expr::Constant(ast::ExprConstant {
@ -166,21 +168,17 @@ fn is_valid_default_value_with_annotation(
}) = operand.as_ref()
{
if *real == 0.0 {
return checker.locator.slice(operand.range()).len() <= 10;
return locator.slice(operand.range()).len() <= 10;
}
}
// Ex) `-math.inf`, `-math.pi`, etc.
if let Expr::Attribute(_) = operand.as_ref() {
if checker
.model
.resolve_call_path(operand)
.map_or(false, |call_path| {
ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS.iter().any(|target| {
// reject `-math.nan`
call_path.as_slice() == *target && *target != ["math", "nan"]
})
if model.resolve_call_path(operand).map_or(false, |call_path| {
ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS.iter().any(|target| {
// reject `-math.nan`
call_path.as_slice() == *target && *target != ["math", "nan"]
})
{
}) {
return true;
}
}
@ -203,7 +201,7 @@ fn is_valid_default_value_with_annotation(
..
}) = left.as_ref()
{
return checker.locator.slice(left.range()).len() <= 10;
return locator.slice(left.range()).len() <= 10;
} else if let Expr::UnaryOp(ast::ExprUnaryOp {
op: Unaryop::USub,
operand,
@ -216,23 +214,19 @@ fn is_valid_default_value_with_annotation(
..
}) = operand.as_ref()
{
return checker.locator.slice(operand.range()).len() <= 10;
return locator.slice(operand.range()).len() <= 10;
}
}
}
}
// Ex) `math.inf`, `sys.stdin`, etc.
Expr::Attribute(_) => {
if checker
.model
.resolve_call_path(default)
.map_or(false, |call_path| {
ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS
.iter()
.chain(ALLOWED_ATTRIBUTES_IN_DEFAULTS.iter())
.any(|target| call_path.as_slice() == *target)
})
{
if model.resolve_call_path(default).map_or(false, |call_path| {
ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS
.iter()
.chain(ALLOWED_ATTRIBUTES_IN_DEFAULTS.iter())
.any(|target| call_path.as_slice() == *target)
}) {
return true;
}
}
@ -332,7 +326,12 @@ pub(crate) fn typed_argument_simple_defaults(checker: &mut Checker, args: &Argum
.and_then(|i| args.defaults.get(i))
{
if arg.annotation.is_some() {
if !is_valid_default_value_with_annotation(default, checker, true) {
if !is_valid_default_value_with_annotation(
default,
true,
checker.locator,
&checker.model,
) {
let mut diagnostic =
Diagnostic::new(TypedArgumentDefaultInStub, default.range());
@ -359,7 +358,12 @@ pub(crate) fn typed_argument_simple_defaults(checker: &mut Checker, args: &Argum
.and_then(|i| args.kw_defaults.get(i))
{
if kwarg.annotation.is_some() {
if !is_valid_default_value_with_annotation(default, checker, true) {
if !is_valid_default_value_with_annotation(
default,
true,
checker.locator,
&checker.model,
) {
let mut diagnostic =
Diagnostic::new(TypedArgumentDefaultInStub, default.range());
@ -389,7 +393,12 @@ pub(crate) fn argument_simple_defaults(checker: &mut Checker, args: &Arguments)
.and_then(|i| args.defaults.get(i))
{
if arg.annotation.is_none() {
if !is_valid_default_value_with_annotation(default, checker, true) {
if !is_valid_default_value_with_annotation(
default,
true,
checker.locator,
&checker.model,
) {
let mut diagnostic =
Diagnostic::new(ArgumentDefaultInStub, default.range());
@ -416,7 +425,12 @@ pub(crate) fn argument_simple_defaults(checker: &mut Checker, args: &Arguments)
.and_then(|i| args.kw_defaults.get(i))
{
if kwarg.annotation.is_none() {
if !is_valid_default_value_with_annotation(default, checker, true) {
if !is_valid_default_value_with_annotation(
default,
true,
checker.locator,
&checker.model,
) {
let mut diagnostic =
Diagnostic::new(ArgumentDefaultInStub, default.range());
@ -454,7 +468,7 @@ pub(crate) fn assignment_default_in_stub(checker: &mut Checker, targets: &[Expr]
if is_valid_default_value_without_annotation(value) {
return;
}
if is_valid_default_value_with_annotation(value, checker, true) {
if is_valid_default_value_with_annotation(value, true, checker.locator, &checker.model) {
return;
}
@ -485,7 +499,7 @@ pub(crate) fn annotated_assignment_default_in_stub(
if is_type_var_like_call(&checker.model, value) {
return;
}
if is_valid_default_value_with_annotation(value, checker, true) {
if is_valid_default_value_with_annotation(value, true, checker.locator, &checker.model) {
return;
}
@ -522,7 +536,7 @@ pub(crate) fn unannotated_assignment_in_stub(
if is_valid_default_value_without_annotation(value) {
return;
}
if !is_valid_default_value_with_annotation(value, checker, true) {
if !is_valid_default_value_with_annotation(value, true, checker.locator, &checker.model) {
return;
}

View file

@ -4,6 +4,7 @@ use rustpython_parser::{lexer, Mode, Tok};
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::source_code::{Generator, Locator};
use crate::checkers::ast::Checker;
use crate::registry::{AsRule, Rule};
@ -45,7 +46,7 @@ impl Violation for PytestParametrizeValuesWrongType {
}
}
fn elts_to_csv(elts: &[Expr], checker: &Checker) -> Option<String> {
fn elts_to_csv(elts: &[Expr], generator: Generator) -> Option<String> {
let all_literals = elts.iter().all(|e| {
matches!(
e,
@ -77,7 +78,7 @@ fn elts_to_csv(elts: &[Expr], checker: &Checker) -> Option<String> {
kind: None,
range: TextRange::default(),
});
Some(checker.generator().expr(&node))
Some(generator.expr(&node))
}
/// Returns the range of the `name` argument of `@pytest.mark.parametrize`.
@ -93,14 +94,14 @@ fn elts_to_csv(elts: &[Expr], checker: &Checker) -> Option<String> {
/// ```
///
/// This method assumes that the first argument is a string.
fn get_parametrize_name_range(checker: &Checker, decorator: &Expr, expr: &Expr) -> TextRange {
fn get_parametrize_name_range(decorator: &Expr, expr: &Expr, locator: &Locator) -> TextRange {
let mut locations = Vec::new();
let mut implicit_concat = None;
// The parenthesis are not part of the AST, so we need to tokenize the
// decorator to find them.
for (tok, range) in lexer::lex_starts_at(
checker.locator.slice(decorator.range()),
locator.slice(decorator.range()),
Mode::Module,
decorator.start(),
)
@ -139,7 +140,8 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) {
if names.len() > 1 {
match names_type {
types::ParametrizeNameType::Tuple => {
let name_range = get_parametrize_name_range(checker, decorator, expr);
let name_range =
get_parametrize_name_range(decorator, expr, checker.locator);
let mut diagnostic = Diagnostic::new(
PytestParametrizeNamesWrongType {
expected: names_type,
@ -170,7 +172,8 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) {
checker.diagnostics.push(diagnostic);
}
types::ParametrizeNameType::List => {
let name_range = get_parametrize_name_range(checker, decorator, expr);
let name_range =
get_parametrize_name_range(decorator, expr, checker.locator);
let mut diagnostic = Diagnostic::new(
PytestParametrizeNamesWrongType {
expected: names_type,
@ -241,7 +244,7 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) {
expr.range(),
);
if checker.patch(diagnostic.kind.rule()) {
if let Some(content) = elts_to_csv(elts, checker) {
if let Some(content) = elts_to_csv(elts, checker.generator()) {
#[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
content,
@ -291,7 +294,7 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) {
expr.range(),
);
if checker.patch(diagnostic.kind.rule()) {
if let Some(content) = elts_to_csv(elts, checker) {
if let Some(content) = elts_to_csv(elts, checker.generator()) {
#[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
content,

View file

@ -4,6 +4,7 @@ use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::format_call_path;
use ruff_python_ast::call_path::from_qualified_name;
use ruff_python_semantic::model::SemanticModel;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
@ -46,13 +47,10 @@ impl Violation for PytestRaisesWithoutException {
}
}
fn is_pytest_raises(checker: &Checker, func: &Expr) -> bool {
checker
.model
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["pytest", "raises"]
})
fn is_pytest_raises(func: &Expr, model: &SemanticModel) -> bool {
model.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 {
@ -66,7 +64,7 @@ const fn is_non_trivial_with_body(body: &[Stmt]) -> bool {
}
pub(crate) fn raises_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: &[Keyword]) {
if is_pytest_raises(checker, func) {
if is_pytest_raises(func, &checker.model) {
if checker
.settings
.rules
@ -106,7 +104,7 @@ pub(crate) fn complex_raises(
let mut is_too_complex = false;
let raises_called = items.iter().any(|item| match &item.context_expr {
Expr::Call(ast::ExprCall { func, .. }) => is_pytest_raises(checker, func),
Expr::Call(ast::ExprCall { func, .. }) => is_pytest_raises(func, &checker.model),
_ => false,
});

View file

@ -2,6 +2,7 @@ use rustpython_parser::ast::{self, Expr, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::model::SemanticModel;
use crate::checkers::ast::Checker;
@ -41,8 +42,8 @@ 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.model.expr_grandparent() else {
fn match_async_exit_stack(model: &SemanticModel) -> bool {
let Some(expr) = model.expr_grandparent() else {
return false;
};
let Expr::Await(ast::ExprAwait { value, range: _ }) = expr else {
@ -57,17 +58,13 @@ fn match_async_exit_stack(checker: &Checker) -> bool {
if attr != "enter_async_context" {
return false;
}
for parent in checker.model.parents() {
for parent in model.parents() {
if let Stmt::With(ast::StmtWith { items, .. }) = parent {
for item in items {
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
if checker
.model
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["contextlib", "AsyncExitStack"]
})
{
if model.resolve_call_path(func).map_or(false, |call_path| {
call_path.as_slice() == ["contextlib", "AsyncExitStack"]
}) {
return true;
}
}
@ -79,8 +76,8 @@ 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.model.expr_parent() else {
fn match_exit_stack(model: &SemanticModel) -> bool {
let Some(expr) = model.expr_parent() else {
return false;
};
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
@ -92,17 +89,13 @@ fn match_exit_stack(checker: &Checker) -> bool {
if attr != "enter_context" {
return false;
}
for parent in checker.model.parents() {
for parent in model.parents() {
if let Stmt::With(ast::StmtWith { items, .. }) = parent {
for item in items {
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
if checker
.model
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["contextlib", "ExitStack"]
})
{
if model.resolve_call_path(func).map_or(false, |call_path| {
call_path.as_slice() == ["contextlib", "ExitStack"]
}) {
return true;
}
}
@ -126,12 +119,12 @@ pub(crate) fn open_file_with_context_handler(checker: &mut Checker, func: &Expr)
}
// Ex) `with contextlib.ExitStack() as exit_stack: ...`
if match_exit_stack(checker) {
if match_exit_stack(&checker.model) {
return;
}
// Ex) `with contextlib.AsyncExitStack() as exit_stack: ...`
if match_async_exit_stack(checker) {
if match_async_exit_stack(&checker.model) {
return;
}

View file

@ -6,8 +6,7 @@ use ruff_python_ast::helpers::map_callable;
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::str::is_implicit_concatenation;
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
use crate::checkers::ast::Checker;
use ruff_python_semantic::model::SemanticModel;
/// Return the index of the first logical line in a string.
pub(crate) fn logical_line(content: &str) -> Option<usize> {
@ -37,7 +36,7 @@ pub(crate) fn normalize_word(first_word: &str) -> String {
/// Check decorator list to see if function should be ignored.
pub(crate) fn should_ignore_definition(
checker: &Checker,
model: &SemanticModel,
definition: &Definition,
ignore_decorators: &BTreeSet<String>,
) -> bool {
@ -52,7 +51,7 @@ pub(crate) fn should_ignore_definition(
}) = definition
{
for decorator in cast::decorator_list(stmt) {
if let Some(call_path) = checker.model.resolve_call_path(map_callable(decorator)) {
if let Some(call_path) = model.resolve_call_path(map_callable(decorator)) {
if ignore_decorators
.iter()
.any(|decorator| from_qualified_name(decorator) == call_path)

View file

@ -1,11 +1,12 @@
use ruff_python_semantic::analyze::function_type;
use ruff_python_semantic::analyze::function_type::FunctionType;
use ruff_python_semantic::model::SemanticModel;
use ruff_python_semantic::scope::{FunctionDef, ScopeKind};
use crate::checkers::ast::Checker;
use crate::settings::Settings;
pub(crate) fn in_dunder_init(checker: &Checker) -> bool {
let scope = checker.model.scope();
pub(crate) fn in_dunder_init(model: &SemanticModel, settings: &Settings) -> bool {
let scope = model.scope();
let ScopeKind::Function(FunctionDef {
name,
decorator_list,
@ -16,18 +17,18 @@ pub(crate) fn in_dunder_init(checker: &Checker) -> bool {
if name != "__init__" {
return false;
}
let Some(parent) = scope.parent.map(|scope_id| &checker.model.scopes[scope_id]) else {
let Some(parent) = scope.parent.map(|scope_id| &model.scopes[scope_id]) else {
return false;
};
if !matches!(
function_type::classify(
&checker.model,
model,
parent,
name,
decorator_list,
&checker.settings.pep8_naming.classmethod_decorators,
&checker.settings.pep8_naming.staticmethod_decorators,
&settings.pep8_naming.classmethod_decorators,
&settings.pep8_naming.staticmethod_decorators,
),
FunctionType::Method
) {

View file

@ -102,7 +102,7 @@ pub(crate) fn logging_call(
return;
}
if !logging::is_logger_candidate(&checker.model, func) {
if !logging::is_logger_candidate(func, &checker.model) {
return;
}

View file

@ -62,7 +62,7 @@ pub(crate) fn return_in_init(checker: &mut Checker, stmt: &Stmt) {
}
}
if in_dunder_init(checker) {
if in_dunder_init(&checker.model, checker.settings) {
checker
.diagnostics
.push(Diagnostic::new(ReturnInInit, stmt.range()));

View file

@ -39,7 +39,7 @@ impl Violation for YieldInInit {
/// PLE0100
pub(crate) fn yield_in_init(checker: &mut Checker, expr: &Expr) {
if in_dunder_init(checker) {
if in_dunder_init(&checker.model, checker.settings) {
checker
.diagnostics
.push(Diagnostic::new(YieldInInit, expr.range()));

View file

@ -6,6 +6,7 @@ use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Keyword, Ranged,
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::source_code::Generator;
use ruff_python_semantic::model::SemanticModel;
use ruff_python_stdlib::identifiers::is_identifier;
use crate::checkers::ast::Checker;
@ -34,7 +35,7 @@ impl Violation for ConvertNamedTupleFunctionalToClass {
/// Return the typename, args, keywords, and base class.
fn match_named_tuple_assign<'a>(
checker: &Checker,
model: &SemanticModel,
targets: &'a [Expr],
value: &'a Expr,
) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a Expr)> {
@ -50,13 +51,9 @@ fn match_named_tuple_assign<'a>(
}) = value else {
return None;
};
if !checker
.model
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["typing", "NamedTuple"]
})
{
if !model.resolve_call_path(func).map_or(false, |call_path| {
call_path.as_slice() == ["typing", "NamedTuple"]
}) {
return None;
}
Some((typename, args, keywords, func))
@ -187,7 +184,7 @@ pub(crate) fn convert_named_tuple_functional_to_class(
value: &Expr,
) {
let Some((typename, args, keywords, base_class)) =
match_named_tuple_assign(checker, targets, value) else
match_named_tuple_assign(&checker.model, targets, value) else
{
return;
};

View file

@ -6,6 +6,7 @@ use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Keyword, Ranged,
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::source_code::Generator;
use ruff_python_semantic::model::SemanticModel;
use ruff_python_stdlib::identifiers::is_identifier;
use crate::checkers::ast::Checker;
@ -35,7 +36,7 @@ impl Violation for ConvertTypedDictFunctionalToClass {
/// Return the class name, arguments, keywords and base class for a `TypedDict`
/// assignment.
fn match_typed_dict_assign<'a>(
checker: &Checker,
model: &SemanticModel,
targets: &'a [Expr],
value: &'a Expr,
) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a Expr)> {
@ -51,13 +52,9 @@ fn match_typed_dict_assign<'a>(
}) = value else {
return None;
};
if !checker
.model
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["typing", "TypedDict"]
})
{
if !model.resolve_call_path(func).map_or(false, |call_path| {
call_path.as_slice() == ["typing", "TypedDict"]
}) {
return None;
}
Some((class_name, args, keywords, func))
@ -243,7 +240,7 @@ pub(crate) fn convert_typed_dict_functional_to_class(
value: &Expr,
) {
let Some((class_name, args, keywords, base_class)) =
match_typed_dict_assign(checker, targets, value) else
match_typed_dict_assign(&checker.model, targets, value) else
{
return;
};

View file

@ -244,10 +244,9 @@ pub(crate) fn mutable_dataclass_default(checker: &mut Checker, body: &[Stmt]) {
}
}
pub(crate) fn is_dataclass(checker: &Checker, decorator_list: &[Expr]) -> bool {
pub(crate) fn is_dataclass(model: &SemanticModel, decorator_list: &[Expr]) -> bool {
decorator_list.iter().any(|decorator| {
checker
.model
model
.resolve_call_path(map_callable(decorator))
.map_or(false, |call_path| {
call_path.as_slice() == ["dataclasses", "dataclass"]

View file

@ -26,7 +26,7 @@ where
{
fn visit_expr(&mut self, expr: &'b Expr) {
if let Expr::Call(ast::ExprCall { func, .. }) = expr {
if logging::is_logger_candidate(self.context, func) {
if logging::is_logger_candidate(func, self.context) {
self.calls.push((expr, func));
}
}

View file

@ -16,7 +16,7 @@ use crate::model::SemanticModel;
/// # This is detected to be a logger candidate
/// bar.error()
/// ```
pub fn is_logger_candidate(model: &SemanticModel, func: &Expr) -> bool {
pub fn is_logger_candidate(func: &Expr, model: &SemanticModel) -> bool {
if let Expr::Attribute(ast::ExprAttribute { value, .. }) = func {
let Some(call_path) = (if let Some(call_path) = model.resolve_call_path(value) {
if call_path.first().map_or(false, |module| *module == "logging") || call_path.as_slice() == ["flask", "current_app", "logger"] {