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 `LintCotext`.
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    // TODO: Add methods for each required AST node type
40
41    /// Should be called after the source unit has been visited. Enables lints that require
42    /// knowledge of the entire AST to perform their analysis.
43    ///
44    /// # Performance
45    ///
46    /// Since a full-AST analysis can be computationally expensive, implementations
47    /// should guard their logic by first checking if the relevant lint is enabled
48    /// using [`LintContext::is_lint_enabled`]. This avoids performing costly work
49    /// if the user has disabled the lint.
50    ///
51    /// ### Example
52    /// ```rust,ignore
53    /// fn check_full_source_unit(&mut self, ctx: &LintContext<'ast>, ast: &'ast ast::SourceUnit<'ast>) {
54    ///     // Check if the lint is enabled before performing expensive work.
55    ///     if ctx.is_lint_enabled(MY_EXPENSIVE_LINT.id) {
56    ///         // ... perform computation and emit diagnostics ...
57    ///     }
58    /// }
59    /// ```
60    fn check_full_source_unit(
61        &mut self,
62        _ctx: &LintContext<'ast, '_>,
63        _ast: &'ast ast::SourceUnit<'ast>,
64    ) {
65    }
66}
67
68/// Visitor struct for `EarlyLintPass`es
69pub struct EarlyLintVisitor<'a, 's, 'ast> {
70    pub ctx: &'a LintContext<'s, 'a>,
71    pub passes: &'a mut [Box<dyn EarlyLintPass<'ast> + 's>],
72}
73
74impl<'a, 's, 'ast> EarlyLintVisitor<'a, 's, 'ast>
75where
76    's: 'ast,
77{
78    pub fn new(
79        ctx: &'a LintContext<'s, 'a>,
80        passes: &'a mut [Box<dyn EarlyLintPass<'ast> + 's>],
81    ) -> Self {
82        Self { ctx, passes }
83    }
84
85    /// Extends the [`Visit`] trait functionality with a hook that can run after the initial
86    /// traversal.
87    pub fn post_source_unit(&mut self, ast: &'ast ast::SourceUnit<'ast>) {
88        for pass in self.passes.iter_mut() {
89            pass.check_full_source_unit(self.ctx, ast);
90        }
91    }
92}
93
94impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast>
95where
96    's: 'ast,
97{
98    type BreakValue = Never;
99
100    fn visit_doc_comment(&mut self, cmnt: &'ast ast::DocComment) -> ControlFlow<Self::BreakValue> {
101        for pass in self.passes.iter_mut() {
102            pass.check_doc_comment(self.ctx, cmnt)
103        }
104        self.walk_doc_comment(cmnt)
105    }
106
107    fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
108        for pass in self.passes.iter_mut() {
109            pass.check_expr(self.ctx, expr)
110        }
111        self.walk_expr(expr)
112    }
113
114    fn visit_variable_definition(
115        &mut self,
116        var: &'ast ast::VariableDefinition<'ast>,
117    ) -> ControlFlow<Self::BreakValue> {
118        for pass in self.passes.iter_mut() {
119            pass.check_variable_definition(self.ctx, var)
120        }
121        self.walk_variable_definition(var)
122    }
123
124    fn visit_item_struct(
125        &mut self,
126        strukt: &'ast ast::ItemStruct<'ast>,
127    ) -> ControlFlow<Self::BreakValue> {
128        for pass in self.passes.iter_mut() {
129            pass.check_item_struct(self.ctx, strukt)
130        }
131        self.walk_item_struct(strukt)
132    }
133
134    fn visit_item_function(
135        &mut self,
136        func: &'ast ast::ItemFunction<'ast>,
137    ) -> ControlFlow<Self::BreakValue> {
138        for pass in self.passes.iter_mut() {
139            pass.check_item_function(self.ctx, func)
140        }
141        self.walk_item_function(func)
142    }
143
144    fn visit_import_directive(
145        &mut self,
146        import: &'ast ast::ImportDirective<'ast>,
147    ) -> ControlFlow<Self::BreakValue> {
148        for pass in self.passes.iter_mut() {
149            pass.check_import_directive(self.ctx, import);
150        }
151        self.walk_import_directive(import)
152    }
153
154    fn visit_using_directive(
155        &mut self,
156        using: &'ast ast::UsingDirective<'ast>,
157    ) -> ControlFlow<Self::BreakValue> {
158        for pass in self.passes.iter_mut() {
159            pass.check_using_directive(self.ctx, using);
160        }
161        self.walk_using_directive(using)
162    }
163
164    fn visit_item_contract(
165        &mut self,
166        contract: &'ast ast::ItemContract<'ast>,
167    ) -> ControlFlow<Self::BreakValue> {
168        for pass in self.passes.iter_mut() {
169            pass.check_item_contract(self.ctx, contract);
170        }
171        self.walk_item_contract(contract)
172    }
173
174    // TODO: Add methods for each required AST node type, mirroring `solar::ast::visit::Visit`
175    // method sigs + adding `LintContext`
176}