forge_lint/
linter.rs

1use foundry_compilers::Language;
2use foundry_config::lint::Severity;
3use solar_ast::{visit::Visit, Expr, ItemFunction, ItemStruct, VariableDefinition};
4use solar_interface::{
5    data_structures::Never,
6    diagnostics::{DiagBuilder, DiagId, MultiSpan},
7    Session, Span,
8};
9use std::{ops::ControlFlow, path::PathBuf};
10
11/// Trait representing a generic linter for analyzing and reporting issues in smart contract source
12/// code files. A linter can be implemented for any smart contract language supported by Foundry.
13///
14/// # Type Parameters
15///
16/// - `Language`: Represents the target programming language. Must implement the [`Language`] trait.
17/// - `Lint`: Represents the types of lints performed by the linter. Must implement the [`Lint`]
18///   trait.
19///
20/// # Required Methods
21///
22/// - `lint`: Scans the provided source files emitting a daignostic for lints found.
23pub trait Linter: Send + Sync + Clone {
24    type Language: Language;
25    type Lint: Lint;
26
27    fn lint(&self, input: &[PathBuf]);
28}
29
30pub trait Lint {
31    fn id(&self) -> &'static str;
32    fn severity(&self) -> Severity;
33    fn description(&self) -> &'static str;
34    fn help(&self) -> &'static str;
35}
36
37pub struct LintContext<'s> {
38    sess: &'s Session,
39    desc: bool,
40}
41
42impl<'s> LintContext<'s> {
43    pub fn new(sess: &'s Session, with_description: bool) -> Self {
44        Self { sess, desc: with_description }
45    }
46
47    // Helper method to emit diagnostics easily from passes
48    pub fn emit<L: Lint>(&self, lint: &'static L, span: Span) {
49        let desc = if self.desc { lint.description() } else { "" };
50        let diag: DiagBuilder<'_, ()> = self
51            .sess
52            .dcx
53            .diag(lint.severity().into(), desc)
54            .code(DiagId::new_str(lint.id()))
55            .span(MultiSpan::from_span(span))
56            .help(lint.help());
57
58        diag.emit();
59    }
60}
61
62/// Trait for lints that operate directly on the AST.
63/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`.
64pub trait EarlyLintPass<'ast>: Send + Sync {
65    fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {}
66    fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {}
67    fn check_item_function(&mut self, _ctx: &LintContext<'_>, _func: &'ast ItemFunction<'ast>) {}
68    fn check_variable_definition(
69        &mut self,
70        _ctx: &LintContext<'_>,
71        _var: &'ast VariableDefinition<'ast>,
72    ) {
73    }
74
75    // TODO: Add methods for each required AST node type
76}
77
78/// Visitor struct for `EarlyLintPass`es
79pub struct EarlyLintVisitor<'a, 's, 'ast> {
80    pub ctx: &'a LintContext<'s>,
81    pub passes: &'a mut [Box<dyn EarlyLintPass<'ast> + 's>],
82}
83
84impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast>
85where
86    's: 'ast,
87{
88    type BreakValue = Never;
89
90    fn visit_expr(&mut self, expr: &'ast Expr<'ast>) -> ControlFlow<Self::BreakValue> {
91        for pass in self.passes.iter_mut() {
92            pass.check_expr(self.ctx, expr)
93        }
94        self.walk_expr(expr)
95    }
96
97    fn visit_variable_definition(
98        &mut self,
99        var: &'ast VariableDefinition<'ast>,
100    ) -> ControlFlow<Self::BreakValue> {
101        for pass in self.passes.iter_mut() {
102            pass.check_variable_definition(self.ctx, var)
103        }
104        self.walk_variable_definition(var)
105    }
106
107    fn visit_item_struct(
108        &mut self,
109        strukt: &'ast ItemStruct<'ast>,
110    ) -> ControlFlow<Self::BreakValue> {
111        for pass in self.passes.iter_mut() {
112            pass.check_item_struct(self.ctx, strukt)
113        }
114        self.walk_item_struct(strukt)
115    }
116
117    fn visit_item_function(
118        &mut self,
119        func: &'ast ItemFunction<'ast>,
120    ) -> ControlFlow<Self::BreakValue> {
121        for pass in self.passes.iter_mut() {
122            pass.check_item_function(self.ctx, func)
123        }
124        self.walk_item_function(func)
125    }
126
127    // TODO: Add methods for each required AST node type, mirroring `solar_ast::visit::Visit` method
128    // sigs + adding `LintContext`
129}