use crate::linter::diagnostic::Diagnostic;
use crate::linter::rules::{Example, Rule, RuleContext};
use crate::semantic::{BindingKind, LoadKind};
pub struct UnusedImport;
impl Rule for UnusedImport {
fn id(&self) -> &'static str {
"unused-import"
}
fn description(&self) -> &'static str {
"Flag an explicitly imported name that is never used: `import X`, \
`import X as Y`, and the colon-item forms `using X: a` / `import X: a`. \
The whole-module `using X` form is exempt, since it attaches exports \
that resolve elsewhere. A qualified use (`X.f`) or a re-`export` counts \
as a use. Uses in the same module's included files also count. Incomplete \
include contexts withhold findings rather than treating missing uses \
as proof that an import is unused."
}
fn examples(&self) -> &'static [Example] {
&[Example {
caption: "`sortperm` is imported but never referenced:",
source: "using Base: sortperm, sum\n\nprintln(sum([1, 2, 3]))\n",
}]
}
fn check_file(&self, ctx: &RuleContext<'_>, sink: &mut Vec<Diagnostic>) {
if !ctx.trusts_import_usage() {
return;
}
for binding in ctx.model.bindings() {
if binding.read || binding.kind != BindingKind::Import {
continue;
}
if is_whole_module_using(ctx, binding.def_range) {
continue;
}
if let Some(usage) = ctx.scripts.or(ctx.import_usage) {
let Some(path) = ctx.path else { continue };
if !usage.import_is_unused(
path,
&ctx.model.enclosing_module_path(binding.scope),
&binding.name,
) {
continue;
}
}
sink.push(Diagnostic::new(
self.id(),
binding.def_range,
format!("`{}` is imported but never used", binding.name),
));
}
}
}
fn is_whole_module_using(ctx: &RuleContext<'_>, def: rowan::TextRange) -> bool {
ctx.model.module_loads().iter().any(|load| {
load.kind == LoadKind::Using && load.items.is_none() && load.range.contains_range(def)
})
}