forge_lint/sol/info/
imports.rs1use super::Imports;
2use crate::{
3 linter::{EarlyLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::{
7 ast::{self as ast, SourceUnit, Symbol, visit::Visit},
8 data_structures::{Never, map::FxHashSet},
9 interface::SourceMap,
10};
11use std::ops::ControlFlow;
12
13declare_forge_lint!(UNUSED_IMPORT, Severity::Info, "unused-import", "unused import");
14
15declare_forge_lint!(
16 UNALIASED_PLAIN_IMPORT,
17 Severity::Info,
18 "unaliased-plain-import",
19 "plain import has no alias; use named imports `{A, B}` or alias `import \"..\" as X`"
20);
21
22impl<'ast> EarlyLintPass<'ast> for Imports {
23 fn check_import_directive(
24 &mut self,
25 ctx: &LintContext,
26 import: &'ast ast::ImportDirective<'ast>,
27 ) {
28 if let ast::ImportItems::Plain(_) = &import.items
30 && import.source_alias().is_none()
31 {
32 ctx.emit(&UNALIASED_PLAIN_IMPORT, import.path.span);
33 }
34 }
35
36 fn check_full_source_unit(&mut self, ctx: &LintContext<'ast, '_>, ast: &'ast SourceUnit<'ast>) {
37 if !ctx.is_lint_enabled(UNUSED_IMPORT.id) {
39 return;
40 }
41 let mut checker =
42 UsedSymbols { source_map: ctx.session().source_map(), used: FxHashSet::default() };
43 let _ = checker.visit_source_unit(ast);
44 let used = checker.used;
45
46 for item in ast.items.iter() {
47 let ast::ItemKind::Import(import) = &item.kind else { continue };
48 match &import.items {
49 ast::ImportItems::Aliases(symbols) => {
50 for &(orig, alias) in symbols.iter() {
51 let name = alias.unwrap_or(orig);
52 if !used.contains(&name.name) {
53 ctx.emit(&UNUSED_IMPORT, orig.span.to(name.span));
54 }
55 }
56 }
57 ast::ImportItems::Plain(_) | ast::ImportItems::Glob(_) => {
58 if let Some(alias) = import.source_alias()
59 && !used.contains(&alias.name)
60 {
61 ctx.emit(&UNUSED_IMPORT, item.span);
62 }
63 }
64 }
65 }
66 }
67}
68
69struct UsedSymbols<'ast> {
71 source_map: &'ast SourceMap,
72 used: FxHashSet<Symbol>,
73}
74
75impl<'ast> Visit<'ast> for UsedSymbols<'ast> {
76 type BreakValue = Never;
77
78 fn visit_item(&mut self, item: &'ast ast::Item<'ast>) -> ControlFlow<Self::BreakValue> {
79 if let ast::ItemKind::Import(_) = &item.kind {
80 return ControlFlow::Continue(());
81 }
82 self.walk_item(item)
83 }
84
85 fn visit_using_directive(
86 &mut self,
87 using: &'ast ast::UsingDirective<'ast>,
88 ) -> ControlFlow<Self::BreakValue> {
89 match &using.list {
90 ast::UsingList::Single(path) => {
91 self.used.insert(path.first().name);
92 }
93 ast::UsingList::Multiple(items) => {
94 self.used.extend(items.iter().map(|(path, _)| path.first().name));
95 }
96 }
97 self.walk_using_directive(using)
98 }
99
100 fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
101 if let ast::ExprKind::Ident(id) = expr.kind {
102 self.used.insert(id.name);
103 }
104 self.walk_expr(expr)
105 }
106
107 fn visit_path(&mut self, path: &'ast ast::PathSlice) -> ControlFlow<Self::BreakValue> {
108 self.used.extend(path.segments().iter().map(|id| id.name));
109 self.walk_path(path)
110 }
111
112 fn visit_ty(&mut self, ty: &'ast ast::Type<'ast>) -> ControlFlow<Self::BreakValue> {
113 if let ast::TypeKind::Custom(path) = &ty.kind {
114 self.used.insert(path.first().name);
115 }
116 self.walk_ty(ty)
117 }
118
119 fn visit_doc_comment(&mut self, cmnt: &'ast ast::DocComment) -> ControlFlow<Self::BreakValue> {
120 if let Ok(snip) = self.source_map.span_to_snippet(cmnt.span) {
121 for line in snip.lines() {
122 if let Some((_, relevant)) = line.split_once("@inheritdoc") {
123 self.used.insert(Symbol::intern(relevant.trim()));
124 }
125 }
126 }
127 ControlFlow::Continue(())
128 }
129}