Compare commits

...

3 Commits

Author SHA1 Message Date
Dhruv Manilawala
a3b4adab3f Update w.r.t latest API changes 2023-07-13 20:55:15 +05:30
Dhruv Manilawala
4110065a18 Merge branch 'main' into dhruv/unused-imports 2023-07-13 20:45:18 +05:30
Dhruv Manilawala
059ba0c59c Consider submodule imports to detect unused imports 2023-06-11 17:02:05 +05:30
8 changed files with 144 additions and 29 deletions

View File

@@ -0,0 +1,6 @@
"""Test: submodule import access."""
import logging.config
import logging.handlers
logging.config.fileConfig("logging.conf")

View File

@@ -0,0 +1,9 @@
"""Testing that multiple submodule imports are handled correctly."""
# The logic goes through each of the shadowed bindings upwards to get all the unused
# imports. It should only detect imports, not any other kind of binding.
multiprocessing = None
import multiprocessing.connection
import multiprocessing.pool
import multiprocessing.queues

View File

@@ -44,6 +44,8 @@ mod tests {
#[test_case(Rule::UnusedImport, Path::new("F401_16.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_17.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_18.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_19.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_20.py"))]
#[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.py"))]
#[test_case(Rule::UndefinedLocalWithImportStar, Path::new("F403.py"))]
#[test_case(Rule::LateFutureImport, Path::new("F404.py"))]

View File

@@ -101,44 +101,53 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
let mut ignored: FxHashMap<(NodeId, Exceptions), Vec<Import>> = FxHashMap::default();
for binding_id in scope.binding_ids() {
let binding = checker.semantic().binding(binding_id);
let top_binding = checker.semantic().binding(binding_id);
if binding.is_used()
|| binding.is_explicit_export()
|| binding.is_nonlocal()
|| binding.is_global()
if top_binding.is_used()
|| top_binding.is_explicit_export()
|| top_binding.is_nonlocal()
|| top_binding.is_global()
{
continue;
}
let Some(qualified_name) = binding.qualified_name() else {
let Some(binding_name) = scope.get_name(binding_id) else {
continue;
};
let Some(stmt_id) = binding.source else {
continue;
};
let import = Import {
qualified_name,
range: binding.range,
parent_range: binding.parent_range(checker.semantic()),
};
if checker.rule_is_ignored(Rule::UnusedImport, import.range.start())
|| import.parent_range.map_or(false, |parent_range| {
checker.rule_is_ignored(Rule::UnusedImport, parent_range.start())
})
for binding in scope
.get_all(binding_name)
.map(|binding_id| checker.semantic().binding(binding_id))
{
ignored
.entry((stmt_id, binding.exceptions))
.or_default()
.push(import);
} else {
unused
.entry((stmt_id, binding.exceptions))
.or_default()
.push(import);
let Some(qualified_name) = binding.qualified_name() else {
break;
};
let Some(stmt_id) = binding.source else {
continue;
};
let import = Import {
qualified_name,
range: binding.range,
parent_range: binding.parent_range(checker.semantic()),
};
if checker.rule_is_ignored(Rule::UnusedImport, import.range.start())
|| import.parent_range.map_or(false, |parent_range| {
checker.rule_is_ignored(Rule::UnusedImport, parent_range.start())
})
{
ignored
.entry((stmt_id, binding.exceptions))
.or_default()
.push(import);
} else {
unused
.entry((stmt_id, binding.exceptions))
.or_default()
.push(import);
}
}
}

View File

@@ -39,6 +39,26 @@ F401_0.py:6:5: F401 [*] `collections.OrderedDict` imported but unused
8 7 | )
9 8 | import multiprocessing.pool
F401_0.py:11:8: F401 [*] `logging.config` imported but unused
|
9 | import multiprocessing.pool
10 | import multiprocessing.process
11 | import logging.config
| ^^^^^^^^^^^^^^ F401
12 | import logging.handlers
13 | from typing import (
|
= help: Remove unused import: `logging.config`
Fix
8 8 | )
9 9 | import multiprocessing.pool
10 10 | import multiprocessing.process
11 |-import logging.config
12 11 | import logging.handlers
13 12 | from typing import (
14 13 | TYPE_CHECKING,
F401_0.py:12:8: F401 [*] `logging.handlers` imported but unused
|
10 | import multiprocessing.process

View File

@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
---

View File

@@ -0,0 +1,54 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
---
F401_20.py:7:8: F401 [*] `multiprocessing.connection` imported but unused
|
5 | multiprocessing = None
6 |
7 | import multiprocessing.connection
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ F401
8 | import multiprocessing.pool
9 | import multiprocessing.queues
|
= help: Remove unused import: `multiprocessing.connection`
Fix
4 4 | # imports. It should only detect imports, not any other kind of binding.
5 5 | multiprocessing = None
6 6 |
7 |-import multiprocessing.connection
8 7 | import multiprocessing.pool
9 8 | import multiprocessing.queues
F401_20.py:8:8: F401 [*] `multiprocessing.pool` imported but unused
|
7 | import multiprocessing.connection
8 | import multiprocessing.pool
| ^^^^^^^^^^^^^^^^^^^^ F401
9 | import multiprocessing.queues
|
= help: Remove unused import: `multiprocessing.pool`
Fix
5 5 | multiprocessing = None
6 6 |
7 7 | import multiprocessing.connection
8 |-import multiprocessing.pool
9 8 | import multiprocessing.queues
F401_20.py:9:8: F401 [*] `multiprocessing.queues` imported but unused
|
7 | import multiprocessing.connection
8 | import multiprocessing.pool
9 | import multiprocessing.queues
| ^^^^^^^^^^^^^^^^^^^^^^ F401
|
= help: Remove unused import: `multiprocessing.queues`
Fix
6 6 |
7 7 | import multiprocessing.connection
8 8 | import multiprocessing.pool
9 |-import multiprocessing.queues

View File

@@ -27,6 +27,9 @@ pub struct Scope<'a> {
/// A map from bound name to binding ID.
bindings: FxHashMap<&'a str, BindingId>,
/// A map from binding ID to bound name.
binding_name: FxHashMap<BindingId, &'a str>,
/// A map from binding ID to binding ID that it shadows.
///
/// For example:
@@ -53,6 +56,7 @@ impl<'a> Scope<'a> {
parent: None,
star_imports: Vec::default(),
bindings: FxHashMap::default(),
binding_name: FxHashMap::default(),
shadowed_bindings: IntMap::default(),
globals_id: None,
flags: ScopeFlags::empty(),
@@ -65,6 +69,7 @@ impl<'a> Scope<'a> {
parent: Some(parent),
star_imports: Vec::default(),
bindings: FxHashMap::default(),
binding_name: FxHashMap::default(),
shadowed_bindings: IntMap::default(),
globals_id: None,
flags: ScopeFlags::empty(),
@@ -76,8 +81,14 @@ impl<'a> Scope<'a> {
self.bindings.get(name).copied()
}
/// Returns the name bound to the given [id](BindingId).
pub fn get_name(&self, id: BindingId) -> Option<&'a str> {
self.binding_name.get(&id).copied()
}
/// Adds a new binding with the given name to this scope.
pub fn add(&mut self, name: &'a str, id: BindingId) -> Option<BindingId> {
self.binding_name.insert(id, name);
if let Some(shadowed) = self.bindings.insert(name, id) {
self.shadowed_bindings.insert(id, shadowed);
Some(shadowed)