mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-01 09:22:19 +00:00
Split CallPath
into QualifiedName
and UnqualifiedName
(#10210)
## Summary Charlie can probably explain this better than I but it turns out, `CallPath` is used for two different things: * To represent unqualified names like `version` where `version` can be a local variable or imported (e.g. `from sys import version` where the full qualified name is `sys.version`) * To represent resolved, full qualified names This PR splits `CallPath` into two types to make this destinction clear. > Note: I haven't renamed all `call_path` variables to `qualified_name` or `unqualified_name`. I can do that if that's welcomed but I first want to get feedback on the approach and naming overall. ## Test Plan `cargo test`
This commit is contained in:
parent
ba4328226d
commit
a6d892b1f4
181 changed files with 1692 additions and 1412 deletions
|
@ -418,11 +418,11 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
self.semantic.add_module(module);
|
self.semantic.add_module(module);
|
||||||
|
|
||||||
if alias.asname.is_none() && alias.name.contains('.') {
|
if alias.asname.is_none() && alias.name.contains('.') {
|
||||||
let call_path: Box<[&str]> = alias.name.split('.').collect();
|
let qualified_name: Box<[&str]> = alias.name.split('.').collect();
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
module,
|
module,
|
||||||
alias.identifier(),
|
alias.identifier(),
|
||||||
BindingKind::SubmoduleImport(SubmoduleImport { call_path }),
|
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }),
|
||||||
BindingFlags::EXTERNAL,
|
BindingFlags::EXTERNAL,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -439,11 +439,11 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = alias.asname.as_ref().unwrap_or(&alias.name);
|
let name = alias.asname.as_ref().unwrap_or(&alias.name);
|
||||||
let call_path: Box<[&str]> = alias.name.split('.').collect();
|
let qualified_name: Box<[&str]> = alias.name.split('.').collect();
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
name,
|
name,
|
||||||
alias.identifier(),
|
alias.identifier(),
|
||||||
BindingKind::Import(Import { call_path }),
|
BindingKind::Import(Import { qualified_name }),
|
||||||
flags,
|
flags,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -503,12 +503,12 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
// Attempt to resolve any relative imports; but if we don't know the current
|
// Attempt to resolve any relative imports; but if we don't know the current
|
||||||
// module path, or the relative import extends beyond the package root,
|
// module path, or the relative import extends beyond the package root,
|
||||||
// fallback to a literal representation (e.g., `[".", "foo"]`).
|
// fallback to a literal representation (e.g., `[".", "foo"]`).
|
||||||
let call_path = collect_import_from_member(level, module, &alias.name)
|
let qualified_name = collect_import_from_member(level, module, &alias.name)
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
name,
|
name,
|
||||||
alias.identifier(),
|
alias.identifier(),
|
||||||
BindingKind::FromImport(FromImport { call_path }),
|
BindingKind::FromImport(FromImport { qualified_name }),
|
||||||
flags,
|
flags,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -751,8 +751,8 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
}) => {
|
}) => {
|
||||||
let mut handled_exceptions = Exceptions::empty();
|
let mut handled_exceptions = Exceptions::empty();
|
||||||
for type_ in extract_handled_exceptions(handlers) {
|
for type_ in extract_handled_exceptions(handlers) {
|
||||||
if let Some(call_path) = self.semantic.resolve_call_path(type_) {
|
if let Some(qualified_name) = self.semantic.resolve_qualified_name(type_) {
|
||||||
match call_path.segments() {
|
match qualified_name.segments() {
|
||||||
["", "NameError"] => {
|
["", "NameError"] => {
|
||||||
handled_exceptions |= Exceptions::NAME_ERROR;
|
handled_exceptions |= Exceptions::NAME_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -1065,42 +1065,54 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
}) => {
|
}) => {
|
||||||
self.visit_expr(func);
|
self.visit_expr(func);
|
||||||
|
|
||||||
let callable = self.semantic.resolve_call_path(func).and_then(|call_path| {
|
let callable =
|
||||||
if self.semantic.match_typing_call_path(&call_path, "cast") {
|
self.semantic
|
||||||
Some(typing::Callable::Cast)
|
.resolve_qualified_name(func)
|
||||||
} else if self.semantic.match_typing_call_path(&call_path, "NewType") {
|
.and_then(|qualified_name| {
|
||||||
Some(typing::Callable::NewType)
|
if self
|
||||||
} else if self.semantic.match_typing_call_path(&call_path, "TypeVar") {
|
.semantic
|
||||||
Some(typing::Callable::TypeVar)
|
.match_typing_qualified_name(&qualified_name, "cast")
|
||||||
} else if self
|
{
|
||||||
.semantic
|
Some(typing::Callable::Cast)
|
||||||
.match_typing_call_path(&call_path, "NamedTuple")
|
} else if self
|
||||||
{
|
.semantic
|
||||||
Some(typing::Callable::NamedTuple)
|
.match_typing_qualified_name(&qualified_name, "NewType")
|
||||||
} else if self
|
{
|
||||||
.semantic
|
Some(typing::Callable::NewType)
|
||||||
.match_typing_call_path(&call_path, "TypedDict")
|
} else if self
|
||||||
{
|
.semantic
|
||||||
Some(typing::Callable::TypedDict)
|
.match_typing_qualified_name(&qualified_name, "TypeVar")
|
||||||
} else if matches!(
|
{
|
||||||
call_path.segments(),
|
Some(typing::Callable::TypeVar)
|
||||||
[
|
} else if self
|
||||||
"mypy_extensions",
|
.semantic
|
||||||
"Arg"
|
.match_typing_qualified_name(&qualified_name, "NamedTuple")
|
||||||
| "DefaultArg"
|
{
|
||||||
| "NamedArg"
|
Some(typing::Callable::NamedTuple)
|
||||||
| "DefaultNamedArg"
|
} else if self
|
||||||
| "VarArg"
|
.semantic
|
||||||
| "KwArg"
|
.match_typing_qualified_name(&qualified_name, "TypedDict")
|
||||||
]
|
{
|
||||||
) {
|
Some(typing::Callable::TypedDict)
|
||||||
Some(typing::Callable::MypyExtension)
|
} else if matches!(
|
||||||
} else if matches!(call_path.segments(), ["", "bool"]) {
|
qualified_name.segments(),
|
||||||
Some(typing::Callable::Bool)
|
[
|
||||||
} else {
|
"mypy_extensions",
|
||||||
None
|
"Arg"
|
||||||
}
|
| "DefaultArg"
|
||||||
});
|
| "NamedArg"
|
||||||
|
| "DefaultNamedArg"
|
||||||
|
| "VarArg"
|
||||||
|
| "KwArg"
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
Some(typing::Callable::MypyExtension)
|
||||||
|
} else if matches!(qualified_name.segments(), ["", "bool"]) {
|
||||||
|
Some(typing::Callable::Bool)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
match callable {
|
match callable {
|
||||||
Some(typing::Callable::Bool) => {
|
Some(typing::Callable::Bool) => {
|
||||||
let mut args = arguments.args.iter();
|
let mut args = arguments.args.iter();
|
||||||
|
|
|
@ -1,44 +1,7 @@
|
||||||
use libcst_native::{
|
use libcst_native::{
|
||||||
Expression, Name, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace, UnaryOperation,
|
Expression, Name, ParenthesizableWhitespace, SimpleWhitespace, UnaryOperation,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
|
|
||||||
match expr {
|
|
||||||
Expression::Call(expr) => {
|
|
||||||
compose_call_path_inner(&expr.func, parts);
|
|
||||||
}
|
|
||||||
Expression::Attribute(expr) => {
|
|
||||||
compose_call_path_inner(&expr.value, parts);
|
|
||||||
parts.push(expr.attr.value);
|
|
||||||
}
|
|
||||||
Expression::Name(expr) => {
|
|
||||||
parts.push(expr.value);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn compose_call_path(expr: &Expression) -> Option<String> {
|
|
||||||
let mut segments = vec![];
|
|
||||||
compose_call_path_inner(expr, &mut segments);
|
|
||||||
if segments.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(segments.join("."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn compose_module_path(module: &NameOrAttribute) -> String {
|
|
||||||
match module {
|
|
||||||
NameOrAttribute::N(name) => name.value.to_string(),
|
|
||||||
NameOrAttribute::A(attr) => {
|
|
||||||
let name = attr.attr.value;
|
|
||||||
let prefix = compose_call_path(&attr.value);
|
|
||||||
prefix.map_or_else(|| name.to_string(), |prefix| format!("{prefix}.{name}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a [`ParenthesizableWhitespace`] containing a single space.
|
/// Return a [`ParenthesizableWhitespace`] containing a single space.
|
||||||
pub(crate) fn space() -> ParenthesizableWhitespace<'static> {
|
pub(crate) fn space() -> ParenthesizableWhitespace<'static> {
|
||||||
ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" "))
|
ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" "))
|
||||||
|
|
|
@ -2,14 +2,16 @@
|
||||||
//! and return the modified code snippet as output.
|
//! and return the modified code snippet as output.
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use libcst_native::{
|
use libcst_native::{
|
||||||
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
|
Codegen, CodegenState, Expression, ImportNames, NameOrAttribute, ParenthesizableWhitespace,
|
||||||
|
SmallStatement, Statement,
|
||||||
};
|
};
|
||||||
|
use ruff_python_ast::name::UnqualifiedName;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use ruff_python_ast::Stmt;
|
use ruff_python_ast::Stmt;
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
|
|
||||||
use crate::cst::helpers::compose_module_path;
|
|
||||||
use crate::cst::matchers::match_statement;
|
use crate::cst::matchers::match_statement;
|
||||||
|
|
||||||
/// Glue code to make libcst codegen work with ruff's Stylist
|
/// Glue code to make libcst codegen work with ruff's Stylist
|
||||||
|
@ -78,7 +80,7 @@ pub(crate) fn remove_imports<'a>(
|
||||||
for member in member_names {
|
for member in member_names {
|
||||||
let alias_index = aliases
|
let alias_index = aliases
|
||||||
.iter()
|
.iter()
|
||||||
.position(|alias| member == compose_module_path(&alias.name));
|
.position(|alias| member == qualified_name_from_name_or_attribute(&alias.name));
|
||||||
if let Some(index) = alias_index {
|
if let Some(index) = alias_index {
|
||||||
aliases.remove(index);
|
aliases.remove(index);
|
||||||
}
|
}
|
||||||
|
@ -142,7 +144,7 @@ pub(crate) fn retain_imports(
|
||||||
aliases.retain(|alias| {
|
aliases.retain(|alias| {
|
||||||
member_names
|
member_names
|
||||||
.iter()
|
.iter()
|
||||||
.any(|member| *member == compose_module_path(&alias.name))
|
.any(|member| *member == qualified_name_from_name_or_attribute(&alias.name))
|
||||||
});
|
});
|
||||||
|
|
||||||
// But avoid destroying any trailing comments.
|
// But avoid destroying any trailing comments.
|
||||||
|
@ -164,3 +166,40 @@ pub(crate) fn retain_imports(
|
||||||
|
|
||||||
Ok(tree.codegen_stylist(stylist))
|
Ok(tree.codegen_stylist(stylist))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn collect_segments<'a>(expr: &'a Expression, parts: &mut SmallVec<[&'a str; 8]>) {
|
||||||
|
match expr {
|
||||||
|
Expression::Call(expr) => {
|
||||||
|
collect_segments(&expr.func, parts);
|
||||||
|
}
|
||||||
|
Expression::Attribute(expr) => {
|
||||||
|
collect_segments(&expr.value, parts);
|
||||||
|
parts.push(expr.attr.value);
|
||||||
|
}
|
||||||
|
Expression::Name(expr) => {
|
||||||
|
parts.push(expr.value);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unqualified_name_from_expression<'a>(expr: &'a Expression<'a>) -> Option<UnqualifiedName<'a>> {
|
||||||
|
let mut segments = smallvec![];
|
||||||
|
collect_segments(expr, &mut segments);
|
||||||
|
if segments.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(segments.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qualified_name_from_name_or_attribute(module: &NameOrAttribute) -> String {
|
||||||
|
match module {
|
||||||
|
NameOrAttribute::N(name) => name.value.to_string(),
|
||||||
|
NameOrAttribute::A(attr) => {
|
||||||
|
let name = attr.attr.value;
|
||||||
|
let prefix = unqualified_name_from_expression(&attr.value);
|
||||||
|
prefix.map_or_else(|| name.to_string(), |prefix| format!("{prefix}.{name}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -232,7 +232,7 @@ impl Renamer {
|
||||||
}
|
}
|
||||||
BindingKind::SubmoduleImport(import) => {
|
BindingKind::SubmoduleImport(import) => {
|
||||||
// Ex) Rename `import pandas.core` to `import pandas as pd`.
|
// Ex) Rename `import pandas.core` to `import pandas as pd`.
|
||||||
let module_name = import.call_path.first().unwrap();
|
let module_name = import.qualified_name.first().unwrap();
|
||||||
Some(Edit::range_replacement(
|
Some(Edit::range_replacement(
|
||||||
format!("{module_name} as {target}"),
|
format!("{module_name} as {target}"),
|
||||||
binding.range(),
|
binding.range(),
|
||||||
|
|
|
@ -68,8 +68,8 @@ pub(crate) fn variable_name_task_id(
|
||||||
// If the function doesn't come from Airflow, we can't do anything.
|
// If the function doesn't come from Airflow, we can't do anything.
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments()[0], "airflow"))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments()[0], "airflow"))
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@ use ruff_python_semantic::SemanticModel;
|
||||||
|
|
||||||
pub(super) fn is_sys(expr: &Expr, target: &str, semantic: &SemanticModel) -> bool {
|
pub(super) fn is_sys(expr: &Expr, target: &str, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(expr)
|
.resolve_qualified_name(expr)
|
||||||
.is_some_and(|call_path| call_path.segments() == ["sys", target])
|
.is_some_and(|qualified_name| qualified_name.segments() == ["sys", target])
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,8 @@ pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(expr)
|
.resolve_qualified_name(expr)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["six", "PY3"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["six", "PY3"]))
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
|
|
@ -2,7 +2,7 @@ use ruff_python_ast::ExprCall;
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::name::QualifiedName;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -41,9 +41,9 @@ impl Violation for BlockingHttpCallInAsyncFunction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_blocking_http_call(call_path: &CallPath) -> bool {
|
fn is_blocking_http_call(qualified_name: &QualifiedName) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["urllib", "request", "urlopen"]
|
["urllib", "request", "urlopen"]
|
||||||
| [
|
| [
|
||||||
"httpx" | "requests",
|
"httpx" | "requests",
|
||||||
|
@ -65,7 +65,7 @@ pub(crate) fn blocking_http_call(checker: &mut Checker, call: &ExprCall) {
|
||||||
if checker.semantic().in_async_context() {
|
if checker.semantic().in_async_context() {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(call.func.as_ref())
|
.resolve_qualified_name(call.func.as_ref())
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(is_blocking_http_call)
|
.is_some_and(is_blocking_http_call)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@ use ruff_python_ast::ExprCall;
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::name::QualifiedName;
|
||||||
use ruff_python_semantic::Modules;
|
use ruff_python_semantic::Modules;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) {
|
||||||
if checker.semantic().in_async_context() {
|
if checker.semantic().in_async_context() {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(call.func.as_ref())
|
.resolve_qualified_name(call.func.as_ref())
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(is_unsafe_os_method)
|
.is_some_and(is_unsafe_os_method)
|
||||||
{
|
{
|
||||||
|
@ -60,9 +60,9 @@ pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_unsafe_os_method(call_path: &CallPath) -> bool {
|
fn is_unsafe_os_method(qualified_name: &QualifiedName) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
[
|
[
|
||||||
"os",
|
"os",
|
||||||
"popen"
|
"popen"
|
||||||
|
|
|
@ -58,24 +58,26 @@ pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ast::E
|
||||||
/// Returns `true` if the expression resolves to a blocking call, like `time.sleep` or
|
/// Returns `true` if the expression resolves to a blocking call, like `time.sleep` or
|
||||||
/// `subprocess.run`.
|
/// `subprocess.run`.
|
||||||
fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic.resolve_call_path(func).is_some_and(|call_path| {
|
semantic
|
||||||
matches!(
|
.resolve_qualified_name(func)
|
||||||
call_path.segments(),
|
.is_some_and(|qualified_name| {
|
||||||
["", "open"]
|
matches!(
|
||||||
| ["time", "sleep"]
|
qualified_name.segments(),
|
||||||
| [
|
["", "open"]
|
||||||
"subprocess",
|
| ["time", "sleep"]
|
||||||
"run"
|
| [
|
||||||
| "Popen"
|
"subprocess",
|
||||||
| "call"
|
"run"
|
||||||
| "check_call"
|
| "Popen"
|
||||||
| "check_output"
|
| "call"
|
||||||
| "getoutput"
|
| "check_call"
|
||||||
| "getstatusoutput"
|
| "check_output"
|
||||||
]
|
| "getoutput"
|
||||||
| ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"]
|
| "getstatusoutput"
|
||||||
)
|
]
|
||||||
})
|
| ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"]
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`.
|
/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`.
|
||||||
|
@ -94,10 +96,10 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
// Path("foo").open()
|
// Path("foo").open()
|
||||||
// ```
|
// ```
|
||||||
if let Expr::Call(call) = value.as_ref() {
|
if let Expr::Call(call) = value.as_ref() {
|
||||||
let Some(call_path) = semantic.resolve_call_path(call.func.as_ref()) else {
|
let Some(qualified_name) = semantic.resolve_qualified_name(call.func.as_ref()) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if call_path.segments() == ["pathlib", "Path"] {
|
if qualified_name.segments() == ["pathlib", "Path"] {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,6 +125,6 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
};
|
};
|
||||||
|
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(call.func.as_ref())
|
.resolve_qualified_name(call.func.as_ref())
|
||||||
.is_some_and(|call_path| call_path.segments() == ["pathlib", "Path"])
|
.is_some_and(|qualified_name| qualified_name.segments() == ["pathlib", "Path"])
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,24 @@ pub(super) fn is_untyped_exception(type_: Option<&Expr>, semantic: &SemanticMode
|
||||||
type_.map_or(true, |type_| {
|
type_.map_or(true, |type_| {
|
||||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_ {
|
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_ {
|
||||||
elts.iter().any(|type_| {
|
elts.iter().any(|type_| {
|
||||||
semantic.resolve_call_path(type_).is_some_and(|call_path| {
|
semantic
|
||||||
matches!(call_path.segments(), ["", "Exception" | "BaseException"])
|
.resolve_qualified_name(type_)
|
||||||
})
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["", "Exception" | "BaseException"]
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
semantic.resolve_call_path(type_).is_some_and(|call_path| {
|
semantic
|
||||||
matches!(call_path.segments(), ["", "Exception" | "BaseException"])
|
.resolve_qualified_name(type_)
|
||||||
})
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["", "Exception" | "BaseException"]
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::name::QualifiedName;
|
||||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||||
use ruff_python_semantic::{Modules, SemanticModel};
|
use ruff_python_semantic::{Modules, SemanticModel};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
@ -66,8 +66,8 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["os", "chmod"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["os", "chmod"]))
|
||||||
{
|
{
|
||||||
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
|
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
|
||||||
match parse_mask(mode_arg, checker.semantic()) {
|
match parse_mask(mode_arg, checker.semantic()) {
|
||||||
|
@ -101,8 +101,8 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
|
||||||
const WRITE_WORLD: u16 = 0o2;
|
const WRITE_WORLD: u16 = 0o2;
|
||||||
const EXECUTE_GROUP: u16 = 0o10;
|
const EXECUTE_GROUP: u16 = 0o10;
|
||||||
|
|
||||||
fn py_stat(call_path: &CallPath) -> Option<u16> {
|
fn py_stat(qualified_name: &QualifiedName) -> Option<u16> {
|
||||||
match call_path.segments() {
|
match qualified_name.segments() {
|
||||||
["stat", "ST_MODE"] => Some(0o0),
|
["stat", "ST_MODE"] => Some(0o0),
|
||||||
["stat", "S_IFDOOR"] => Some(0o0),
|
["stat", "S_IFDOOR"] => Some(0o0),
|
||||||
["stat", "S_IFPORT"] => Some(0o0),
|
["stat", "S_IFPORT"] => Some(0o0),
|
||||||
|
@ -155,7 +155,10 @@ fn parse_mask(expr: &Expr, semantic: &SemanticModel) -> Result<Option<u16>> {
|
||||||
Some(value) => Ok(Some(value)),
|
Some(value) => Ok(Some(value)),
|
||||||
None => anyhow::bail!("int value out of range"),
|
None => anyhow::bail!("int value out of range"),
|
||||||
},
|
},
|
||||||
Expr::Attribute(_) => Ok(semantic.resolve_call_path(expr).as_ref().and_then(py_stat)),
|
Expr::Attribute(_) => Ok(semantic
|
||||||
|
.resolve_qualified_name(expr)
|
||||||
|
.as_ref()
|
||||||
|
.and_then(py_stat)),
|
||||||
Expr::BinOp(ast::ExprBinOp {
|
Expr::BinOp(ast::ExprBinOp {
|
||||||
left,
|
left,
|
||||||
op,
|
op,
|
||||||
|
|
|
@ -42,10 +42,10 @@ pub(crate) fn django_raw_sql(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["django", "db", "models", "expressions", "RawSQL"]
|
["django", "db", "models", "expressions", "RawSQL"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -35,8 +35,8 @@ impl Violation for ExecBuiltin {
|
||||||
pub(crate) fn exec_used(checker: &mut Checker, func: &Expr) {
|
pub(crate) fn exec_used(checker: &mut Checker, func: &Expr) {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["" | "builtin", "exec"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["" | "builtin", "exec"]))
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
|
|
@ -65,7 +65,7 @@ pub(crate) fn flask_debug_true(checker: &mut Checker, call: &ExprCall) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if typing::resolve_assignment(value, checker.semantic())
|
if typing::resolve_assignment(value, checker.semantic())
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["flask", "Flask"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["flask", "Flask"]))
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
|
|
@ -74,8 +74,8 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike)
|
||||||
{
|
{
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["tempfile", ..]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["tempfile", ..]))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,18 +61,17 @@ impl Violation for HashlibInsecureHashFunction {
|
||||||
|
|
||||||
/// S324
|
/// S324
|
||||||
pub(crate) fn hashlib_insecure_hash_functions(checker: &mut Checker, call: &ast::ExprCall) {
|
pub(crate) fn hashlib_insecure_hash_functions(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
if let Some(hashlib_call) =
|
if let Some(hashlib_call) = checker
|
||||||
checker
|
.semantic()
|
||||||
.semantic()
|
.resolve_qualified_name(&call.func)
|
||||||
.resolve_call_path(&call.func)
|
.and_then(|qualified_name| match qualified_name.segments() {
|
||||||
.and_then(|call_path| match call_path.segments() {
|
["hashlib", "new"] => Some(HashlibCall::New),
|
||||||
["hashlib", "new"] => Some(HashlibCall::New),
|
["hashlib", "md4"] => Some(HashlibCall::WeakHash("md4")),
|
||||||
["hashlib", "md4"] => Some(HashlibCall::WeakHash("md4")),
|
["hashlib", "md5"] => Some(HashlibCall::WeakHash("md5")),
|
||||||
["hashlib", "md5"] => Some(HashlibCall::WeakHash("md5")),
|
["hashlib", "sha"] => Some(HashlibCall::WeakHash("sha")),
|
||||||
["hashlib", "sha"] => Some(HashlibCall::WeakHash("sha")),
|
["hashlib", "sha1"] => Some(HashlibCall::WeakHash("sha1")),
|
||||||
["hashlib", "sha1"] => Some(HashlibCall::WeakHash("sha1")),
|
_ => None,
|
||||||
_ => None,
|
})
|
||||||
})
|
|
||||||
{
|
{
|
||||||
if !is_used_for_security(&call.arguments) {
|
if !is_used_for_security(&call.arguments) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -61,8 +61,10 @@ impl Violation for Jinja2AutoescapeFalse {
|
||||||
pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCall) {
|
pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["jinja2", "Environment"]))
|
.is_some_and(|qualifieed_name| {
|
||||||
|
matches!(qualifieed_name.segments(), ["jinja2", "Environment"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
if let Some(keyword) = call.arguments.find_keyword("autoescape") {
|
if let Some(keyword) = call.arguments.find_keyword("autoescape") {
|
||||||
match &keyword.value {
|
match &keyword.value {
|
||||||
|
|
|
@ -42,8 +42,10 @@ pub(crate) fn logging_config_insecure_listen(checker: &mut Checker, call: &ast::
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["logging", "config", "listen"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["logging", "config", "listen"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
if call.arguments.find_keyword("verify").is_some() {
|
if call.arguments.find_keyword("verify").is_some() {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -47,8 +47,10 @@ impl Violation for MakoTemplates {
|
||||||
pub(crate) fn mako_templates(checker: &mut Checker, call: &ast::ExprCall) {
|
pub(crate) fn mako_templates(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["mako", "template", "Template"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["mako", "template", "Template"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
|
|
@ -39,8 +39,10 @@ impl Violation for ParamikoCall {
|
||||||
pub(crate) fn paramiko_call(checker: &mut Checker, func: &Expr) {
|
pub(crate) fn paramiko_call(checker: &mut Checker, func: &Expr) {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["paramiko", "exec_command"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["paramiko", "exec_command"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
|
|
@ -49,8 +49,8 @@ impl Violation for RequestWithNoCertValidation {
|
||||||
pub(crate) fn request_with_no_cert_validation(checker: &mut Checker, call: &ast::ExprCall) {
|
pub(crate) fn request_with_no_cert_validation(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
if let Some(target) = checker
|
if let Some(target) = checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.and_then(|call_path| match call_path.segments() {
|
.and_then(|qualified_name| match qualified_name.segments() {
|
||||||
["requests", "get" | "options" | "head" | "post" | "put" | "patch" | "delete"] => {
|
["requests", "get" | "options" | "head" | "post" | "put" | "patch" | "delete"] => {
|
||||||
Some("requests")
|
Some("requests")
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,10 +52,10 @@ impl Violation for RequestWithoutTimeout {
|
||||||
pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCall) {
|
pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
[
|
[
|
||||||
"requests",
|
"requests",
|
||||||
"get" | "options" | "head" | "post" | "put" | "patch" | "delete"
|
"get" | "options" | "head" | "post" | "put" | "patch" | "delete"
|
||||||
|
|
|
@ -419,8 +419,8 @@ enum CallKind {
|
||||||
/// Return the [`CallKind`] of the given function call.
|
/// Return the [`CallKind`] of the given function call.
|
||||||
fn get_call_kind(func: &Expr, semantic: &SemanticModel) -> Option<CallKind> {
|
fn get_call_kind(func: &Expr, semantic: &SemanticModel) -> Option<CallKind> {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.and_then(|call_path| match call_path.segments() {
|
.and_then(|qualified_name| match qualified_name.segments() {
|
||||||
&[module, submodule] => match module {
|
&[module, submodule] => match module {
|
||||||
"os" => match submodule {
|
"os" => match submodule {
|
||||||
"execl" | "execle" | "execlp" | "execlpe" | "execv" | "execve" | "execvp"
|
"execl" | "execle" | "execlp" | "execlpe" | "execv" | "execve" | "execvp"
|
||||||
|
|
|
@ -44,9 +44,12 @@ impl Violation for SnmpInsecureVersion {
|
||||||
pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall) {
|
pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(call_path.segments(), ["pysnmp", "hlapi", "CommunityData"])
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["pysnmp", "hlapi", "CommunityData"]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
if let Some(keyword) = call.arguments.find_keyword("mpModel") {
|
if let Some(keyword) = call.arguments.find_keyword("mpModel") {
|
||||||
|
|
|
@ -45,9 +45,12 @@ pub(crate) fn snmp_weak_cryptography(checker: &mut Checker, call: &ast::ExprCall
|
||||||
if call.arguments.len() < 3 {
|
if call.arguments.len() < 3 {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(call_path.segments(), ["pysnmp", "hlapi", "UsmUserData"])
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["pysnmp", "hlapi", "UsmUserData"]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
|
|
|
@ -60,10 +60,10 @@ pub(crate) fn ssh_no_host_key_verification(checker: &mut Checker, call: &ExprCal
|
||||||
// Detect either, e.g., `paramiko.client.AutoAddPolicy` or `paramiko.client.AutoAddPolicy()`.
|
// Detect either, e.g., `paramiko.client.AutoAddPolicy` or `paramiko.client.AutoAddPolicy()`.
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(map_callable(policy_argument))
|
.resolve_qualified_name(map_callable(policy_argument))
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["paramiko", "client", "AutoAddPolicy" | "WarningPolicy"]
|
["paramiko", "client", "AutoAddPolicy" | "WarningPolicy"]
|
||||||
| ["paramiko", "AutoAddPolicy" | "WarningPolicy"]
|
| ["paramiko", "AutoAddPolicy" | "WarningPolicy"]
|
||||||
)
|
)
|
||||||
|
@ -72,9 +72,9 @@ pub(crate) fn ssh_no_host_key_verification(checker: &mut Checker, call: &ExprCal
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if typing::resolve_assignment(value, checker.semantic()).is_some_and(|call_path| {
|
if typing::resolve_assignment(value, checker.semantic()).is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["paramiko", "client", "SSHClient"] | ["paramiko", "SSHClient"]
|
["paramiko", "client", "SSHClient"] | ["paramiko", "SSHClient"]
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -51,8 +51,8 @@ impl Violation for SslInsecureVersion {
|
||||||
pub(crate) fn ssl_insecure_version(checker: &mut Checker, call: &ExprCall) {
|
pub(crate) fn ssl_insecure_version(checker: &mut Checker, call: &ExprCall) {
|
||||||
let Some(keyword) = checker
|
let Some(keyword) = checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(call.func.as_ref())
|
.resolve_qualified_name(call.func.as_ref())
|
||||||
.and_then(|call_path| match call_path.segments() {
|
.and_then(|qualified_name| match qualified_name.segments() {
|
||||||
["ssl", "wrap_socket"] => Some("ssl_version"),
|
["ssl", "wrap_socket"] => Some("ssl_version"),
|
||||||
["OpenSSL", "SSL", "Context"] => Some("method"),
|
["OpenSSL", "SSL", "Context"] => Some("method"),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
|
@ -39,8 +39,8 @@ impl Violation for SslWithNoVersion {
|
||||||
pub(crate) fn ssl_with_no_version(checker: &mut Checker, call: &ExprCall) {
|
pub(crate) fn ssl_with_no_version(checker: &mut Checker, call: &ExprCall) {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(call.func.as_ref())
|
.resolve_qualified_name(call.func.as_ref())
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["ssl", "wrap_socket"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["ssl", "wrap_socket"]))
|
||||||
{
|
{
|
||||||
if call.arguments.find_keyword("ssl_version").is_none() {
|
if call.arguments.find_keyword("ssl_version").is_none() {
|
||||||
checker
|
checker
|
||||||
|
|
|
@ -825,8 +825,8 @@ impl Violation for SuspiciousFTPLibUsage {
|
||||||
|
|
||||||
/// S301, S302, S303, S304, S305, S306, S307, S308, S310, S311, S312, S313, S314, S315, S316, S317, S318, S319, S320, S321, S323
|
/// S301, S302, S303, S304, S305, S306, S307, S308, S310, S311, S312, S313, S314, S315, S316, S317, S318, S319, S320, S321, S323
|
||||||
pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
|
pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
|
||||||
let Some(diagnostic_kind) = checker.semantic().resolve_call_path(call.func.as_ref()).and_then(|call_path| {
|
let Some(diagnostic_kind) = checker.semantic().resolve_qualified_name(call.func.as_ref()).and_then(|qualified_name| {
|
||||||
match call_path.segments() {
|
match qualified_name.segments() {
|
||||||
// Pickle
|
// Pickle
|
||||||
["pickle" | "dill", "load" | "loads" | "Unpickler"] |
|
["pickle" | "dill", "load" | "loads" | "Unpickler"] |
|
||||||
["shelve", "open" | "DbfilenameShelf"] |
|
["shelve", "open" | "DbfilenameShelf"] |
|
||||||
|
@ -906,9 +906,9 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
|
||||||
pub(crate) fn suspicious_function_decorator(checker: &mut Checker, decorator: &Decorator) {
|
pub(crate) fn suspicious_function_decorator(checker: &mut Checker, decorator: &Decorator) {
|
||||||
let Some(diagnostic_kind) = checker
|
let Some(diagnostic_kind) = checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&decorator.expression)
|
.resolve_qualified_name(&decorator.expression)
|
||||||
.and_then(|call_path| {
|
.and_then(|qualified_name| {
|
||||||
match call_path.segments() {
|
match qualified_name.segments() {
|
||||||
// MarkSafe
|
// MarkSafe
|
||||||
["django", "utils", "safestring" | "html", "mark_safe"] => {
|
["django", "utils", "safestring" | "html", "mark_safe"] => {
|
||||||
Some(SuspiciousMarkSafeUsage.into())
|
Some(SuspiciousMarkSafeUsage.into())
|
||||||
|
|
|
@ -62,16 +62,16 @@ impl Violation for UnsafeYAMLLoad {
|
||||||
pub(crate) fn unsafe_yaml_load(checker: &mut Checker, call: &ast::ExprCall) {
|
pub(crate) fn unsafe_yaml_load(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["yaml", "load"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["yaml", "load"]))
|
||||||
{
|
{
|
||||||
if let Some(loader_arg) = call.arguments.find_argument("Loader", 1) {
|
if let Some(loader_arg) = call.arguments.find_argument("Loader", 1) {
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(loader_arg)
|
.resolve_qualified_name(loader_arg)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["yaml", "SafeLoader" | "CSafeLoader"]
|
["yaml", "SafeLoader" | "CSafeLoader"]
|
||||||
| ["yaml", "loader", "SafeLoader" | "CSafeLoader"]
|
| ["yaml", "loader", "SafeLoader" | "CSafeLoader"]
|
||||||
)
|
)
|
||||||
|
|
|
@ -101,8 +101,8 @@ fn extract_cryptographic_key(
|
||||||
checker: &mut Checker,
|
checker: &mut Checker,
|
||||||
call: &ExprCall,
|
call: &ExprCall,
|
||||||
) -> Option<(CryptographicKey, TextRange)> {
|
) -> Option<(CryptographicKey, TextRange)> {
|
||||||
let call_path = checker.semantic().resolve_call_path(&call.func)?;
|
let qualified_name = checker.semantic().resolve_qualified_name(&call.func)?;
|
||||||
match call_path.segments() {
|
match qualified_name.segments() {
|
||||||
["cryptography", "hazmat", "primitives", "asymmetric", function, "generate_private_key"] => {
|
["cryptography", "hazmat", "primitives", "asymmetric", function, "generate_private_key"] => {
|
||||||
match *function {
|
match *function {
|
||||||
"dsa" => {
|
"dsa" => {
|
||||||
|
@ -116,9 +116,9 @@ fn extract_cryptographic_key(
|
||||||
"ec" => {
|
"ec" => {
|
||||||
let argument = call.arguments.find_argument("curve", 0)?;
|
let argument = call.arguments.find_argument("curve", 0)?;
|
||||||
let ExprAttribute { attr, value, .. } = argument.as_attribute_expr()?;
|
let ExprAttribute { attr, value, .. } = argument.as_attribute_expr()?;
|
||||||
let call_path = checker.semantic().resolve_call_path(value)?;
|
let qualified_name = checker.semantic().resolve_qualified_name(value)?;
|
||||||
if matches!(
|
if matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["cryptography", "hazmat", "primitives", "asymmetric", "ec"]
|
["cryptography", "hazmat", "primitives", "asymmetric", "ec"]
|
||||||
) {
|
) {
|
||||||
Some((
|
Some((
|
||||||
|
|
|
@ -140,8 +140,8 @@ pub(crate) fn blind_except(
|
||||||
Expr::Name(ast::ExprName { .. }) => {
|
Expr::Name(ast::ExprName { .. }) => {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func.as_ref())
|
.resolve_qualified_name(func.as_ref())
|
||||||
.is_some_and(|call_path| match call_path.segments() {
|
.is_some_and(|qualified_name| match qualified_name.segments() {
|
||||||
["logging", "exception"] => true,
|
["logging", "exception"] => true,
|
||||||
["logging", "error"] => {
|
["logging", "error"] => {
|
||||||
if let Some(keyword) = arguments.find_keyword("exc_info") {
|
if let Some(keyword) = arguments.find_keyword("exc_info") {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::name::UnqualifiedName;
|
||||||
use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters};
|
use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters};
|
||||||
use ruff_python_semantic::analyze::visibility;
|
use ruff_python_semantic::analyze::visibility;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
@ -119,8 +119,8 @@ pub(crate) fn boolean_default_value_positional_argument(
|
||||||
{
|
{
|
||||||
// Allow Boolean defaults in setters.
|
// Allow Boolean defaults in setters.
|
||||||
if decorator_list.iter().any(|decorator| {
|
if decorator_list.iter().any(|decorator| {
|
||||||
CallPath::from_expr(&decorator.expression)
|
UnqualifiedName::from_expr(&decorator.expression)
|
||||||
.is_some_and(|call_path| call_path.segments() == [name, "setter"])
|
.is_some_and(|unqualified_name| unqualified_name.segments() == [name, "setter"])
|
||||||
}) {
|
}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use ruff_python_ast::{self as ast, Decorator, Expr, ParameterWithDefault, Parameters};
|
|
||||||
|
|
||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_diagnostics::Violation;
|
use ruff_diagnostics::Violation;
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::name::UnqualifiedName;
|
||||||
|
use ruff_python_ast::{self as ast, Decorator, Expr, ParameterWithDefault, Parameters};
|
||||||
use ruff_python_semantic::analyze::visibility;
|
use ruff_python_semantic::analyze::visibility;
|
||||||
use ruff_python_semantic::SemanticModel;
|
use ruff_python_semantic::SemanticModel;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
@ -136,8 +135,8 @@ pub(crate) fn boolean_type_hint_positional_argument(
|
||||||
|
|
||||||
// Allow Boolean type hints in setters.
|
// Allow Boolean type hints in setters.
|
||||||
if decorator_list.iter().any(|decorator| {
|
if decorator_list.iter().any(|decorator| {
|
||||||
CallPath::from_expr(&decorator.expression)
|
UnqualifiedName::from_expr(&decorator.expression)
|
||||||
.is_some_and(|call_path| call_path.segments() == [name, "setter"])
|
.is_some_and(|unqualified_name| unqualified_name.segments() == [name, "setter"])
|
||||||
}) {
|
}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -196,11 +195,10 @@ fn match_annotation_to_complex_bool(annotation: &Expr, semantic: &SemanticModel)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let call_path = semantic.resolve_call_path(value);
|
let qualified_name = semantic.resolve_qualified_name(value);
|
||||||
if call_path
|
if qualified_name.as_ref().is_some_and(|qualified_name| {
|
||||||
.as_ref()
|
semantic.match_typing_qualified_name(qualified_name, "Union")
|
||||||
.is_some_and(|call_path| semantic.match_typing_call_path(call_path, "Union"))
|
}) {
|
||||||
{
|
|
||||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||||
elts.iter()
|
elts.iter()
|
||||||
.any(|elt| match_annotation_to_complex_bool(elt, semantic))
|
.any(|elt| match_annotation_to_complex_bool(elt, semantic))
|
||||||
|
@ -208,10 +206,9 @@ fn match_annotation_to_complex_bool(annotation: &Expr, semantic: &SemanticModel)
|
||||||
// Union with a single type is an invalid type annotation
|
// Union with a single type is an invalid type annotation
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
} else if call_path
|
} else if qualified_name.as_ref().is_some_and(|qualified_name| {
|
||||||
.as_ref()
|
semantic.match_typing_qualified_name(qualified_name, "Optional")
|
||||||
.is_some_and(|call_path| semantic.match_typing_call_path(call_path, "Optional"))
|
}) {
|
||||||
{
|
|
||||||
match_annotation_to_complex_bool(slice, semantic)
|
match_annotation_to_complex_bool(slice, semantic)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
@ -109,12 +109,14 @@ fn is_abc_class(bases: &[Expr], keywords: &[Keyword], semantic: &SemanticModel)
|
||||||
keywords.iter().any(|keyword| {
|
keywords.iter().any(|keyword| {
|
||||||
keyword.arg.as_ref().is_some_and(|arg| arg == "metaclass")
|
keyword.arg.as_ref().is_some_and(|arg| arg == "metaclass")
|
||||||
&& semantic
|
&& semantic
|
||||||
.resolve_call_path(&keyword.value)
|
.resolve_qualified_name(&keyword.value)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["abc", "ABCMeta"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["abc", "ABCMeta"])
|
||||||
|
})
|
||||||
}) || bases.iter().any(|base| {
|
}) || bases.iter().any(|base| {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(base)
|
.resolve_qualified_name(base)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["abc", "ABC"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["abc", "ABC"]))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,14 +95,15 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem])
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(exception) = checker
|
let Some(exception) =
|
||||||
.semantic()
|
checker
|
||||||
.resolve_call_path(arg)
|
.semantic()
|
||||||
.and_then(|call_path| match call_path.segments() {
|
.resolve_qualified_name(arg)
|
||||||
["", "Exception"] => Some(ExceptionKind::Exception),
|
.and_then(|qualified_name| match qualified_name.segments() {
|
||||||
["", "BaseException"] => Some(ExceptionKind::BaseException),
|
["", "Exception"] => Some(ExceptionKind::Exception),
|
||||||
_ => None,
|
["", "BaseException"] => Some(ExceptionKind::BaseException),
|
||||||
})
|
_ => None,
|
||||||
|
})
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -112,8 +113,8 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem])
|
||||||
AssertionKind::AssertRaises
|
AssertionKind::AssertRaises
|
||||||
} else if checker
|
} else if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "raises"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pytest", "raises"]))
|
||||||
&& arguments.find_keyword("match").is_none()
|
&& arguments.find_keyword("match").is_none()
|
||||||
{
|
{
|
||||||
AssertionKind::PytestRaises
|
AssertionKind::PytestRaises
|
||||||
|
|
|
@ -71,9 +71,14 @@ impl Violation for CachedInstanceMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic.resolve_call_path(expr).is_some_and(|call_path| {
|
semantic
|
||||||
matches!(call_path.segments(), ["functools", "lru_cache" | "cache"])
|
.resolve_qualified_name(expr)
|
||||||
})
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["functools", "lru_cache" | "cache"]
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// B019
|
/// B019
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext};
|
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysFixableViolation, Violation};
|
use ruff_diagnostics::{AlwaysFixableViolation, Violation};
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::name::UnqualifiedName;
|
||||||
|
use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext};
|
||||||
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::fix::edits::pad;
|
use crate::fix::edits::pad;
|
||||||
|
@ -118,16 +118,16 @@ fn duplicate_handler_exceptions<'a>(
|
||||||
checker: &mut Checker,
|
checker: &mut Checker,
|
||||||
expr: &'a Expr,
|
expr: &'a Expr,
|
||||||
elts: &'a [Expr],
|
elts: &'a [Expr],
|
||||||
) -> FxHashMap<CallPath<'a>, &'a Expr> {
|
) -> FxHashMap<UnqualifiedName<'a>, &'a Expr> {
|
||||||
let mut seen: FxHashMap<CallPath, &Expr> = FxHashMap::default();
|
let mut seen: FxHashMap<UnqualifiedName, &Expr> = FxHashMap::default();
|
||||||
let mut duplicates: FxHashSet<CallPath> = FxHashSet::default();
|
let mut duplicates: FxHashSet<UnqualifiedName> = FxHashSet::default();
|
||||||
let mut unique_elts: Vec<&Expr> = Vec::default();
|
let mut unique_elts: Vec<&Expr> = Vec::default();
|
||||||
for type_ in elts {
|
for type_ in elts {
|
||||||
if let Some(call_path) = CallPath::from_expr(type_) {
|
if let Some(name) = UnqualifiedName::from_expr(type_) {
|
||||||
if seen.contains_key(&call_path) {
|
if seen.contains_key(&name) {
|
||||||
duplicates.insert(call_path);
|
duplicates.insert(name);
|
||||||
} else {
|
} else {
|
||||||
seen.entry(call_path).or_insert(type_);
|
seen.entry(name).or_insert(type_);
|
||||||
unique_elts.push(type_);
|
unique_elts.push(type_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ fn duplicate_handler_exceptions<'a>(
|
||||||
DuplicateHandlerException {
|
DuplicateHandlerException {
|
||||||
names: duplicates
|
names: duplicates
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|call_path| call_path.segments().join("."))
|
.map(|qualified_name| qualified_name.segments().join("."))
|
||||||
.sorted()
|
.sorted()
|
||||||
.collect::<Vec<String>>(),
|
.collect::<Vec<String>>(),
|
||||||
},
|
},
|
||||||
|
@ -171,8 +171,8 @@ fn duplicate_handler_exceptions<'a>(
|
||||||
|
|
||||||
/// B025
|
/// B025
|
||||||
pub(crate) fn duplicate_exceptions(checker: &mut Checker, handlers: &[ExceptHandler]) {
|
pub(crate) fn duplicate_exceptions(checker: &mut Checker, handlers: &[ExceptHandler]) {
|
||||||
let mut seen: FxHashSet<CallPath> = FxHashSet::default();
|
let mut seen: FxHashSet<UnqualifiedName> = FxHashSet::default();
|
||||||
let mut duplicates: FxHashMap<CallPath, Vec<&Expr>> = FxHashMap::default();
|
let mut duplicates: FxHashMap<UnqualifiedName, Vec<&Expr>> = FxHashMap::default();
|
||||||
for handler in handlers {
|
for handler in handlers {
|
||||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||||
type_: Some(type_),
|
type_: Some(type_),
|
||||||
|
@ -183,11 +183,11 @@ pub(crate) fn duplicate_exceptions(checker: &mut Checker, handlers: &[ExceptHand
|
||||||
};
|
};
|
||||||
match type_.as_ref() {
|
match type_.as_ref() {
|
||||||
Expr::Attribute(_) | Expr::Name(_) => {
|
Expr::Attribute(_) | Expr::Name(_) => {
|
||||||
if let Some(call_path) = CallPath::from_expr(type_) {
|
if let Some(name) = UnqualifiedName::from_expr(type_) {
|
||||||
if seen.contains(&call_path) {
|
if seen.contains(&name) {
|
||||||
duplicates.entry(call_path).or_default().push(type_);
|
duplicates.entry(name).or_default().push(type_);
|
||||||
} else {
|
} else {
|
||||||
seen.insert(call_path);
|
seen.insert(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||||
use ruff_diagnostics::Violation;
|
use ruff_diagnostics::Violation;
|
||||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::{compose_call_path, CallPath};
|
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
|
||||||
use ruff_python_ast::visitor;
|
use ruff_python_ast::visitor;
|
||||||
use ruff_python_ast::visitor::Visitor;
|
use ruff_python_ast::visitor::Visitor;
|
||||||
use ruff_python_semantic::analyze::typing::{
|
use ruff_python_semantic::analyze::typing::{
|
||||||
|
@ -81,12 +81,15 @@ impl Violation for FunctionCallInDefaultArgument {
|
||||||
|
|
||||||
struct ArgumentDefaultVisitor<'a, 'b> {
|
struct ArgumentDefaultVisitor<'a, 'b> {
|
||||||
semantic: &'a SemanticModel<'b>,
|
semantic: &'a SemanticModel<'b>,
|
||||||
extend_immutable_calls: &'a [CallPath<'b>],
|
extend_immutable_calls: &'a [QualifiedName<'b>],
|
||||||
diagnostics: Vec<(DiagnosticKind, TextRange)>,
|
diagnostics: Vec<(DiagnosticKind, TextRange)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> ArgumentDefaultVisitor<'a, 'b> {
|
impl<'a, 'b> ArgumentDefaultVisitor<'a, 'b> {
|
||||||
fn new(semantic: &'a SemanticModel<'b>, extend_immutable_calls: &'a [CallPath<'b>]) -> Self {
|
fn new(
|
||||||
|
semantic: &'a SemanticModel<'b>,
|
||||||
|
extend_immutable_calls: &'a [QualifiedName<'b>],
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
semantic,
|
semantic,
|
||||||
extend_immutable_calls,
|
extend_immutable_calls,
|
||||||
|
@ -104,7 +107,7 @@ impl Visitor<'_> for ArgumentDefaultVisitor<'_, '_> {
|
||||||
{
|
{
|
||||||
self.diagnostics.push((
|
self.diagnostics.push((
|
||||||
FunctionCallInDefaultArgument {
|
FunctionCallInDefaultArgument {
|
||||||
name: compose_call_path(func),
|
name: UnqualifiedName::from_expr(func).map(|name| name.to_string()),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
expr.range(),
|
expr.range(),
|
||||||
|
@ -123,12 +126,12 @@ impl Visitor<'_> for ArgumentDefaultVisitor<'_, '_> {
|
||||||
/// B008
|
/// B008
|
||||||
pub(crate) fn function_call_in_argument_default(checker: &mut Checker, parameters: &Parameters) {
|
pub(crate) fn function_call_in_argument_default(checker: &mut Checker, parameters: &Parameters) {
|
||||||
// Map immutable calls to (module, member) format.
|
// Map immutable calls to (module, member) format.
|
||||||
let extend_immutable_calls: Vec<CallPath> = checker
|
let extend_immutable_calls: Vec<QualifiedName> = checker
|
||||||
.settings
|
.settings
|
||||||
.flake8_bugbear
|
.flake8_bugbear
|
||||||
.extend_immutable_calls
|
.extend_immutable_calls
|
||||||
.iter()
|
.iter()
|
||||||
.map(|target| CallPath::from_qualified_name(target))
|
.map(|target| QualifiedName::from_dotted_name(target))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut visitor = ArgumentDefaultVisitor::new(checker.semantic(), &extend_immutable_calls);
|
let mut visitor = ArgumentDefaultVisitor::new(checker.semantic(), &extend_immutable_calls);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use ast::call_path::CallPath;
|
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||||
|
use ruff_python_ast::name::QualifiedName;
|
||||||
use ruff_python_ast::{self as ast, Expr, Parameter, ParameterWithDefault, Stmt};
|
use ruff_python_ast::{self as ast, Expr, Parameter, ParameterWithDefault, Stmt};
|
||||||
use ruff_python_codegen::{Generator, Stylist};
|
use ruff_python_codegen::{Generator, Stylist};
|
||||||
use ruff_python_index::Indexer;
|
use ruff_python_index::Indexer;
|
||||||
|
@ -98,12 +98,12 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let extend_immutable_calls: Vec<CallPath> = checker
|
let extend_immutable_calls: Vec<QualifiedName> = checker
|
||||||
.settings
|
.settings
|
||||||
.flake8_bugbear
|
.flake8_bugbear
|
||||||
.extend_immutable_calls
|
.extend_immutable_calls
|
||||||
.iter()
|
.iter()
|
||||||
.map(|target| CallPath::from_qualified_name(target))
|
.map(|target| QualifiedName::from_dotted_name(target))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if is_mutable_expr(default, checker.semantic())
|
if is_mutable_expr(default, checker.semantic())
|
||||||
|
|
|
@ -40,8 +40,8 @@ impl Violation for NoExplicitStacklevel {
|
||||||
pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, call: &ast::ExprCall) {
|
pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["warnings", "warn"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["warnings", "warn"]))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,8 +63,8 @@ pub(crate) fn re_sub_positional_args(checker: &mut Checker, call: &ast::ExprCall
|
||||||
|
|
||||||
let Some(method) = checker
|
let Some(method) = checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.and_then(|call_path| match call_path.segments() {
|
.and_then(|qualified_name| match qualified_name.segments() {
|
||||||
["re", "sub"] => Some(Method::Sub),
|
["re", "sub"] => Some(Method::Sub),
|
||||||
["re", "subn"] => Some(Method::Subn),
|
["re", "subn"] => Some(Method::Subn),
|
||||||
["re", "split"] => Some(Method::Split),
|
["re", "split"] => Some(Method::Split),
|
||||||
|
|
|
@ -310,8 +310,8 @@ pub(crate) fn reuse_of_groupby_generator(
|
||||||
// Check if the function call is `itertools.groupby`
|
// Check if the function call is `itertools.groupby`
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["itertools", "groupby"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["itertools", "groupby"]))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,8 +59,10 @@ pub(crate) fn useless_contextlib_suppress(
|
||||||
if args.is_empty()
|
if args.is_empty()
|
||||||
&& checker
|
&& checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["contextlib", "suppress"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["contextlib", "suppress"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
|
|
@ -99,32 +99,34 @@ fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
semantic.resolve_call_path(func).is_some_and(|call_path| {
|
semantic
|
||||||
match call_path.segments() {
|
.resolve_qualified_name(func)
|
||||||
["itertools", "cycle" | "count"] => true,
|
.is_some_and(|qualified_name| {
|
||||||
["itertools", "repeat"] => {
|
match qualified_name.segments() {
|
||||||
// Ex) `itertools.repeat(1)`
|
["itertools", "cycle" | "count"] => true,
|
||||||
if keywords.is_empty() && args.len() == 1 {
|
["itertools", "repeat"] => {
|
||||||
return true;
|
// Ex) `itertools.repeat(1)`
|
||||||
}
|
if keywords.is_empty() && args.len() == 1 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Ex) `itertools.repeat(1, None)`
|
// Ex) `itertools.repeat(1, None)`
|
||||||
if args.len() == 2 && args[1].is_none_literal_expr() {
|
if args.len() == 2 && args[1].is_none_literal_expr() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ex) `iterools.repeat(1, times=None)`
|
// Ex) `iterools.repeat(1, times=None)`
|
||||||
for keyword in keywords.iter() {
|
for keyword in keywords.iter() {
|
||||||
if keyword.arg.as_ref().is_some_and(|name| name == "times") {
|
if keyword.arg.as_ref().is_some_and(|name| name == "times") {
|
||||||
if keyword.value.is_none_literal_expr() {
|
if keyword.value.is_none_literal_expr() {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
false
|
false
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
_ => false,
|
})
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,9 +64,12 @@ pub(crate) fn call_date_fromtimestamp(checker: &mut Checker, func: &Expr, locati
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(call_path.segments(), ["datetime", "date", "fromtimestamp"])
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["datetime", "date", "fromtimestamp"]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
|
|
|
@ -63,8 +63,10 @@ pub(crate) fn call_date_today(checker: &mut Checker, func: &Expr, location: Text
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "date", "today"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["datetime", "date", "today"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
|
|
@ -67,10 +67,10 @@ pub(crate) fn call_datetime_fromtimestamp(checker: &mut Checker, call: &ast::Exp
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["datetime", "datetime", "fromtimestamp"]
|
["datetime", "datetime", "fromtimestamp"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -63,8 +63,10 @@ pub(crate) fn call_datetime_now_without_tzinfo(checker: &mut Checker, call: &ast
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "datetime", "now"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["datetime", "datetime", "now"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,9 +71,12 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: &
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(call_path.segments(), ["datetime", "datetime", "strptime"])
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["datetime", "datetime", "strptime"]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -62,8 +62,10 @@ pub(crate) fn call_datetime_today(checker: &mut Checker, func: &Expr, location:
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "datetime", "today"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["datetime", "datetime", "today"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,10 +70,10 @@ pub(crate) fn call_datetime_utcfromtimestamp(
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["datetime", "datetime", "utcfromtimestamp"]
|
["datetime", "datetime", "utcfromtimestamp"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -66,8 +66,13 @@ pub(crate) fn call_datetime_utcnow(checker: &mut Checker, func: &Expr, location:
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "datetime", "utcnow"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["datetime", "datetime", "utcnow"]
|
||||||
|
)
|
||||||
|
})
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,8 +59,8 @@ pub(crate) fn call_datetime_without_tzinfo(checker: &mut Checker, call: &ast::Ex
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "datetime"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["datetime", "datetime"]))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use ruff_python_ast::{Expr, Stmt};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::{CallPath, CallPathBuilder};
|
use ruff_python_ast::name::{QualifiedName, QualifiedNameBuilder};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -48,16 +48,17 @@ impl Violation for Debugger {
|
||||||
|
|
||||||
/// Checks for the presence of a debugger call.
|
/// Checks for the presence of a debugger call.
|
||||||
pub(crate) fn debugger_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
pub(crate) fn debugger_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||||
if let Some(using_type) = checker
|
if let Some(using_type) =
|
||||||
.semantic()
|
checker
|
||||||
.resolve_call_path(func)
|
.semantic()
|
||||||
.and_then(|call_path| {
|
.resolve_qualified_name(func)
|
||||||
if is_debugger_call(&call_path) {
|
.and_then(|qualified_name| {
|
||||||
Some(DebuggerUsingType::Call(call_path.to_string()))
|
if is_debugger_call(&qualified_name) {
|
||||||
} else {
|
Some(DebuggerUsingType::Call(qualified_name.to_string()))
|
||||||
None
|
} else {
|
||||||
}
|
None
|
||||||
})
|
}
|
||||||
|
})
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
@ -68,22 +69,23 @@ pub(crate) fn debugger_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||||
/// Checks for the presence of a debugger import.
|
/// Checks for the presence of a debugger import.
|
||||||
pub(crate) fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option<Diagnostic> {
|
pub(crate) fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option<Diagnostic> {
|
||||||
if let Some(module) = module {
|
if let Some(module) = module {
|
||||||
let mut builder = CallPathBuilder::from_path(CallPath::from_unqualified_name(module));
|
let mut builder =
|
||||||
|
QualifiedNameBuilder::from_qualified_name(QualifiedName::imported(module));
|
||||||
builder.push(name);
|
builder.push(name);
|
||||||
let call_path = builder.build();
|
let qualified_name = builder.build();
|
||||||
|
|
||||||
if is_debugger_call(&call_path) {
|
if is_debugger_call(&qualified_name) {
|
||||||
return Some(Diagnostic::new(
|
return Some(Diagnostic::new(
|
||||||
Debugger {
|
Debugger {
|
||||||
using_type: DebuggerUsingType::Import(call_path.to_string()),
|
using_type: DebuggerUsingType::Import(qualified_name.to_string()),
|
||||||
},
|
},
|
||||||
stmt.range(),
|
stmt.range(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let call_path: CallPath = CallPath::from_unqualified_name(name);
|
let qualified_name = QualifiedName::imported(name);
|
||||||
|
|
||||||
if is_debugger_import(&call_path) {
|
if is_debugger_import(&qualified_name) {
|
||||||
return Some(Diagnostic::new(
|
return Some(Diagnostic::new(
|
||||||
Debugger {
|
Debugger {
|
||||||
using_type: DebuggerUsingType::Import(name.to_string()),
|
using_type: DebuggerUsingType::Import(name.to_string()),
|
||||||
|
@ -95,9 +97,9 @@ pub(crate) fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) ->
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_debugger_call(call_path: &CallPath) -> bool {
|
fn is_debugger_call(qualified_name: &QualifiedName) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["pdb" | "pudb" | "ipdb", "set_trace"]
|
["pdb" | "pudb" | "ipdb", "set_trace"]
|
||||||
| ["ipdb", "sset_trace"]
|
| ["ipdb", "sset_trace"]
|
||||||
| ["IPython", "terminal", "embed", "InteractiveShellEmbed"]
|
| ["IPython", "terminal", "embed", "InteractiveShellEmbed"]
|
||||||
|
@ -115,13 +117,13 @@ fn is_debugger_call(call_path: &CallPath) -> bool {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_debugger_import(call_path: &CallPath) -> bool {
|
fn is_debugger_import(qualified_name: &QualifiedName) -> bool {
|
||||||
// Constructed by taking every pattern in `is_debugger_call`, removing the last element in
|
// Constructed by taking every pattern in `is_debugger_call`, removing the last element in
|
||||||
// each pattern, and de-duplicating the values.
|
// each pattern, and de-duplicating the values.
|
||||||
// As a special-case, we omit `builtins` to allow `import builtins`, which is far more general
|
// As a special-case, we omit `builtins` to allow `import builtins`, which is far more general
|
||||||
// than (e.g.) `import celery.contrib.rdb`.
|
// than (e.g.) `import celery.contrib.rdb`.
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["pdb" | "pudb" | "ipdb" | "debugpy" | "ptvsd"]
|
["pdb" | "pudb" | "ipdb" | "debugpy" | "ptvsd"]
|
||||||
| ["IPython", "terminal", "embed"]
|
| ["IPython", "terminal", "embed"]
|
||||||
| ["IPython", "frontend", "terminal", "embed",]
|
| ["IPython", "frontend", "terminal", "embed",]
|
||||||
|
|
|
@ -4,16 +4,19 @@ use ruff_python_semantic::{analyze, SemanticModel};
|
||||||
|
|
||||||
/// Return `true` if a Python class appears to be a Django model, based on its base classes.
|
/// Return `true` if a Python class appears to be a Django model, based on its base classes.
|
||||||
pub(super) fn is_model(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
|
pub(super) fn is_model(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
|
||||||
analyze::class::any_call_path(class_def, semantic, &|call_path| {
|
analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| {
|
||||||
matches!(call_path.segments(), ["django", "db", "models", "Model"])
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["django", "db", "models", "Model"]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if a Python class appears to be a Django model form, based on its base classes.
|
/// Return `true` if a Python class appears to be a Django model form, based on its base classes.
|
||||||
pub(super) fn is_model_form(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
|
pub(super) fn is_model_form(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
|
||||||
analyze::class::any_call_path(class_def, semantic, &|call_path| {
|
analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["django", "forms", "ModelForm"] | ["django", "forms", "models", "ModelForm"]
|
["django", "forms", "ModelForm"] | ["django", "forms", "models", "ModelForm"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -21,11 +24,13 @@ pub(super) fn is_model_form(class_def: &ast::StmtClassDef, semantic: &SemanticMo
|
||||||
|
|
||||||
/// Return `true` if the expression is constructor for a Django model field.
|
/// Return `true` if the expression is constructor for a Django model field.
|
||||||
pub(super) fn is_model_field(expr: &Expr, semantic: &SemanticModel) -> bool {
|
pub(super) fn is_model_field(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic.resolve_call_path(expr).is_some_and(|call_path| {
|
semantic
|
||||||
call_path
|
.resolve_qualified_name(expr)
|
||||||
.segments()
|
.is_some_and(|qualified_name| {
|
||||||
.starts_with(&["django", "db", "models"])
|
qualified_name
|
||||||
})
|
.segments()
|
||||||
|
.starts_with(&["django", "db", "models"])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the name of the field type, if the expression is constructor for a Django model field.
|
/// Return the name of the field type, if the expression is constructor for a Django model field.
|
||||||
|
@ -33,11 +38,13 @@ pub(super) fn get_model_field_name<'a>(
|
||||||
expr: &'a Expr,
|
expr: &'a Expr,
|
||||||
semantic: &'a SemanticModel,
|
semantic: &'a SemanticModel,
|
||||||
) -> Option<&'a str> {
|
) -> Option<&'a str> {
|
||||||
semantic.resolve_call_path(expr).and_then(|call_path| {
|
semantic
|
||||||
let call_path = call_path.segments();
|
.resolve_qualified_name(expr)
|
||||||
if !call_path.starts_with(&["django", "db", "models"]) {
|
.and_then(|qualified_name| {
|
||||||
return None;
|
let qualified_name = qualified_name.segments();
|
||||||
}
|
if !qualified_name.starts_with(&["django", "db", "models"]) {
|
||||||
call_path.last().copied()
|
return None;
|
||||||
})
|
}
|
||||||
|
qualified_name.last().copied()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,8 +51,10 @@ pub(crate) fn locals_in_render_function(checker: &mut Checker, call: &ast::ExprC
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["django", "shortcuts", "render"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["django", "shortcuts", "render"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -72,6 +74,6 @@ fn is_locals_call(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["", "locals"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["", "locals"]))
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,9 +61,12 @@ pub(crate) fn non_leading_receiver_decorator(checker: &mut Checker, decorator_li
|
||||||
let is_receiver = decorator.expression.as_call_expr().is_some_and(|call| {
|
let is_receiver = decorator.expression.as_call_expr().is_some_and(|call| {
|
||||||
checker
|
checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(call_path.segments(), ["django", "dispatch", "receiver"])
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["django", "dispatch", "receiver"]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
if i > 0 && is_receiver && !seen_receiver {
|
if i > 0 && is_receiver && !seen_receiver {
|
||||||
|
|
|
@ -80,7 +80,7 @@ impl Violation for FutureRewritableTypeAnnotation {
|
||||||
pub(crate) fn future_rewritable_type_annotation(checker: &mut Checker, expr: &Expr) {
|
pub(crate) fn future_rewritable_type_annotation(checker: &mut Checker, expr: &Expr) {
|
||||||
let name = checker
|
let name = checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(expr)
|
.resolve_qualified_name(expr)
|
||||||
.map(|binding| binding.to_string());
|
.map(|binding| binding.to_string());
|
||||||
|
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
|
|
|
@ -61,8 +61,8 @@ pub(crate) fn direct_logger_instantiation(checker: &mut Checker, call: &ast::Exp
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(call.func.as_ref())
|
.resolve_qualified_name(call.func.as_ref())
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["logging", "Logger"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["logging", "Logger"]))
|
||||||
{
|
{
|
||||||
let mut diagnostic = Diagnostic::new(DirectLoggerInstantiation, call.func.range());
|
let mut diagnostic = Diagnostic::new(DirectLoggerInstantiation, call.func.range());
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
|
|
|
@ -62,8 +62,10 @@ pub(crate) fn exception_without_exc_info(checker: &mut Checker, call: &ExprCall)
|
||||||
Expr::Name(_) => {
|
Expr::Name(_) => {
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(call.func.as_ref())
|
.resolve_qualified_name(call.func.as_ref())
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["logging", "exception"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["logging", "exception"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,8 +77,8 @@ pub(crate) fn invalid_get_logger_argument(checker: &mut Checker, call: &ast::Exp
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(call.func.as_ref())
|
.resolve_qualified_name(call.func.as_ref())
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["logging", "getLogger"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["logging", "getLogger"]))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,8 +55,8 @@ pub(crate) fn undocumented_warn(checker: &mut Checker, expr: &Expr) {
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(expr)
|
.resolve_qualified_name(expr)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["logging", "WARN"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["logging", "WARN"]))
|
||||||
{
|
{
|
||||||
let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range());
|
let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range());
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
|
|
|
@ -110,8 +110,8 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) {
|
||||||
}) => {
|
}) => {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["", "dict"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["", "dict"]))
|
||||||
{
|
{
|
||||||
for keyword in keywords.iter() {
|
for keyword in keywords.iter() {
|
||||||
if let Some(attr) = &keyword.arg {
|
if let Some(attr) = &keyword.arg {
|
||||||
|
@ -165,10 +165,13 @@ pub(crate) fn logging_call(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
(call_type, attr.range())
|
(call_type, attr.range())
|
||||||
}
|
}
|
||||||
Expr::Name(_) => {
|
Expr::Name(_) => {
|
||||||
let Some(call_path) = checker.semantic().resolve_call_path(call.func.as_ref()) else {
|
let Some(qualified_name) = checker
|
||||||
|
.semantic()
|
||||||
|
.resolve_qualified_name(call.func.as_ref())
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let ["logging", attribute] = call_path.segments() else {
|
let ["logging", attribute] = qualified_name.segments() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(call_type) = LoggingCallType::from_attribute(attribute) else {
|
let Some(call_type) = LoggingCallType::from_attribute(attribute) else {
|
||||||
|
|
|
@ -62,8 +62,8 @@ pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stm
|
||||||
if !parent.bases().iter().any(|expr| {
|
if !parent.bases().iter().any(|expr| {
|
||||||
checker
|
checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(expr)
|
.resolve_qualified_name(expr)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["enum", "Enum"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["enum", "Enum"]))
|
||||||
}) {
|
}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -77,8 +77,8 @@ pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stm
|
||||||
if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
|
if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["enum", "auto"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["enum", "auto"]))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,30 +98,31 @@ impl Violation for PPrint {
|
||||||
/// T201, T203
|
/// T201, T203
|
||||||
pub(crate) fn print_call(checker: &mut Checker, call: &ast::ExprCall) {
|
pub(crate) fn print_call(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
let mut diagnostic = {
|
let mut diagnostic = {
|
||||||
let call_path = checker.semantic().resolve_call_path(&call.func);
|
let qualified_name = checker.semantic().resolve_qualified_name(&call.func);
|
||||||
if call_path
|
if qualified_name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["", "print"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["", "print"]))
|
||||||
{
|
{
|
||||||
// If the print call has a `file=` argument (that isn't `None`, `"sys.stdout"`,
|
// If the print call has a `file=` argument (that isn't `None`, `"sys.stdout"`,
|
||||||
// or `"sys.stderr"`), don't trigger T201.
|
// or `"sys.stderr"`), don't trigger T201.
|
||||||
if let Some(keyword) = call.arguments.find_keyword("file") {
|
if let Some(keyword) = call.arguments.find_keyword("file") {
|
||||||
if !keyword.value.is_none_literal_expr() {
|
if !keyword.value.is_none_literal_expr() {
|
||||||
if checker.semantic().resolve_call_path(&keyword.value).map_or(
|
if checker
|
||||||
true,
|
.semantic()
|
||||||
|call_path| {
|
.resolve_qualified_name(&keyword.value)
|
||||||
call_path.segments() != ["sys", "stdout"]
|
.map_or(true, |qualified_name| {
|
||||||
&& call_path.segments() != ["sys", "stderr"]
|
qualified_name.segments() != ["sys", "stdout"]
|
||||||
},
|
&& qualified_name.segments() != ["sys", "stderr"]
|
||||||
) {
|
})
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Diagnostic::new(Print, call.func.range())
|
Diagnostic::new(Print, call.func.range())
|
||||||
} else if call_path
|
} else if qualified_name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["pprint", "pprint"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pprint", "pprint"]))
|
||||||
{
|
{
|
||||||
Diagnostic::new(PPrint, call.func.range())
|
Diagnostic::new(PPrint, call.func.range())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -125,10 +125,10 @@ pub(crate) fn bad_generator_return_type(
|
||||||
// Determine the module from which the existing annotation is imported (e.g., `typing` or
|
// Determine the module from which the existing annotation is imported (e.g., `typing` or
|
||||||
// `collections.abc`)
|
// `collections.abc`)
|
||||||
let (method, module, member) = {
|
let (method, module, member) = {
|
||||||
let Some(call_path) = semantic.resolve_call_path(map_subscript(returns)) else {
|
let Some(qualified_name) = semantic.resolve_qualified_name(map_subscript(returns)) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match (name, call_path.segments()) {
|
match (name, qualified_name.segments()) {
|
||||||
("__iter__", ["typing", "Generator"]) => {
|
("__iter__", ["typing", "Generator"]) => {
|
||||||
(Method::Iter, Module::Typing, Generator::Generator)
|
(Method::Iter, Module::Typing, Generator::Generator)
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,8 +75,8 @@ pub(crate) fn bad_version_info_comparison(checker: &mut Checker, test: &Expr) {
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(left)
|
.resolve_qualified_name(left)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["sys", "version_info"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["sys", "version_info"]))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,8 +57,10 @@ pub(crate) fn collections_named_tuple(checker: &mut Checker, expr: &Expr) {
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(expr)
|
.resolve_qualified_name(expr)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["collections", "namedtuple"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["collections", "namedtuple"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
|
|
@ -67,9 +67,12 @@ pub(crate) fn complex_if_statement_in_stub(checker: &mut Checker, test: &Expr) {
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(left)
|
.resolve_qualified_name(left)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(call_path.segments(), ["sys", "version_info" | "platform"])
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["sys", "version_info" | "platform"]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -244,11 +244,11 @@ fn non_none_annotation_element<'a>(
|
||||||
) -> Option<&'a Expr> {
|
) -> Option<&'a Expr> {
|
||||||
// E.g., `typing.Union` or `typing.Optional`
|
// E.g., `typing.Union` or `typing.Optional`
|
||||||
if let Expr::Subscript(ExprSubscript { value, slice, .. }) = annotation {
|
if let Expr::Subscript(ExprSubscript { value, slice, .. }) = annotation {
|
||||||
let call_path = semantic.resolve_call_path(value);
|
let qualified_name = semantic.resolve_qualified_name(value);
|
||||||
|
|
||||||
if call_path
|
if qualified_name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|value| semantic.match_typing_call_path(value, "Optional"))
|
.is_some_and(|value| semantic.match_typing_qualified_name(value, "Optional"))
|
||||||
{
|
{
|
||||||
return if slice.is_none_literal_expr() {
|
return if slice.is_none_literal_expr() {
|
||||||
None
|
None
|
||||||
|
@ -257,9 +257,9 @@ fn non_none_annotation_element<'a>(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if !call_path
|
if !qualified_name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|value| semantic.match_typing_call_path(value, "Union"))
|
.is_some_and(|value| semantic.match_typing_qualified_name(value, "Union"))
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -305,11 +305,11 @@ fn non_none_annotation_element<'a>(
|
||||||
/// Return `true` if the [`Expr`] is the `object` builtin or the `_typeshed.Unused` type.
|
/// Return `true` if the [`Expr`] is the `object` builtin or the `_typeshed.Unused` type.
|
||||||
fn is_object_or_unused(expr: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_object_or_unused(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(expr)
|
.resolve_qualified_name(expr)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["" | "builtins", "object"] | ["_typeshed", "Unused"]
|
["" | "builtins", "object"] | ["_typeshed", "Unused"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -318,17 +318,24 @@ fn is_object_or_unused(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
/// Return `true` if the [`Expr`] is `BaseException`.
|
/// Return `true` if the [`Expr`] is `BaseException`.
|
||||||
fn is_base_exception(expr: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_base_exception(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(expr)
|
.resolve_qualified_name(expr)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["" | "builtins", "BaseException"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["" | "builtins", "BaseException"]
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the [`Expr`] is the `types.TracebackType` type.
|
/// Return `true` if the [`Expr`] is the `types.TracebackType` type.
|
||||||
fn is_traceback_type(expr: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_traceback_type(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(expr)
|
.resolve_qualified_name(expr)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["types", "TracebackType"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["types", "TracebackType"])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the [`Expr`] is, e.g., `Type[BaseException]`.
|
/// Return `true` if the [`Expr`] is, e.g., `Type[BaseException]`.
|
||||||
|
@ -339,9 +346,11 @@ fn is_base_exception_type(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
|
|
||||||
if semantic.match_typing_expr(value, "Type")
|
if semantic.match_typing_expr(value, "Type")
|
||||||
|| semantic
|
|| semantic
|
||||||
.resolve_call_path(value)
|
.resolve_qualified_name(value)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["" | "builtins", "type"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["" | "builtins", "type"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
is_base_exception(slice, semantic)
|
is_base_exception(slice, semantic)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -88,16 +88,16 @@ pub(crate) fn iter_method_return_iterable(checker: &mut Checker, definition: &De
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(map_subscript(annotation))
|
.resolve_qualified_name(map_subscript(annotation))
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
if is_async {
|
if is_async {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["typing", "AsyncIterable"] | ["collections", "abc", "AsyncIterable"]
|
["typing", "AsyncIterable"] | ["collections", "abc", "AsyncIterable"]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["typing", "Iterable"] | ["collections", "abc", "Iterable"]
|
["typing", "Iterable"] | ["collections", "abc", "Iterable"]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,12 +231,14 @@ fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool
|
||||||
|
|
||||||
/// Returns `true` if the given expression resolves to a metaclass.
|
/// Returns `true` if the given expression resolves to a metaclass.
|
||||||
fn is_metaclass_base(base: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_metaclass_base(base: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic.resolve_call_path(base).is_some_and(|call_path| {
|
semantic
|
||||||
matches!(
|
.resolve_qualified_name(base)
|
||||||
call_path.segments(),
|
.is_some_and(|qualified_name| {
|
||||||
["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"]
|
matches!(
|
||||||
)
|
qualified_name.segments(),
|
||||||
})
|
["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"]
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the method is an in-place binary operator.
|
/// Returns `true` if the method is an in-place binary operator.
|
||||||
|
@ -279,10 +281,10 @@ fn is_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool
|
||||||
};
|
};
|
||||||
bases.iter().any(|expr| {
|
bases.iter().any(|expr| {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(map_subscript(expr))
|
.resolve_qualified_name(map_subscript(expr))
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["typing", "Iterator"] | ["collections", "abc", "Iterator"]
|
["typing", "Iterator"] | ["collections", "abc", "Iterator"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -292,10 +294,10 @@ fn is_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool
|
||||||
/// Return `true` if the given expression resolves to `collections.abc.Iterable`.
|
/// Return `true` if the given expression resolves to `collections.abc.Iterable`.
|
||||||
fn is_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(map_subscript(expr))
|
.resolve_qualified_name(map_subscript(expr))
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["typing", "Iterable" | "Iterator"]
|
["typing", "Iterable" | "Iterator"]
|
||||||
| ["collections", "abc", "Iterable" | "Iterator"]
|
| ["collections", "abc", "Iterable" | "Iterator"]
|
||||||
)
|
)
|
||||||
|
@ -309,10 +311,10 @@ fn is_async_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) ->
|
||||||
};
|
};
|
||||||
bases.iter().any(|expr| {
|
bases.iter().any(|expr| {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(map_subscript(expr))
|
.resolve_qualified_name(map_subscript(expr))
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["typing", "AsyncIterator"] | ["collections", "abc", "AsyncIterator"]
|
["typing", "AsyncIterator"] | ["collections", "abc", "AsyncIterator"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -322,10 +324,10 @@ fn is_async_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) ->
|
||||||
/// Return `true` if the given expression resolves to `collections.abc.AsyncIterable`.
|
/// Return `true` if the given expression resolves to `collections.abc.AsyncIterable`.
|
||||||
fn is_async_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_async_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(map_subscript(expr))
|
.resolve_qualified_name(map_subscript(expr))
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["typing", "AsyncIterable" | "AsyncIterator"]
|
["typing", "AsyncIterable" | "AsyncIterator"]
|
||||||
| ["collections", "abc", "AsyncIterable" | "AsyncIterator"]
|
| ["collections", "abc", "AsyncIterable" | "AsyncIterator"]
|
||||||
)
|
)
|
||||||
|
|
|
@ -81,21 +81,21 @@ pub(crate) fn prefix_type_params(checker: &mut Checker, value: &Expr, targets: &
|
||||||
|
|
||||||
let Some(kind) = checker
|
let Some(kind) = checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.and_then(|call_path| {
|
.and_then(|qualified_name| {
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.match_typing_call_path(&call_path, "ParamSpec")
|
.match_typing_qualified_name(&qualified_name, "ParamSpec")
|
||||||
{
|
{
|
||||||
Some(VarKind::ParamSpec)
|
Some(VarKind::ParamSpec)
|
||||||
} else if checker
|
} else if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.match_typing_call_path(&call_path, "TypeVar")
|
.match_typing_qualified_name(&qualified_name, "TypeVar")
|
||||||
{
|
{
|
||||||
Some(VarKind::TypeVar)
|
Some(VarKind::TypeVar)
|
||||||
} else if checker
|
} else if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.match_typing_call_path(&call_path, "TypeVarTuple")
|
.match_typing_qualified_name(&qualified_name, "TypeVarTuple")
|
||||||
{
|
{
|
||||||
Some(VarKind::TypeVarTuple)
|
Some(VarKind::TypeVarTuple)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -90,11 +90,11 @@ fn check_annotation(checker: &mut Checker, annotation: &Expr) {
|
||||||
let mut has_int = false;
|
let mut has_int = false;
|
||||||
|
|
||||||
let mut func = |expr: &Expr, _parent: &Expr| {
|
let mut func = |expr: &Expr, _parent: &Expr| {
|
||||||
let Some(call_path) = checker.semantic().resolve_call_path(expr) else {
|
let Some(qualified_name) = checker.semantic().resolve_qualified_name(expr) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match call_path.segments() {
|
match qualified_name.segments() {
|
||||||
["" | "builtins", "int"] => has_int = true,
|
["" | "builtins", "int"] => has_int = true,
|
||||||
["" | "builtins", "float"] => has_float = true,
|
["" | "builtins", "float"] => has_float = true,
|
||||||
["" | "builtins", "complex"] => has_complex = true,
|
["" | "builtins", "complex"] => has_complex = true,
|
||||||
|
|
|
@ -2,8 +2,8 @@ use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation};
|
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
|
||||||
use ruff_python_ast::helpers::map_subscript;
|
use ruff_python_ast::helpers::map_subscript;
|
||||||
|
use ruff_python_ast::name::QualifiedName;
|
||||||
use ruff_python_ast::{
|
use ruff_python_ast::{
|
||||||
self as ast, Expr, Operator, ParameterWithDefault, Parameters, Stmt, UnaryOp,
|
self as ast, Expr, Operator, ParameterWithDefault, Parameters, Stmt, UnaryOp,
|
||||||
};
|
};
|
||||||
|
@ -248,13 +248,16 @@ impl AlwaysFixableViolation for TypeAliasWithoutAnnotation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_allowed_negated_math_attribute(call_path: &CallPath) -> bool {
|
fn is_allowed_negated_math_attribute(qualified_name: &QualifiedName) -> bool {
|
||||||
matches!(call_path.segments(), ["math", "inf" | "e" | "pi" | "tau"])
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["math", "inf" | "e" | "pi" | "tau"]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_allowed_math_attribute(call_path: &CallPath) -> bool {
|
fn is_allowed_math_attribute(qualified_name: &QualifiedName) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["math", "inf" | "nan" | "e" | "pi" | "tau"]
|
["math", "inf" | "nan" | "e" | "pi" | "tau"]
|
||||||
| [
|
| [
|
||||||
"sys",
|
"sys",
|
||||||
|
@ -324,7 +327,7 @@ fn is_valid_default_value_with_annotation(
|
||||||
// Ex) `-math.inf`, `-math.pi`, etc.
|
// Ex) `-math.inf`, `-math.pi`, etc.
|
||||||
Expr::Attribute(_) => {
|
Expr::Attribute(_) => {
|
||||||
if semantic
|
if semantic
|
||||||
.resolve_call_path(operand)
|
.resolve_qualified_name(operand)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(is_allowed_negated_math_attribute)
|
.is_some_and(is_allowed_negated_math_attribute)
|
||||||
{
|
{
|
||||||
|
@ -373,7 +376,7 @@ fn is_valid_default_value_with_annotation(
|
||||||
// Ex) `math.inf`, `sys.stdin`, etc.
|
// Ex) `math.inf`, `sys.stdin`, etc.
|
||||||
Expr::Attribute(_) => {
|
Expr::Attribute(_) => {
|
||||||
if semantic
|
if semantic
|
||||||
.resolve_call_path(default)
|
.resolve_qualified_name(default)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(is_allowed_math_attribute)
|
.is_some_and(is_allowed_math_attribute)
|
||||||
{
|
{
|
||||||
|
@ -435,15 +438,17 @@ fn is_type_var_like_call(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
semantic.resolve_call_path(func).is_some_and(|call_path| {
|
semantic
|
||||||
matches!(
|
.resolve_qualified_name(func)
|
||||||
call_path.segments(),
|
.is_some_and(|qualified_name| {
|
||||||
[
|
matches!(
|
||||||
"typing" | "typing_extensions",
|
qualified_name.segments(),
|
||||||
"TypeVar" | "TypeVarTuple" | "NewType" | "ParamSpec"
|
[
|
||||||
]
|
"typing" | "typing_extensions",
|
||||||
)
|
"TypeVar" | "TypeVarTuple" | "NewType" | "ParamSpec"
|
||||||
})
|
]
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if this is a "special" assignment which must have a value (e.g., an assignment to
|
/// Returns `true` if this is a "special" assignment which must have a value (e.g., an assignment to
|
||||||
|
@ -480,10 +485,10 @@ fn is_enum(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
|
||||||
class_def.bases().iter().any(|expr| {
|
class_def.bases().iter().any(|expr| {
|
||||||
// If the base class is `enum.Enum`, `enum.Flag`, etc., then this is an enum.
|
// If the base class is `enum.Enum`, `enum.Flag`, etc., then this is an enum.
|
||||||
if semantic
|
if semantic
|
||||||
.resolve_call_path(map_subscript(expr))
|
.resolve_qualified_name(map_subscript(expr))
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
[
|
[
|
||||||
"enum",
|
"enum",
|
||||||
"Enum" | "Flag" | "IntEnum" | "IntFlag" | "StrEnum" | "ReprEnum"
|
"Enum" | "Flag" | "IntEnum" | "IntFlag" | "StrEnum" | "ReprEnum"
|
||||||
|
|
|
@ -80,9 +80,9 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(returns)
|
.resolve_qualified_name(returns)
|
||||||
.map_or(true, |call_path| {
|
.map_or(true, |qualified_name| {
|
||||||
!matches!(call_path.segments(), ["" | "builtins", "str"])
|
!matches!(qualified_name.segments(), ["" | "builtins", "str"])
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -84,10 +84,10 @@ fn is_warnings_dot_deprecated(expr: Option<&ast::Expr>, semantic: &SemanticModel
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(&call.func)
|
.resolve_qualified_name(&call.func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["warnings" | "typing_extensions", "deprecated"]
|
["warnings" | "typing_extensions", "deprecated"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -80,8 +80,10 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr)
|
||||||
let unwrapped = subscript.unwrap();
|
let unwrapped = subscript.unwrap();
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(unwrapped.value.as_ref())
|
.resolve_qualified_name(unwrapped.value.as_ref())
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["" | "builtins", "type"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["" | "builtins", "type"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
type_exprs.push(unwrapped.slice.as_ref());
|
type_exprs.push(unwrapped.slice.as_ref());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -107,8 +107,8 @@ pub(crate) fn unrecognized_platform(checker: &mut Checker, test: &Expr) {
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(left)
|
.resolve_qualified_name(left)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["sys", "platform"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["sys", "platform"]))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,8 +135,8 @@ pub(crate) fn unrecognized_version_info(checker: &mut Checker, test: &Expr) {
|
||||||
|
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(map_subscript(left))
|
.resolve_qualified_name(map_subscript(left))
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["sys", "version_info"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["sys", "version_info"]))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,17 +195,22 @@ pub(crate) fn unused_private_type_var(
|
||||||
};
|
};
|
||||||
|
|
||||||
let semantic = checker.semantic();
|
let semantic = checker.semantic();
|
||||||
let Some(type_var_like_kind) = semantic.resolve_call_path(func).and_then(|call_path| {
|
let Some(type_var_like_kind) =
|
||||||
if semantic.match_typing_call_path(&call_path, "TypeVar") {
|
semantic
|
||||||
Some("TypeVar")
|
.resolve_qualified_name(func)
|
||||||
} else if semantic.match_typing_call_path(&call_path, "ParamSpec") {
|
.and_then(|qualified_name| {
|
||||||
Some("ParamSpec")
|
if semantic.match_typing_qualified_name(&qualified_name, "TypeVar") {
|
||||||
} else if semantic.match_typing_call_path(&call_path, "TypeVarTuple") {
|
Some("TypeVar")
|
||||||
Some("TypeVarTuple")
|
} else if semantic.match_typing_qualified_name(&qualified_name, "ParamSpec") {
|
||||||
} else {
|
Some("ParamSpec")
|
||||||
None
|
} else if semantic.match_typing_qualified_name(&qualified_name, "TypeVarTuple")
|
||||||
}
|
{
|
||||||
}) else {
|
Some("TypeVarTuple")
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ use std::fmt;
|
||||||
use ruff_diagnostics::{AlwaysFixableViolation, Violation};
|
use ruff_diagnostics::{AlwaysFixableViolation, Violation};
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
|
use ruff_python_ast::name::UnqualifiedName;
|
||||||
use ruff_python_ast::visitor;
|
use ruff_python_ast::visitor;
|
||||||
use ruff_python_ast::visitor::Visitor;
|
use ruff_python_ast::visitor::Visitor;
|
||||||
use ruff_python_ast::Decorator;
|
use ruff_python_ast::Decorator;
|
||||||
|
@ -646,9 +646,9 @@ impl<'a> Visitor<'a> for SkipFunctionsVisitor<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||||
if CallPath::from_expr(func).is_some_and(|call_path| {
|
if UnqualifiedName::from_expr(func)
|
||||||
matches!(call_path.segments(), ["request", "addfinalizer"])
|
.is_some_and(|name| matches!(name.segments(), ["request", "addfinalizer"]))
|
||||||
}) {
|
{
|
||||||
self.addfinalizer_call = Some(expr);
|
self.addfinalizer_call = Some(expr);
|
||||||
};
|
};
|
||||||
visitor::walk_expr(self, expr);
|
visitor::walk_expr(self, expr);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use ruff_python_ast::call_path::CallPath;
|
|
||||||
use ruff_python_ast::{self as ast, Decorator, Expr, Keyword};
|
|
||||||
|
|
||||||
use ruff_python_ast::helpers::map_callable;
|
use ruff_python_ast::helpers::map_callable;
|
||||||
|
use ruff_python_ast::name::UnqualifiedName;
|
||||||
|
use ruff_python_ast::{self as ast, Decorator, Expr, Keyword};
|
||||||
use ruff_python_semantic::SemanticModel;
|
use ruff_python_semantic::SemanticModel;
|
||||||
use ruff_python_trivia::PythonWhitespace;
|
use ruff_python_trivia::PythonWhitespace;
|
||||||
|
|
||||||
|
@ -9,10 +8,10 @@ pub(super) fn get_mark_decorators(
|
||||||
decorators: &[Decorator],
|
decorators: &[Decorator],
|
||||||
) -> impl Iterator<Item = (&Decorator, &str)> {
|
) -> impl Iterator<Item = (&Decorator, &str)> {
|
||||||
decorators.iter().filter_map(|decorator| {
|
decorators.iter().filter_map(|decorator| {
|
||||||
let Some(call_path) = CallPath::from_expr(map_callable(&decorator.expression)) else {
|
let Some(name) = UnqualifiedName::from_expr(map_callable(&decorator.expression)) else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let ["pytest", "mark", marker] = call_path.segments() else {
|
let ["pytest", "mark", marker] = name.segments() else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
Some((decorator, *marker))
|
Some((decorator, *marker))
|
||||||
|
@ -21,26 +20,30 @@ pub(super) fn get_mark_decorators(
|
||||||
|
|
||||||
pub(super) fn is_pytest_fail(call: &Expr, semantic: &SemanticModel) -> bool {
|
pub(super) fn is_pytest_fail(call: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(call)
|
.resolve_qualified_name(call)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "fail"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pytest", "fail"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_pytest_fixture(decorator: &Decorator, semantic: &SemanticModel) -> bool {
|
pub(super) fn is_pytest_fixture(decorator: &Decorator, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(map_callable(&decorator.expression))
|
.resolve_qualified_name(map_callable(&decorator.expression))
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "fixture"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pytest", "fixture"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_pytest_yield_fixture(decorator: &Decorator, semantic: &SemanticModel) -> bool {
|
pub(super) fn is_pytest_yield_fixture(decorator: &Decorator, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(map_callable(&decorator.expression))
|
.resolve_qualified_name(map_callable(&decorator.expression))
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "yield_fixture"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["pytest", "yield_fixture"])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_pytest_parametrize(decorator: &Decorator, semantic: &SemanticModel) -> bool {
|
pub(super) fn is_pytest_parametrize(decorator: &Decorator, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(map_callable(&decorator.expression))
|
.resolve_qualified_name(map_callable(&decorator.expression))
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "mark", "parametrize"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["pytest", "mark", "parametrize"])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn keyword_is_literal(keyword: &Keyword, literal: &str) -> bool {
|
pub(super) fn keyword_is_literal(keyword: &Keyword, literal: &str) -> bool {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::name::UnqualifiedName;
|
||||||
use ruff_python_ast::visitor;
|
use ruff_python_ast::visitor;
|
||||||
use ruff_python_ast::visitor::Visitor;
|
use ruff_python_ast::visitor::Visitor;
|
||||||
use ruff_python_ast::{self as ast, Expr, Parameters};
|
use ruff_python_ast::{self as ast, Expr, Parameters};
|
||||||
|
@ -104,10 +104,10 @@ fn check_patch_call(call: &ast::ExprCall, index: usize) -> Option<Diagnostic> {
|
||||||
|
|
||||||
/// PT008
|
/// PT008
|
||||||
pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option<Diagnostic> {
|
pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option<Diagnostic> {
|
||||||
let call_path = CallPath::from_expr(&call.func)?;
|
let name = UnqualifiedName::from_expr(&call.func)?;
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
call_path.segments(),
|
name.segments(),
|
||||||
[
|
[
|
||||||
"mocker"
|
"mocker"
|
||||||
| "class_mocker"
|
| "class_mocker"
|
||||||
|
@ -120,7 +120,7 @@ pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option<Diagnostic> {
|
||||||
) {
|
) {
|
||||||
check_patch_call(call, 1)
|
check_patch_call(call, 1)
|
||||||
} else if matches!(
|
} else if matches!(
|
||||||
call_path.segments(),
|
name.segments(),
|
||||||
[
|
[
|
||||||
"mocker"
|
"mocker"
|
||||||
| "class_mocker"
|
| "class_mocker"
|
||||||
|
|
|
@ -153,8 +153,8 @@ impl Violation for PytestRaisesWithoutException {
|
||||||
|
|
||||||
fn is_pytest_raises(func: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_pytest_raises(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "raises"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pytest", "raises"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn is_non_trivial_with_body(body: &[Stmt]) -> bool {
|
const fn is_non_trivial_with_body(body: &[Stmt]) -> bool {
|
||||||
|
@ -226,11 +226,11 @@ pub(crate) fn complex_raises(
|
||||||
|
|
||||||
/// PT011
|
/// PT011
|
||||||
fn exception_needs_match(checker: &mut Checker, exception: &Expr) {
|
fn exception_needs_match(checker: &mut Checker, exception: &Expr) {
|
||||||
if let Some(call_path) = checker
|
if let Some(qualified_name) = checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(exception)
|
.resolve_qualified_name(exception)
|
||||||
.and_then(|call_path| {
|
.and_then(|qualified_name| {
|
||||||
let call_path = call_path.to_string();
|
let qualified_name = qualified_name.to_string();
|
||||||
checker
|
checker
|
||||||
.settings
|
.settings
|
||||||
.flake8_pytest_style
|
.flake8_pytest_style
|
||||||
|
@ -242,13 +242,13 @@ fn exception_needs_match(checker: &mut Checker, exception: &Expr) {
|
||||||
.flake8_pytest_style
|
.flake8_pytest_style
|
||||||
.raises_extend_require_match_for,
|
.raises_extend_require_match_for,
|
||||||
)
|
)
|
||||||
.any(|pattern| pattern.matches(&call_path))
|
.any(|pattern| pattern.matches(&qualified_name))
|
||||||
.then_some(call_path)
|
.then_some(qualified_name)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
PytestRaisesTooBroad {
|
PytestRaisesTooBroad {
|
||||||
exception: call_path,
|
exception: qualified_name,
|
||||||
},
|
},
|
||||||
exception.range(),
|
exception.range(),
|
||||||
));
|
));
|
||||||
|
|
|
@ -99,8 +99,10 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
|
||||||
// we might as well get it right.
|
// we might as well get it right.
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["ctypes", "WinError"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["ctypes", "WinError"])
|
||||||
|
})
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -400,16 +400,19 @@ fn implicit_return_value(checker: &mut Checker, stack: &Stack) {
|
||||||
fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
// First, look for known functions that never return from the standard library and popular
|
// First, look for known functions that never return from the standard library and popular
|
||||||
// libraries.
|
// libraries.
|
||||||
if semantic.resolve_call_path(func).is_some_and(|call_path| {
|
if semantic
|
||||||
matches!(
|
.resolve_qualified_name(func)
|
||||||
call_path.segments(),
|
.is_some_and(|qualified_name| {
|
||||||
["" | "builtins" | "sys" | "_thread" | "pytest", "exit"]
|
matches!(
|
||||||
| ["" | "builtins", "quit"]
|
qualified_name.segments(),
|
||||||
| ["os" | "posix", "_exit" | "abort"]
|
["" | "builtins" | "sys" | "_thread" | "pytest", "exit"]
|
||||||
| ["_winapi", "ExitProcess"]
|
| ["" | "builtins", "quit"]
|
||||||
| ["pytest", "fail" | "skip" | "xfail"]
|
| ["os" | "posix", "_exit" | "abort"]
|
||||||
) || semantic.match_typing_call_path(&call_path, "assert_never")
|
| ["_winapi", "ExitProcess"]
|
||||||
}) {
|
| ["pytest", "fail" | "skip" | "xfail"]
|
||||||
|
) || semantic.match_typing_qualified_name(&qualified_name, "assert_never")
|
||||||
|
})
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,11 +433,11 @@ fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(call_path) = semantic.resolve_call_path(returns) else {
|
let Some(qualified_name) = semantic.resolve_qualified_name(returns) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
semantic.match_typing_call_path(&call_path, "NoReturn")
|
semantic.match_typing_qualified_name(&qualified_name, "NoReturn")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RET503
|
/// RET503
|
||||||
|
|
|
@ -198,8 +198,8 @@ fn has_conditional_body(with: &ast::StmtWith, semantic: &SemanticModel) -> bool
|
||||||
else {
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if let Some(call_path) = semantic.resolve_call_path(func) {
|
if let Some(qualified_name) = semantic.resolve_qualified_name(func) {
|
||||||
if call_path.segments() == ["contextlib", "suppress"] {
|
if qualified_name.segments() == ["contextlib", "suppress"] {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::name::UnqualifiedName;
|
||||||
use ruff_python_ast::{self as ast, Expr};
|
use ruff_python_ast::{self as ast, Expr};
|
||||||
use ruff_python_semantic::{BindingKind, ScopeKind};
|
use ruff_python_semantic::{BindingKind, ScopeKind};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
@ -141,24 +141,24 @@ pub(crate) fn private_member_access(checker: &mut Checker, expr: &Expr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow some documented private methods, like `os._exit()`.
|
// Allow some documented private methods, like `os._exit()`.
|
||||||
if let Some(call_path) = checker.semantic().resolve_call_path(expr) {
|
if let Some(qualified_name) = checker.semantic().resolve_qualified_name(expr) {
|
||||||
if matches!(call_path.segments(), ["os", "_exit"]) {
|
if matches!(qualified_name.segments(), ["os", "_exit"]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
|
if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
|
||||||
// Ignore `super()` calls.
|
// Ignore `super()` calls.
|
||||||
if let Some(call_path) = CallPath::from_expr(func) {
|
if let Some(name) = UnqualifiedName::from_expr(func) {
|
||||||
if matches!(call_path.segments(), ["super"]) {
|
if matches!(name.segments(), ["super"]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(call_path) = CallPath::from_expr(value) {
|
if let Some(name) = UnqualifiedName::from_expr(value) {
|
||||||
// Ignore `self` and `cls` accesses.
|
// Ignore `self` and `cls` accesses.
|
||||||
if matches!(call_path.segments(), ["self" | "cls" | "mcs"]) {
|
if matches!(name.segments(), ["self" | "cls" | "mcs"]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,10 +149,10 @@ pub(crate) fn use_capital_environment_variables(checker: &mut Checker, expr: &Ex
|
||||||
};
|
};
|
||||||
if !checker
|
if !checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["os", "environ", "get"] | ["os", "getenv"]
|
["os", "environ", "get"] | ["os", "getenv"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -99,10 +99,10 @@ fn explicit_with_items(checker: &mut Checker, with_items: &[WithItem]) -> bool {
|
||||||
};
|
};
|
||||||
checker
|
checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(&expr_call.func)
|
.resolve_qualified_name(&expr_call.func)
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(
|
matches!(
|
||||||
call_path.segments(),
|
qualified_name.segments(),
|
||||||
["asyncio", "timeout" | "timeout_at"]
|
["asyncio", "timeout" | "timeout_at"]
|
||||||
| ["anyio", "CancelScope" | "fail_after" | "move_on_after"]
|
| ["anyio", "CancelScope" | "fail_after" | "move_on_after"]
|
||||||
| [
|
| [
|
||||||
|
|
|
@ -63,9 +63,12 @@ fn match_async_exit_stack(semantic: &SemanticModel) -> bool {
|
||||||
if let Stmt::With(ast::StmtWith { items, .. }) = parent {
|
if let Stmt::With(ast::StmtWith { items, .. }) = parent {
|
||||||
for item in items {
|
for item in items {
|
||||||
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
|
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
|
||||||
if semantic.resolve_call_path(func).is_some_and(|call_path| {
|
if semantic
|
||||||
matches!(call_path.segments(), ["contextlib", "AsyncExitStack"])
|
.resolve_qualified_name(func)
|
||||||
}) {
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["contextlib", "AsyncExitStack"])
|
||||||
|
})
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,9 +97,12 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool {
|
||||||
if let Stmt::With(ast::StmtWith { items, .. }) = parent {
|
if let Stmt::With(ast::StmtWith { items, .. }) = parent {
|
||||||
for item in items {
|
for item in items {
|
||||||
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
|
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
|
||||||
if semantic.resolve_call_path(func).is_some_and(|call_path| {
|
if semantic
|
||||||
matches!(call_path.segments(), ["contextlib", "ExitStack"])
|
.resolve_qualified_name(func)
|
||||||
}) {
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["contextlib", "ExitStack"])
|
||||||
|
})
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,8 +120,10 @@ fn is_open(checker: &mut Checker, func: &Expr) -> bool {
|
||||||
match value.as_ref() {
|
match value.as_ref() {
|
||||||
Expr::Call(ast::ExprCall { func, .. }) => checker
|
Expr::Call(ast::ExprCall { func, .. }) => checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["pathlib", "Path"])),
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["pathlib", "Path"])
|
||||||
|
}),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::compose_call_path;
|
|
||||||
use ruff_python_ast::helpers;
|
use ruff_python_ast::helpers;
|
||||||
|
use ruff_python_ast::name::UnqualifiedName;
|
||||||
use ruff_python_ast::{self as ast, ExceptHandler, Stmt};
|
use ruff_python_ast::{self as ast, ExceptHandler, Stmt};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
use ruff_text_size::{TextLen, TextRange};
|
use ruff_text_size::{TextLen, TextRange};
|
||||||
|
@ -107,7 +107,7 @@ pub(crate) fn suppressible_exception(
|
||||||
|
|
||||||
let Some(handler_names) = helpers::extract_handled_exceptions(handlers)
|
let Some(handler_names) = helpers::extract_handled_exceptions(handlers)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(compose_call_path)
|
.map(|expr| UnqualifiedName::from_expr(expr).map(|name| name.to_string()))
|
||||||
.collect::<Option<Vec<String>>>()
|
.collect::<Option<Vec<String>>>()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -73,8 +73,10 @@ pub(crate) fn no_slots_in_namedtuple_subclass(
|
||||||
};
|
};
|
||||||
checker
|
checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|call_path| matches!(call_path.segments(), ["collections", "namedtuple"]))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["collections", "namedtuple"])
|
||||||
|
})
|
||||||
}) {
|
}) {
|
||||||
if !has_slots(&class.body) {
|
if !has_slots(&class.body) {
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
|
|
@ -69,8 +69,8 @@ pub(crate) fn no_slots_in_str_subclass(checker: &mut Checker, stmt: &Stmt, class
|
||||||
fn is_str_subclass(bases: &[Expr], semantic: &SemanticModel) -> bool {
|
fn is_str_subclass(bases: &[Expr], semantic: &SemanticModel) -> bool {
|
||||||
let mut is_str_subclass = false;
|
let mut is_str_subclass = false;
|
||||||
for base in bases {
|
for base in bases {
|
||||||
if let Some(call_path) = semantic.resolve_call_path(base) {
|
if let Some(qualified_name) = semantic.resolve_qualified_name(base) {
|
||||||
match call_path.segments() {
|
match qualified_name.segments() {
|
||||||
["" | "builtins", "str"] => {
|
["" | "builtins", "str"] => {
|
||||||
is_str_subclass = true;
|
is_str_subclass = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,12 +58,12 @@ pub(crate) fn no_slots_in_tuple_subclass(checker: &mut Checker, stmt: &Stmt, cla
|
||||||
if bases.iter().any(|base| {
|
if bases.iter().any(|base| {
|
||||||
checker
|
checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(map_subscript(base))
|
.resolve_qualified_name(map_subscript(base))
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|qualified_name| {
|
||||||
matches!(call_path.segments(), ["" | "builtins", "tuple"])
|
matches!(qualified_name.segments(), ["" | "builtins", "tuple"])
|
||||||
|| checker
|
|| checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.match_typing_call_path(&call_path, "Tuple")
|
.match_typing_qualified_name(&qualified_name, "Tuple")
|
||||||
})
|
})
|
||||||
}) {
|
}) {
|
||||||
if !has_slots(&class.body) {
|
if !has_slots(&class.body) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use ruff_python_ast::Expr;
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::name::QualifiedName;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -65,10 +65,10 @@ pub(crate) fn banned_attribute_access(checker: &mut Checker, expr: &Expr) {
|
||||||
if let Some((banned_path, ban)) =
|
if let Some((banned_path, ban)) =
|
||||||
checker
|
checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(expr)
|
.resolve_qualified_name(expr)
|
||||||
.and_then(|call_path| {
|
.and_then(|qualified_name| {
|
||||||
banned_api.iter().find(|(banned_path, ..)| {
|
banned_api.iter().find(|(banned_path, ..)| {
|
||||||
call_path == CallPath::from_qualified_name(banned_path)
|
qualified_name == QualifiedName::from_dotted_name(banned_path)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::name::QualifiedName;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub(super) enum MethodName {
|
pub(super) enum MethodName {
|
||||||
|
@ -70,8 +70,8 @@ impl MethodName {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MethodName {
|
impl MethodName {
|
||||||
pub(super) fn try_from(call_path: &CallPath<'_>) -> Option<Self> {
|
pub(super) fn try_from(qualified_name: &QualifiedName<'_>) -> Option<Self> {
|
||||||
match call_path.segments() {
|
match qualified_name.segments() {
|
||||||
["trio", "CancelScope"] => Some(Self::CancelScope),
|
["trio", "CancelScope"] => Some(Self::CancelScope),
|
||||||
["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully),
|
["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully),
|
||||||
["trio", "fail_after"] => Some(Self::FailAfter),
|
["trio", "fail_after"] => Some(Self::FailAfter),
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue