Skip to main content

forge_lint/linter/
early.rs

1use super::LintContext;
2use solar::{
3    ast::{self as ast, visit::Visit},
4    interface::data_structures::Never,
5};
6use std::ops::ControlFlow;
7
8/// Trait for lints that operate directly on the AST.
9/// Its methods mirror `ast::visit::Visit`, with the addition of `LintContext`.
10pub trait EarlyLintPass<'ast>: Send + Sync {
11    fn check_expr(&mut self, _ctx: &LintContext, _expr: &'ast ast::Expr<'ast>) {}
12    fn check_item_struct(&mut self, _ctx: &LintContext, _struct: &'ast ast::ItemStruct<'ast>) {}
13    fn check_item_function(&mut self, _ctx: &LintContext, _func: &'ast ast::ItemFunction<'ast>) {}
14    fn check_variable_definition(
15        &mut self,
16        _ctx: &LintContext,
17        _var: &'ast ast::VariableDefinition<'ast>,
18    ) {
19    }
20    fn check_import_directive(
21        &mut self,
22        _ctx: &LintContext,
23        _import: &'ast ast::ImportDirective<'ast>,
24    ) {
25    }
26    fn check_using_directive(
27        &mut self,
28        _ctx: &LintContext,
29        _using: &'ast ast::UsingDirective<'ast>,
30    ) {
31    }
32    fn check_item_contract(
33        &mut self,
34        _ctx: &LintContext,
35        _contract: &'ast ast::ItemContract<'ast>,
36    ) {
37    }
38    fn check_doc_comment(&mut self, _ctx: &LintContext, _cmnt: &'ast ast::DocComment) {}
39    fn check_item(&mut self, _ctx: &LintContext, _item: &'ast ast::Item<'ast>) {}
40    fn check_stmt(&mut self, _ctx: &LintContext, _stmt: &'ast ast::Stmt<'ast>) {}
41    fn check_path(&mut self, _ctx: &LintContext, _path: &'ast ast::PathSlice) {}
42    fn check_ty(&mut self, _ctx: &LintContext, _ty: &'ast ast::Type<'ast>) {}
43
44    /// Should be called after the source unit has been visited. Enables lints that require
45    /// knowledge of the entire AST to perform their analysis.
46    ///
47    /// # Performance
48    ///
49    /// Since a full-AST analysis can be computationally expensive, implementations
50    /// should guard their logic by first checking if the relevant lint is enabled
51    /// using [`LintContext::is_lint_enabled`]. This avoids performing costly work
52    /// if the user has disabled the lint.
53    ///
54    /// ### Example
55    /// ```rust,ignore
56    /// fn check_full_source_unit(&mut self, ctx: &LintContext<'ast, '_>, ast: &'ast ast::SourceUnit<'ast>) {
57    ///     // Check if the lint is enabled before performing expensive work.
58    ///     if ctx.is_lint_enabled(MY_EXPENSIVE_LINT.id) {
59    ///         // ... perform computation and emit diagnostics ...
60    ///     }
61    /// }
62    /// ```
63    fn check_full_source_unit(
64        &mut self,
65        _ctx: &LintContext<'ast, '_>,
66        _ast: &'ast ast::SourceUnit<'ast>,
67    ) {
68    }
69}
70
71/// Visitor struct for `EarlyLintPass`es
72pub struct EarlyLintVisitor<'a, 's, 'ast> {
73    pub ctx: &'a LintContext<'s, 'a>,
74    pub passes: &'a mut [Box<dyn EarlyLintPass<'ast> + 's>],
75}
76
77impl<'a, 's, 'ast> EarlyLintVisitor<'a, 's, 'ast>
78where
79    's: 'ast,
80{
81    pub fn new(
82        ctx: &'a LintContext<'s, 'a>,
83        passes: &'a mut [Box<dyn EarlyLintPass<'ast> + 's>],
84    ) -> Self {
85        Self { ctx, passes }
86    }
87
88    /// Extends the [`Visit`] trait functionality with a hook that can run after the initial
89    /// traversal.
90    pub fn post_source_unit(&mut self, ast: &'ast ast::SourceUnit<'ast>) {
91        for pass in self.passes.iter_mut() {
92            pass.check_full_source_unit(self.ctx, ast);
93        }
94    }
95}
96
97impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast>
98where
99    's: 'ast,
100{
101    type BreakValue = Never;
102
103    fn visit_doc_comment(&mut self, cmnt: &'ast ast::DocComment) -> ControlFlow<Self::BreakValue> {
104        for pass in self.passes.iter_mut() {
105            pass.check_doc_comment(self.ctx, cmnt)
106        }
107        self.walk_doc_comment(cmnt)
108    }
109
110    fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
111        for pass in self.passes.iter_mut() {
112            pass.check_expr(self.ctx, expr)
113        }
114        self.walk_expr(expr)
115    }
116
117    fn visit_variable_definition(
118        &mut self,
119        var: &'ast ast::VariableDefinition<'ast>,
120    ) -> ControlFlow<Self::BreakValue> {
121        for pass in self.passes.iter_mut() {
122            pass.check_variable_definition(self.ctx, var)
123        }
124        self.walk_variable_definition(var)
125    }
126
127    fn visit_item_struct(
128        &mut self,
129        strukt: &'ast ast::ItemStruct<'ast>,
130    ) -> ControlFlow<Self::BreakValue> {
131        for pass in self.passes.iter_mut() {
132            pass.check_item_struct(self.ctx, strukt)
133        }
134        self.walk_item_struct(strukt)
135    }
136
137    fn visit_item_function(
138        &mut self,
139        func: &'ast ast::ItemFunction<'ast>,
140    ) -> ControlFlow<Self::BreakValue> {
141        for pass in self.passes.iter_mut() {
142            pass.check_item_function(self.ctx, func)
143        }
144        self.walk_item_function(func)
145    }
146
147    fn visit_import_directive(
148        &mut self,
149        import: &'ast ast::ImportDirective<'ast>,
150    ) -> ControlFlow<Self::BreakValue> {
151        for pass in self.passes.iter_mut() {
152            pass.check_import_directive(self.ctx, import);
153        }
154        self.walk_import_directive(import)
155    }
156
157    fn visit_using_directive(
158        &mut self,
159        using: &'ast ast::UsingDirective<'ast>,
160    ) -> ControlFlow<Self::BreakValue> {
161        for pass in self.passes.iter_mut() {
162            pass.check_using_directive(self.ctx, using);
163        }
164        self.walk_using_directive(using)
165    }
166
167    fn visit_item_contract(
168        &mut self,
169        contract: &'ast ast::ItemContract<'ast>,
170    ) -> ControlFlow<Self::BreakValue> {
171        for pass in self.passes.iter_mut() {
172            pass.check_item_contract(self.ctx, contract);
173        }
174        self.walk_item_contract(contract)
175    }
176
177    fn visit_item(&mut self, item: &'ast ast::Item<'ast>) -> ControlFlow<Self::BreakValue> {
178        for pass in self.passes.iter_mut() {
179            pass.check_item(self.ctx, item)
180        }
181        self.walk_item(item)
182    }
183
184    fn visit_stmt(&mut self, stmt: &'ast ast::Stmt<'ast>) -> ControlFlow<Self::BreakValue> {
185        for pass in self.passes.iter_mut() {
186            pass.check_stmt(self.ctx, stmt)
187        }
188        self.walk_stmt(stmt)
189    }
190
191    fn visit_path(&mut self, path: &'ast ast::PathSlice) -> ControlFlow<Self::BreakValue> {
192        for pass in self.passes.iter_mut() {
193            pass.check_path(self.ctx, path)
194        }
195        self.walk_path(path)
196    }
197
198    fn visit_ty(&mut self, ty: &'ast ast::Type<'ast>) -> ControlFlow<Self::BreakValue> {
199        for pass in self.passes.iter_mut() {
200            pass.check_ty(self.ctx, ty)
201        }
202        self.walk_ty(ty)
203    }
204}