Skip to main content

forge_lint/linter/
early.rs

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