forge_lint/sol/info/
imports.rs
1use solar_ast::{self as ast, SourceUnit, Span, Symbol, visit::Visit};
2use solar_data_structures::map::FxIndexSet;
3use solar_interface::SourceMap;
4use std::ops::ControlFlow;
5
6use super::Imports;
7use crate::{
8 linter::{EarlyLintPass, LintContext},
9 sol::{Severity, SolLint},
10};
11
12declare_forge_lint!(
13 UNUSED_IMPORT,
14 Severity::Info,
15 "unused-import",
16 "unused imports should be removed"
17);
18
19declare_forge_lint!(
20 UNALIASED_PLAIN_IMPORT,
21 Severity::Info,
22 "unaliased-plain-import",
23 "use named imports '{A, B}' or alias 'import \"..\" as X'"
24);
25
26impl<'ast> EarlyLintPass<'ast> for Imports {
27 fn check_import_directive(
28 &mut self,
29 ctx: &LintContext<'_>,
30 import: &'ast ast::ImportDirective<'ast>,
31 ) {
32 if let ast::ImportItems::Plain(_) = &import.items
34 && import.source_alias().is_none()
35 {
36 ctx.emit(&UNALIASED_PLAIN_IMPORT, import.path.span);
37 }
38 }
39
40 fn check_full_source_unit(&mut self, ctx: &LintContext<'ast>, ast: &'ast SourceUnit<'ast>) {
41 let mut checker = UnusedChecker::new(ctx.session().source_map());
42 let _ = checker.visit_source_unit(ast);
43 checker.check_unused_imports(ast, ctx);
44 checker.clear();
45 }
46}
47
48struct UnusedChecker<'ast> {
50 used_symbols: FxIndexSet<Symbol>,
51 source_map: &'ast SourceMap,
52}
53
54impl<'ast> UnusedChecker<'ast> {
55 fn new(source_map: &'ast SourceMap) -> Self {
56 Self { source_map, used_symbols: Default::default() }
57 }
58
59 fn clear(&mut self) {
60 self.used_symbols.clear();
61 }
62
63 fn mark_symbol_used(&mut self, symbol: Symbol) {
65 self.used_symbols.insert(symbol);
66 }
67
68 fn check_unused_imports(&self, ast: &SourceUnit<'_>, ctx: &LintContext<'_>) {
70 for item in ast.items.iter() {
71 let span = item.span;
72 let ast::ItemKind::Import(import) = &item.kind else { continue };
73 match &import.items {
74 ast::ImportItems::Plain(_) | ast::ImportItems::Glob(_) => {
75 if let Some(alias) = import.source_alias()
76 && !self.used_symbols.contains(&alias.name)
77 {
78 self.unused_import(ctx, span);
79 }
80 }
81 ast::ImportItems::Aliases(symbols) => {
82 for &(orig, alias) in symbols.iter() {
83 let name = alias.unwrap_or(orig);
84 if !self.used_symbols.contains(&name.name) {
85 self.unused_import(ctx, orig.span.to(name.span));
86 }
87 }
88 }
89 }
90 }
91 }
92
93 fn unused_import(&self, ctx: &LintContext<'_>, span: Span) {
94 ctx.emit(&UNUSED_IMPORT, span);
95 }
96}
97
98impl<'ast> Visit<'ast> for UnusedChecker<'ast> {
99 type BreakValue = solar_data_structures::Never;
100
101 fn visit_item(&mut self, item: &'ast ast::Item<'ast>) -> ControlFlow<Self::BreakValue> {
102 if let ast::ItemKind::Import(_) = &item.kind {
103 return ControlFlow::Continue(());
104 }
105
106 self.walk_item(item)
107 }
108
109 fn visit_using_directive(
110 &mut self,
111 using: &'ast ast::UsingDirective<'ast>,
112 ) -> ControlFlow<Self::BreakValue> {
113 match &using.list {
114 ast::UsingList::Single(path) => {
115 self.mark_symbol_used(path.first().name);
116 }
117 ast::UsingList::Multiple(items) => {
118 for (path, _) in items.iter() {
119 self.mark_symbol_used(path.first().name);
120 }
121 }
122 }
123
124 self.walk_using_directive(using)
125 }
126
127 fn visit_function_header(
128 &mut self,
129 header: &'ast solar_ast::FunctionHeader<'ast>,
130 ) -> ControlFlow<Self::BreakValue> {
131 if let Some(ref override_) = header.override_ {
133 for path in override_.paths.iter() {
134 _ = self.visit_path(path);
135 }
136 }
137
138 self.walk_function_header(header)
139 }
140
141 fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
142 if let ast::ExprKind::Ident(id) = expr.kind {
143 self.mark_symbol_used(id.name);
144 }
145
146 self.walk_expr(expr)
147 }
148
149 fn visit_path(&mut self, path: &'ast ast::PathSlice) -> ControlFlow<Self::BreakValue> {
150 for id in path.segments() {
151 self.mark_symbol_used(id.name);
152 }
153
154 self.walk_path(path)
155 }
156
157 fn visit_ty(&mut self, ty: &'ast ast::Type<'ast>) -> ControlFlow<Self::BreakValue> {
158 if let ast::TypeKind::Custom(path) = &ty.kind {
159 self.mark_symbol_used(path.first().name);
160 }
161
162 self.walk_ty(ty)
163 }
164
165 fn visit_doc_comment(
166 &mut self,
167 cmnt: &'ast solar_ast::DocComment,
168 ) -> ControlFlow<Self::BreakValue> {
169 if let Ok(snip) = self.source_map.span_to_snippet(cmnt.span) {
170 for line in snip.lines() {
171 if let Some((_, relevant)) = line.split_once("@inheritdoc") {
172 self.mark_symbol_used(Symbol::intern(relevant.trim()));
173 }
174 }
175 }
176 ControlFlow::Continue(())
177 }
178}