mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
Flag, but don't fix, unused imports in ModuleNotFoundError
blocks (#3658)
This commit is contained in:
parent
3a8e98341b
commit
1b3e54231c
8 changed files with 163 additions and 89 deletions
|
@ -1,10 +1,19 @@
|
||||||
"""Test: imports within `ModuleNotFoundError` handlers."""
|
"""Test: imports within `ModuleNotFoundError` and `ImportError` handlers."""
|
||||||
|
|
||||||
|
|
||||||
def check_orjson():
|
def module_not_found_error():
|
||||||
try:
|
try:
|
||||||
import orjson
|
import orjson
|
||||||
|
|
||||||
return True
|
return True
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def import_error():
|
||||||
|
try:
|
||||||
|
import orjson
|
||||||
|
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
|
@ -14,13 +14,11 @@ use rustpython_parser::ast::{
|
||||||
|
|
||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_python_ast::context::Context;
|
use ruff_python_ast::context::Context;
|
||||||
use ruff_python_ast::helpers::{
|
use ruff_python_ast::helpers::{binding_range, extract_handled_exceptions, to_module_path};
|
||||||
binding_range, extract_handled_exceptions, to_module_path, Exceptions,
|
|
||||||
};
|
|
||||||
use ruff_python_ast::operations::{extract_all_names, AllNamesFlags};
|
use ruff_python_ast::operations::{extract_all_names, AllNamesFlags};
|
||||||
use ruff_python_ast::scope::{
|
use ruff_python_ast::scope::{
|
||||||
Binding, BindingId, BindingKind, ClassDef, ExecutionContext, FunctionDef, Lambda, Scope,
|
Binding, BindingId, BindingKind, ClassDef, Exceptions, ExecutionContext, FunctionDef, Lambda,
|
||||||
ScopeId, ScopeKind, ScopeStack,
|
Scope, ScopeId, ScopeKind, ScopeStack,
|
||||||
};
|
};
|
||||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||||
use ruff_python_ast::types::{Node, Range, RefEquality};
|
use ruff_python_ast::types::{Node, Range, RefEquality};
|
||||||
|
@ -198,6 +196,7 @@ where
|
||||||
if !scope_index.is_global() {
|
if !scope_index.is_global() {
|
||||||
// Add the binding to the current scope.
|
// Add the binding to the current scope.
|
||||||
let context = self.ctx.execution_context();
|
let context = self.ctx.execution_context();
|
||||||
|
let exceptions = self.ctx.exceptions();
|
||||||
let scope = &mut self.ctx.scopes[scope_index];
|
let scope = &mut self.ctx.scopes[scope_index];
|
||||||
let usage = Some((scope.id, Range::from(stmt)));
|
let usage = Some((scope.id, Range::from(stmt)));
|
||||||
for (name, range) in names.iter().zip(ranges.iter()) {
|
for (name, range) in names.iter().zip(ranges.iter()) {
|
||||||
|
@ -209,6 +208,7 @@ where
|
||||||
range: *range,
|
range: *range,
|
||||||
source: Some(RefEquality(stmt)),
|
source: Some(RefEquality(stmt)),
|
||||||
context,
|
context,
|
||||||
|
exceptions,
|
||||||
});
|
});
|
||||||
scope.add(name, id);
|
scope.add(name, id);
|
||||||
}
|
}
|
||||||
|
@ -226,6 +226,7 @@ where
|
||||||
let ranges: Vec<Range> = helpers::find_names(stmt, self.locator).collect();
|
let ranges: Vec<Range> = helpers::find_names(stmt, self.locator).collect();
|
||||||
if !scope_index.is_global() {
|
if !scope_index.is_global() {
|
||||||
let context = self.ctx.execution_context();
|
let context = self.ctx.execution_context();
|
||||||
|
let exceptions = self.ctx.exceptions();
|
||||||
let scope = &mut self.ctx.scopes[scope_index];
|
let scope = &mut self.ctx.scopes[scope_index];
|
||||||
let usage = Some((scope.id, Range::from(stmt)));
|
let usage = Some((scope.id, Range::from(stmt)));
|
||||||
for (name, range) in names.iter().zip(ranges.iter()) {
|
for (name, range) in names.iter().zip(ranges.iter()) {
|
||||||
|
@ -238,6 +239,7 @@ where
|
||||||
range: *range,
|
range: *range,
|
||||||
source: Some(RefEquality(stmt)),
|
source: Some(RefEquality(stmt)),
|
||||||
context,
|
context,
|
||||||
|
exceptions,
|
||||||
});
|
});
|
||||||
scope.add(name, id);
|
scope.add(name, id);
|
||||||
}
|
}
|
||||||
|
@ -639,7 +641,6 @@ where
|
||||||
self.visit_expr(expr);
|
self.visit_expr(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = self.ctx.execution_context();
|
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
name,
|
name,
|
||||||
Binding {
|
Binding {
|
||||||
|
@ -649,7 +650,8 @@ where
|
||||||
typing_usage: None,
|
typing_usage: None,
|
||||||
range: Range::from(stmt),
|
range: Range::from(stmt),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context,
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -834,14 +836,6 @@ where
|
||||||
pyupgrade::rules::deprecated_mock_import(self, stmt);
|
pyupgrade::rules::deprecated_mock_import(self, stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a module is imported within a `ModuleNotFoundError` body, treat that as a
|
|
||||||
// synthetic usage.
|
|
||||||
let is_handled = self
|
|
||||||
.ctx
|
|
||||||
.handled_exceptions
|
|
||||||
.iter()
|
|
||||||
.any(|exceptions| exceptions.contains(Exceptions::MODULE_NOT_FOUND_ERROR));
|
|
||||||
|
|
||||||
for alias in names {
|
for alias in names {
|
||||||
if alias.node.name == "__future__" {
|
if alias.node.name == "__future__" {
|
||||||
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||||
|
@ -856,6 +850,7 @@ where
|
||||||
range: Range::from(alias),
|
range: Range::from(alias),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -877,15 +872,12 @@ where
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::SubmoduleImportation(name, full_name),
|
kind: BindingKind::SubmoduleImportation(name, full_name),
|
||||||
runtime_usage: None,
|
runtime_usage: None,
|
||||||
synthetic_usage: if is_handled {
|
synthetic_usage: None,
|
||||||
Some((self.ctx.scope_id(), Range::from(alias)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
typing_usage: None,
|
typing_usage: None,
|
||||||
range: Range::from(alias),
|
range: Range::from(alias),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -907,7 +899,7 @@ where
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::Importation(name, full_name),
|
kind: BindingKind::Importation(name, full_name),
|
||||||
runtime_usage: None,
|
runtime_usage: None,
|
||||||
synthetic_usage: if is_handled || is_explicit_reexport {
|
synthetic_usage: if is_explicit_reexport {
|
||||||
Some((self.ctx.scope_id(), Range::from(alias)))
|
Some((self.ctx.scope_id(), Range::from(alias)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -916,6 +908,7 @@ where
|
||||||
range: Range::from(alias),
|
range: Range::from(alias),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1160,14 +1153,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a module is imported within a `ModuleNotFoundError` body, treat that as a
|
|
||||||
// synthetic usage.
|
|
||||||
let is_handled = self
|
|
||||||
.ctx
|
|
||||||
.handled_exceptions
|
|
||||||
.iter()
|
|
||||||
.any(|exceptions| exceptions.contains(Exceptions::MODULE_NOT_FOUND_ERROR));
|
|
||||||
|
|
||||||
for alias in names {
|
for alias in names {
|
||||||
if let Some("__future__") = module.as_deref() {
|
if let Some("__future__") = module.as_deref() {
|
||||||
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||||
|
@ -1182,6 +1167,7 @@ where
|
||||||
range: Range::from(alias),
|
range: Range::from(alias),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1207,15 +1193,12 @@ where
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::StarImportation(*level, module.clone()),
|
kind: BindingKind::StarImportation(*level, module.clone()),
|
||||||
runtime_usage: None,
|
runtime_usage: None,
|
||||||
synthetic_usage: if is_handled {
|
synthetic_usage: None,
|
||||||
Some((self.ctx.scope_id(), Range::from(alias)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
typing_usage: None,
|
typing_usage: None,
|
||||||
range: Range::from(stmt),
|
range: Range::from(stmt),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1283,16 +1266,16 @@ where
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::FromImportation(name, full_name),
|
kind: BindingKind::FromImportation(name, full_name),
|
||||||
runtime_usage: None,
|
runtime_usage: None,
|
||||||
synthetic_usage: if is_handled || is_explicit_reexport {
|
synthetic_usage: if is_explicit_reexport {
|
||||||
Some((self.ctx.scope_id(), Range::from(alias)))
|
Some((self.ctx.scope_id(), Range::from(alias)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
typing_usage: None,
|
typing_usage: None,
|
||||||
range: Range::from(alias),
|
range: Range::from(alias),
|
||||||
|
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1914,6 +1897,7 @@ where
|
||||||
range: Range::from(stmt),
|
range: Range::from(stmt),
|
||||||
source: Some(RefEquality(stmt)),
|
source: Some(RefEquality(stmt)),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
});
|
});
|
||||||
self.ctx.global_scope_mut().add(name, id);
|
self.ctx.global_scope_mut().add(name, id);
|
||||||
}
|
}
|
||||||
|
@ -1976,6 +1960,7 @@ where
|
||||||
range: Range::from(*stmt),
|
range: Range::from(*stmt),
|
||||||
source: Some(RefEquality(stmt)),
|
source: Some(RefEquality(stmt)),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
});
|
});
|
||||||
self.ctx.global_scope_mut().add(name, id);
|
self.ctx.global_scope_mut().add(name, id);
|
||||||
}
|
}
|
||||||
|
@ -2125,6 +2110,7 @@ where
|
||||||
range: Range::from(stmt),
|
range: Range::from(stmt),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3906,6 +3892,7 @@ where
|
||||||
range: Range::from(arg),
|
range: Range::from(arg),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3949,6 +3936,7 @@ where
|
||||||
range: Range::from(pattern),
|
range: Range::from(pattern),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4127,6 +4115,7 @@ impl<'a> Checker<'a> {
|
||||||
typing_usage: None,
|
typing_usage: None,
|
||||||
source: None,
|
source: None,
|
||||||
context: ExecutionContext::Runtime,
|
context: ExecutionContext::Runtime,
|
||||||
|
exceptions: Exceptions::empty(),
|
||||||
});
|
});
|
||||||
scope.add(builtin, id);
|
scope.add(builtin, id);
|
||||||
}
|
}
|
||||||
|
@ -4351,6 +4340,7 @@ impl<'a> Checker<'a> {
|
||||||
range: Range::from(expr),
|
range: Range::from(expr),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -4371,6 +4361,7 @@ impl<'a> Checker<'a> {
|
||||||
range: Range::from(expr),
|
range: Range::from(expr),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -4387,6 +4378,7 @@ impl<'a> Checker<'a> {
|
||||||
range: Range::from(expr),
|
range: Range::from(expr),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -4452,6 +4444,7 @@ impl<'a> Checker<'a> {
|
||||||
range: Range::from(expr),
|
range: Range::from(expr),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -4468,6 +4461,7 @@ impl<'a> Checker<'a> {
|
||||||
range: Range::from(expr),
|
range: Range::from(expr),
|
||||||
source: Some(self.ctx.current_stmt().clone()),
|
source: Some(self.ctx.current_stmt().clone()),
|
||||||
context: self.ctx.execution_context(),
|
context: self.ctx.execution_context(),
|
||||||
|
exceptions: self.ctx.exceptions(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4925,8 +4919,11 @@ impl<'a> Checker<'a> {
|
||||||
// Collect all unused imports by location. (Multiple unused imports at the same
|
// Collect all unused imports by location. (Multiple unused imports at the same
|
||||||
// location indicates an `import from`.)
|
// location indicates an `import from`.)
|
||||||
type UnusedImport<'a> = (&'a str, &'a Range);
|
type UnusedImport<'a> = (&'a str, &'a Range);
|
||||||
type BindingContext<'a, 'b> =
|
type BindingContext<'a, 'b> = (
|
||||||
(&'a RefEquality<'b, Stmt>, Option<&'a RefEquality<'b, Stmt>>);
|
&'a RefEquality<'b, Stmt>,
|
||||||
|
Option<&'a RefEquality<'b, Stmt>>,
|
||||||
|
Exceptions,
|
||||||
|
);
|
||||||
|
|
||||||
let mut unused: FxHashMap<BindingContext, Vec<UnusedImport>> = FxHashMap::default();
|
let mut unused: FxHashMap<BindingContext, Vec<UnusedImport>> = FxHashMap::default();
|
||||||
let mut ignored: FxHashMap<BindingContext, Vec<UnusedImport>> =
|
let mut ignored: FxHashMap<BindingContext, Vec<UnusedImport>> =
|
||||||
|
@ -4948,6 +4945,7 @@ impl<'a> Checker<'a> {
|
||||||
|
|
||||||
let defined_by = binding.source.as_ref().unwrap();
|
let defined_by = binding.source.as_ref().unwrap();
|
||||||
let defined_in = self.ctx.child_to_parent.get(defined_by);
|
let defined_in = self.ctx.child_to_parent.get(defined_by);
|
||||||
|
let exceptions = binding.exceptions;
|
||||||
let child: &Stmt = defined_by.into();
|
let child: &Stmt = defined_by.into();
|
||||||
|
|
||||||
let diagnostic_lineno = binding.range.location.row();
|
let diagnostic_lineno = binding.range.location.row();
|
||||||
|
@ -4965,27 +4963,30 @@ impl<'a> Checker<'a> {
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
ignored
|
ignored
|
||||||
.entry((defined_by, defined_in))
|
.entry((defined_by, defined_in, exceptions))
|
||||||
.or_default()
|
.or_default()
|
||||||
.push((full_name, &binding.range));
|
.push((full_name, &binding.range));
|
||||||
} else {
|
} else {
|
||||||
unused
|
unused
|
||||||
.entry((defined_by, defined_in))
|
.entry((defined_by, defined_in, exceptions))
|
||||||
.or_default()
|
.or_default()
|
||||||
.push((full_name, &binding.range));
|
.push((full_name, &binding.range));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ignore_init =
|
let in_init =
|
||||||
self.settings.ignore_init_module_imports && self.path.ends_with("__init__.py");
|
self.settings.ignore_init_module_imports && self.path.ends_with("__init__.py");
|
||||||
for ((defined_by, defined_in), unused_imports) in unused
|
for ((defined_by, defined_in, exceptions), unused_imports) in unused
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.sorted_by_key(|((defined_by, _), _)| defined_by.location)
|
.sorted_by_key(|((defined_by, ..), ..)| defined_by.location)
|
||||||
{
|
{
|
||||||
let child: &Stmt = defined_by.into();
|
let child: &Stmt = defined_by.into();
|
||||||
let parent: Option<&Stmt> = defined_in.map(Into::into);
|
let parent: Option<&Stmt> = defined_in.map(Into::into);
|
||||||
|
let multiple = unused_imports.len() > 1;
|
||||||
|
let in_except_handler = exceptions
|
||||||
|
.intersects(Exceptions::MODULE_NOT_FOUND_ERROR | Exceptions::IMPORT_ERROR);
|
||||||
|
|
||||||
let fix = if !ignore_init && self.patch(Rule::UnusedImport) {
|
let fix = if !in_init && !in_except_handler && self.patch(Rule::UnusedImport) {
|
||||||
let deleted: Vec<&Stmt> = self.deletions.iter().map(Into::into).collect();
|
let deleted: Vec<&Stmt> = self.deletions.iter().map(Into::into).collect();
|
||||||
match autofix::helpers::remove_unused_imports(
|
match autofix::helpers::remove_unused_imports(
|
||||||
unused_imports.iter().map(|(full_name, _)| *full_name),
|
unused_imports.iter().map(|(full_name, _)| *full_name),
|
||||||
|
@ -5011,12 +5012,17 @@ impl<'a> Checker<'a> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let multiple = unused_imports.len() > 1;
|
|
||||||
for (full_name, range) in unused_imports {
|
for (full_name, range) in unused_imports {
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
pyflakes::rules::UnusedImport {
|
pyflakes::rules::UnusedImport {
|
||||||
name: full_name.to_string(),
|
name: full_name.to_string(),
|
||||||
ignore_init,
|
context: if in_except_handler {
|
||||||
|
Some(pyflakes::rules::UnusedImportContext::ExceptHandler)
|
||||||
|
} else if in_init {
|
||||||
|
Some(pyflakes::rules::UnusedImportContext::Init)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
multiple,
|
multiple,
|
||||||
},
|
},
|
||||||
*range,
|
*range,
|
||||||
|
@ -5032,17 +5038,25 @@ impl<'a> Checker<'a> {
|
||||||
diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for ((defined_by, ..), unused_imports) in ignored
|
for ((defined_by, .., exceptions), unused_imports) in ignored
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.sorted_by_key(|((defined_by, _), _)| defined_by.location)
|
.sorted_by_key(|((defined_by, ..), ..)| defined_by.location)
|
||||||
{
|
{
|
||||||
let child: &Stmt = defined_by.into();
|
let child: &Stmt = defined_by.into();
|
||||||
let multiple = unused_imports.len() > 1;
|
let multiple = unused_imports.len() > 1;
|
||||||
|
let in_except_handler = exceptions
|
||||||
|
.intersects(Exceptions::MODULE_NOT_FOUND_ERROR | Exceptions::IMPORT_ERROR);
|
||||||
for (full_name, range) in unused_imports {
|
for (full_name, range) in unused_imports {
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
pyflakes::rules::UnusedImport {
|
pyflakes::rules::UnusedImport {
|
||||||
name: full_name.to_string(),
|
name: full_name.to_string(),
|
||||||
ignore_init,
|
context: if in_except_handler {
|
||||||
|
Some(pyflakes::rules::UnusedImportContext::ExceptHandler)
|
||||||
|
} else if in_init {
|
||||||
|
Some(pyflakes::rules::UnusedImportContext::Init)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
multiple,
|
multiple,
|
||||||
},
|
},
|
||||||
*range,
|
*range,
|
||||||
|
|
|
@ -9,46 +9,52 @@ use ruff_python_stdlib::future::ALL_FEATURE_NAMES;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum UnusedImportContext {
|
||||||
|
ExceptHandler,
|
||||||
|
Init,
|
||||||
|
}
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct UnusedImport {
|
pub struct UnusedImport {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub ignore_init: bool,
|
pub context: Option<UnusedImportContext>,
|
||||||
pub multiple: bool,
|
pub multiple: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_unused_import_autofix_msg(unused_import: &UnusedImport) -> String {
|
|
||||||
let UnusedImport { name, multiple, .. } = unused_import;
|
|
||||||
if *multiple {
|
|
||||||
"Remove unused import".to_string()
|
|
||||||
} else {
|
|
||||||
format!("Remove unused import: `{name}`")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Violation for UnusedImport {
|
impl Violation for UnusedImport {
|
||||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||||
|
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let UnusedImport {
|
let UnusedImport { name, context, .. } = self;
|
||||||
name, ignore_init, ..
|
match context {
|
||||||
} = self;
|
Some(UnusedImportContext::ExceptHandler) => {
|
||||||
if *ignore_init {
|
format!(
|
||||||
format!(
|
"`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability"
|
||||||
"`{name}` imported but unused; consider adding to `__all__` or using a redundant \
|
)
|
||||||
alias"
|
}
|
||||||
)
|
Some(UnusedImportContext::Init) => {
|
||||||
} else {
|
format!(
|
||||||
format!("`{name}` imported but unused")
|
"`{name}` imported but unused; consider adding to `__all__` or using a redundant \
|
||||||
|
alias"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => format!("`{name}` imported but unused"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||||
let UnusedImport { ignore_init, .. } = self;
|
let UnusedImport { context, .. } = self;
|
||||||
if *ignore_init {
|
context
|
||||||
None
|
.is_none()
|
||||||
} else {
|
.then_some(|UnusedImport { name, multiple, .. }| {
|
||||||
Some(fmt_unused_import_autofix_msg)
|
if *multiple {
|
||||||
}
|
"Remove unused import".to_string()
|
||||||
|
} else {
|
||||||
|
format!("Remove unused import: `{name}`")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[violation]
|
#[violation]
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub use if_tuple::{if_tuple, IfTuple};
|
||||||
pub use imports::{
|
pub use imports::{
|
||||||
future_feature_not_defined, FutureFeatureNotDefined, ImportShadowedByLoopVar, LateFutureImport,
|
future_feature_not_defined, FutureFeatureNotDefined, ImportShadowedByLoopVar, LateFutureImport,
|
||||||
UndefinedLocalWithImportStar, UndefinedLocalWithImportStarUsage,
|
UndefinedLocalWithImportStar, UndefinedLocalWithImportStarUsage,
|
||||||
UndefinedLocalWithNestedImportStarUsage, UnusedImport,
|
UndefinedLocalWithNestedImportStarUsage, UnusedImport, UnusedImportContext,
|
||||||
};
|
};
|
||||||
pub use invalid_literal_comparisons::{invalid_literal_comparison, IsLiteral};
|
pub use invalid_literal_comparisons::{invalid_literal_comparison, IsLiteral};
|
||||||
pub use invalid_print_syntax::{invalid_print_syntax, InvalidPrintSyntax};
|
pub use invalid_print_syntax::{invalid_print_syntax, InvalidPrintSyntax};
|
||||||
|
|
|
@ -2,5 +2,37 @@
|
||||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||||
expression: diagnostics
|
expression: diagnostics
|
||||||
---
|
---
|
||||||
[]
|
- kind:
|
||||||
|
name: UnusedImport
|
||||||
|
body: "`orjson` imported but unused; consider using `importlib.util.find_spec` to test for availability"
|
||||||
|
suggestion: ~
|
||||||
|
fixable: false
|
||||||
|
location:
|
||||||
|
row: 6
|
||||||
|
column: 15
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 21
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
name: UnusedImport
|
||||||
|
body: "`orjson` imported but unused"
|
||||||
|
suggestion: "Remove unused import: `orjson`"
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 15
|
||||||
|
column: 15
|
||||||
|
end_location:
|
||||||
|
row: 15
|
||||||
|
column: 21
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 15
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 16
|
||||||
|
column: 0
|
||||||
|
parent: ~
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@ use smallvec::smallvec;
|
||||||
use ruff_python_stdlib::path::is_python_stub_file;
|
use ruff_python_stdlib::path::is_python_stub_file;
|
||||||
use ruff_python_stdlib::typing::TYPING_EXTENSIONS;
|
use ruff_python_stdlib::typing::TYPING_EXTENSIONS;
|
||||||
|
|
||||||
use crate::helpers::{collect_call_path, from_relative_import, Exceptions};
|
use crate::helpers::{collect_call_path, from_relative_import};
|
||||||
use crate::scope::{
|
use crate::scope::{
|
||||||
Binding, BindingId, BindingKind, Bindings, ExecutionContext, Scope, ScopeId, ScopeKind,
|
Binding, BindingId, BindingKind, Bindings, Exceptions, ExecutionContext, Scope, ScopeId,
|
||||||
ScopeStack, Scopes,
|
ScopeKind, ScopeStack, Scopes,
|
||||||
};
|
};
|
||||||
use crate::types::{CallPath, RefEquality};
|
use crate::types::{CallPath, RefEquality};
|
||||||
use crate::typing::AnnotationKind;
|
use crate::typing::AnnotationKind;
|
||||||
|
@ -309,6 +309,7 @@ impl<'a> Context<'a> {
|
||||||
self.in_exception_handler
|
self.in_exception_handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the [`ExecutionContext`] of the current scope.
|
||||||
pub const fn execution_context(&self) -> ExecutionContext {
|
pub const fn execution_context(&self) -> ExecutionContext {
|
||||||
if self.in_type_checking_block
|
if self.in_type_checking_block
|
||||||
|| self.in_annotation
|
|| self.in_annotation
|
||||||
|
@ -319,4 +320,13 @@ impl<'a> Context<'a> {
|
||||||
ExecutionContext::Runtime
|
ExecutionContext::Runtime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the union of all handled exceptions as an [`Exceptions`] bitflag.
|
||||||
|
pub fn exceptions(&self) -> Exceptions {
|
||||||
|
let mut exceptions = Exceptions::empty();
|
||||||
|
for exception in &self.handled_exceptions {
|
||||||
|
exceptions.insert(*exception);
|
||||||
|
}
|
||||||
|
exceptions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::error;
|
use log::error;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
@ -577,13 +576,6 @@ pub fn has_non_none_keyword(keywords: &[Keyword], keyword: &str) -> bool {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
pub struct Exceptions: u32 {
|
|
||||||
const NAME_ERROR = 0b0000_0001;
|
|
||||||
const MODULE_NOT_FOUND_ERROR = 0b0000_0010;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract the names of all handled exceptions.
|
/// Extract the names of all handled exceptions.
|
||||||
pub fn extract_handled_exceptions(handlers: &[Excepthandler]) -> Vec<&Expr> {
|
pub fn extract_handled_exceptions(handlers: &[Excepthandler]) -> Vec<&Expr> {
|
||||||
let mut handled_exceptions = Vec::new();
|
let mut handled_exceptions = Vec::new();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::types::{Range, RefEquality};
|
use crate::types::{Range, RefEquality};
|
||||||
|
use bitflags::bitflags;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use rustpython_parser::ast::{Arguments, Expr, Keyword, Stmt};
|
use rustpython_parser::ast::{Arguments, Expr, Keyword, Stmt};
|
||||||
use std::num::TryFromIntError;
|
use std::num::TryFromIntError;
|
||||||
|
@ -222,6 +223,14 @@ impl Default for ScopeStack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
pub struct Exceptions: u32 {
|
||||||
|
const NAME_ERROR = 0b0000_0001;
|
||||||
|
const MODULE_NOT_FOUND_ERROR = 0b0000_0010;
|
||||||
|
const IMPORT_ERROR = 0b0000_0100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Binding<'a> {
|
pub struct Binding<'a> {
|
||||||
pub kind: BindingKind<'a>,
|
pub kind: BindingKind<'a>,
|
||||||
|
@ -241,6 +250,8 @@ pub struct Binding<'a> {
|
||||||
/// (e.g.) `__future__` imports, explicit re-exports, and other bindings
|
/// (e.g.) `__future__` imports, explicit re-exports, and other bindings
|
||||||
/// that should be considered used even if they're never referenced.
|
/// that should be considered used even if they're never referenced.
|
||||||
pub synthetic_usage: Option<(ScopeId, Range)>,
|
pub synthetic_usage: Option<(ScopeId, Range)>,
|
||||||
|
/// The exceptions that were handled when the binding was defined.
|
||||||
|
pub exceptions: Exceptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Binding<'a> {
|
impl<'a> Binding<'a> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue