Skip to content

Commit

Permalink
Rename call_path to qualified_name
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Mar 4, 2024
1 parent e71b7a2 commit 71dd02a
Show file tree
Hide file tree
Showing 172 changed files with 823 additions and 689 deletions.
10 changes: 5 additions & 5 deletions crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,27 +1071,27 @@ impl<'a> Visitor<'a> for Checker<'a> {
.and_then(|qualified_name| {
if self
.semantic
.match_typing_call_path(&qualified_name, "cast")
.match_typing_qualified_name(&qualified_name, "cast")
{
Some(typing::Callable::Cast)
} else if self
.semantic
.match_typing_call_path(&qualified_name, "NewType")
.match_typing_qualified_name(&qualified_name, "NewType")
{
Some(typing::Callable::NewType)
} else if self
.semantic
.match_typing_call_path(&qualified_name, "TypeVar")
.match_typing_qualified_name(&qualified_name, "TypeVar")
{
Some(typing::Callable::TypeVar)
} else if self
.semantic
.match_typing_call_path(&qualified_name, "NamedTuple")
.match_typing_qualified_name(&qualified_name, "NamedTuple")
{
Some(typing::Callable::NamedTuple)
} else if self
.semantic
.match_typing_call_path(&qualified_name, "TypedDict")
.match_typing_qualified_name(&qualified_name, "TypedDict")
{
Some(typing::Callable::TypedDict)
} else if matches!(
Expand Down
39 changes: 1 addition & 38 deletions crates/ruff_linter/src/cst/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,7 @@
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.
pub(crate) fn space() -> ParenthesizableWhitespace<'static> {
ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" "))
Expand Down
47 changes: 43 additions & 4 deletions crates/ruff_linter/src/fix/codemods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
//! and return the modified code snippet as output.
use anyhow::{bail, Result};
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_codegen::Stylist;
use ruff_source_file::Locator;

use crate::cst::helpers::compose_module_path;
use crate::cst::matchers::match_statement;

/// Glue code to make libcst codegen work with ruff's Stylist
Expand Down Expand Up @@ -78,7 +80,7 @@ pub(crate) fn remove_imports<'a>(
for member in member_names {
let alias_index = aliases
.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 {
aliases.remove(index);
}
Expand Down Expand Up @@ -142,7 +144,7 @@ pub(crate) fn retain_imports(
aliases.retain(|alias| {
member_names
.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.
Expand All @@ -164,3 +166,40 @@ pub(crate) fn retain_imports(

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}"))
}
}
}
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/rules/flake8_2020/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ use ruff_python_semantic::SemanticModel;
pub(super) fn is_sys(expr: &Expr, target: &str, semantic: &SemanticModel) -> bool {
semantic
.resolve_qualified_name(expr)
.is_some_and(|call_path| call_path.segments() == ["sys", target])
.is_some_and(|qualified_name| qualified_name.segments() == ["sys", target])
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
if checker
.semantic()
.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
.diagnostics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ impl Violation for BlockingHttpCallInAsyncFunction {
}
}

fn is_blocking_http_call(call_path: &QualifiedName) -> bool {
fn is_blocking_http_call(qualified_name: &QualifiedName) -> bool {
matches!(
call_path.segments(),
qualified_name.segments(),
["urllib", "request", "urlopen"]
| [
"httpx" | "requests",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) {
}
}

fn is_unsafe_os_method(call_path: &QualifiedName) -> bool {
fn is_unsafe_os_method(qualified_name: &QualifiedName) -> bool {
matches!(
call_path.segments(),
qualified_name.segments(),
[
"os",
"popen"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ast::E
fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool {
semantic
.resolve_qualified_name(func)
.is_some_and(|call_path| {
.is_some_and(|qualified_name| {
matches!(
call_path.segments(),
qualified_name.segments(),
["", "open"]
| ["time", "sleep"]
| [
Expand Down Expand Up @@ -96,10 +96,10 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
// Path("foo").open()
// ```
if let Expr::Call(call) = value.as_ref() {
let Some(call_path) = semantic.resolve_qualified_name(call.func.as_ref()) else {
let Some(qualified_name) = semantic.resolve_qualified_name(call.func.as_ref()) else {
return false;
};
if call_path.segments() == ["pathlib", "Path"] {
if qualified_name.segments() == ["pathlib", "Path"] {
return true;
}
}
Expand All @@ -126,5 +126,5 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {

semantic
.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"])
}
14 changes: 10 additions & 4 deletions crates/ruff_linter/src/rules/flake8_bandit/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,21 @@ pub(super) fn is_untyped_exception(type_: Option<&Expr>, semantic: &SemanticMode
elts.iter().any(|type_| {
semantic
.resolve_qualified_name(type_)
.is_some_and(|call_path| {
matches!(call_path.segments(), ["", "Exception" | "BaseException"])
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["", "Exception" | "BaseException"]
)
})
})
} else {
semantic
.resolve_qualified_name(type_)
.is_some_and(|call_path| {
matches!(call_path.segments(), ["", "Exception" | "BaseException"])
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["", "Exception" | "BaseException"]
)
})
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
if checker
.semantic()
.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) {
match parse_mask(mode_arg, checker.semantic()) {
Expand Down Expand Up @@ -101,8 +101,8 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
const WRITE_WORLD: u16 = 0o2;
const EXECUTE_GROUP: u16 = 0o10;

fn py_stat(call_path: &QualifiedName) -> Option<u16> {
match call_path.segments() {
fn py_stat(qualified_name: &QualifiedName) -> Option<u16> {
match qualified_name.segments() {
["stat", "ST_MODE"] => Some(0o0),
["stat", "S_IFDOOR"] => Some(0o0),
["stat", "S_IFPORT"] => Some(0o0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ pub(crate) fn django_raw_sql(checker: &mut Checker, call: &ast::ExprCall) {
if checker
.semantic()
.resolve_qualified_name(&call.func)
.is_some_and(|call_path| {
.is_some_and(|qualified_name| {
matches!(
call_path.segments(),
qualified_name.segments(),
["django", "db", "models", "expressions", "RawSQL"]
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub(crate) fn exec_used(checker: &mut Checker, func: &Expr) {
if checker
.semantic()
.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
.diagnostics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub(crate) fn flask_debug_true(checker: &mut Checker, call: &ExprCall) {
}

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
.diagnostics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike)
if checker
.semantic()
.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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub(crate) fn hashlib_insecure_hash_functions(checker: &mut Checker, call: &ast:
if let Some(hashlib_call) = checker
.semantic()
.resolve_qualified_name(&call.func)
.and_then(|call_path| match call_path.segments() {
.and_then(|qualified_name| match qualified_name.segments() {
["hashlib", "new"] => Some(HashlibCall::New),
["hashlib", "md4"] => Some(HashlibCall::WeakHash("md4")),
["hashlib", "md5"] => Some(HashlibCall::WeakHash("md5")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCal
if checker
.semantic()
.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") {
match &keyword.value {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ pub(crate) fn logging_config_insecure_listen(checker: &mut Checker, call: &ast::
if checker
.semantic()
.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() {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ pub(crate) fn mako_templates(checker: &mut Checker, call: &ast::ExprCall) {
if checker
.semantic()
.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
.diagnostics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ pub(crate) fn paramiko_call(checker: &mut Checker, func: &Expr) {
if checker
.semantic()
.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
.diagnostics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub(crate) fn request_with_no_cert_validation(checker: &mut Checker, call: &ast:
if let Some(target) = checker
.semantic()
.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"] => {
Some("requests")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCal
if checker
.semantic()
.resolve_qualified_name(&call.func)
.is_some_and(|call_path| {
.is_some_and(|qualified_name| {
matches!(
call_path.segments(),
qualified_name.segments(),
[
"requests",
"get" | "options" | "head" | "post" | "put" | "patch" | "delete"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ enum CallKind {
fn get_call_kind(func: &Expr, semantic: &SemanticModel) -> Option<CallKind> {
semantic
.resolve_qualified_name(func)
.and_then(|call_path| match call_path.segments() {
.and_then(|qualified_name| match qualified_name.segments() {
&[module, submodule] => match module {
"os" => match submodule {
"execl" | "execle" | "execlp" | "execlpe" | "execv" | "execve" | "execvp"
Expand Down
Loading

0 comments on commit 71dd02a

Please sign in to comment.