Skip to main content

forge_lint/sol/
macros.rs

1/// Macro for defining lints and relevant metadata for the Solidity linter.
2///
3/// # Parameters
4///
5/// Each lint requires the following input fields:
6/// - `$id`: Identifier of the generated `SolLint` constant.
7/// - `$severity`: The `Severity` of the lint (e.g. `High`, `Med`, `Low`, `Info`, `Gas`).
8/// - `$str_id`: A unique identifier used to reference a specific lint during configuration.
9/// - `$desc`: A short description of the lint.
10///
11/// # Note
12/// Each lint must have a corresponding markdown documentation file at
13/// `crates/lint/docs/<str_id>.md`. The `help` URL is auto-generated by the macro and points to
14/// the per-lint page on the Foundry docs site (`getfoundry.sh/forge/linting/<str_id>`). To
15/// ensure that new lint rules have their corresponding docs, the existence of every registered
16/// lint's markdown file is validated by a unit test (see `crates/lint/src/sol/mod.rs`).
17#[macro_export]
18macro_rules! declare_forge_lint {
19    ($id:ident, $severity:expr, $str_id:expr, $desc:expr) => {
20        // Declare the static `Lint` metadata
21        pub static $id: SolLint = SolLint {
22            id: $str_id,
23            severity: $severity,
24            description: $desc,
25            help: concat!("https://getfoundry.sh/forge/linting/", $str_id),
26        };
27    };
28}
29
30/// Registers Solidity linter passes that can have both early and late variants.
31///
32/// # Parameters
33///
34/// Each pass is declared with:
35/// - `$pass_id`: Identifier of the generated struct that will implement the pass trait(s).
36/// - `$pass_type`: Either `early`, `late`, or `both` to indicate which traits to implement.
37/// - `$lints`: A parenthesized, comma-separated list of `SolLint` constants.
38///
39/// # Outputs
40///
41/// - Structs for each linting pass
42/// - Helper methods to create early and late passes with required lifetimes
43/// - `const REGISTERED_LINTS` containing all registered lint objects
44#[macro_export]
45macro_rules! register_lints {
46    // 1. Internal rule for declaring structs and their associated lints.
47    ( @declare_structs $( ($pass_id:ident, $pass_type:ident, ($($lint:expr),* $(,)?)) ),* $(,)? ) => {
48        $(
49            #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
50            pub struct $pass_id;
51
52            impl $pass_id {
53                /// Static slice of lints associated with this pass.
54                const LINTS: &'static [SolLint] = &[$($lint),*];
55
56                register_lints!(@early_impl $pass_id, $pass_type);
57                register_lints!(@late_impl $pass_id, $pass_type);
58                register_lints!(@project_impl $pass_id, $pass_type);
59            }
60        )*
61    };
62
63    // 2. Internal rule for declaring the const array of ALL lints.
64    ( @declare_consts $( ($pass_id:ident, $pass_type:ident, ($($lint:expr),* $(,)?)) ),* $(,)? ) => {
65        pub const REGISTERED_LINTS: &[SolLint] = &[
66            $(
67                $($lint,)*
68            )*
69        ];
70    };
71
72    // 3. Internal rule for declaring the helper functions.
73    ( @declare_funcs $( ($pass_id:ident, $pass_type:ident, $lints:tt) ),* $(,)? ) => {
74        pub fn create_early_lint_passes<'ast>() -> Vec<(Box<dyn EarlyLintPass<'ast>>, &'static [SolLint])> {
75            [
76                $(
77                    register_lints!(@early_create $pass_id, $pass_type),
78                )*
79            ]
80            .into_iter()
81            .flatten()
82            .collect()
83        }
84
85        pub fn create_late_lint_passes<'hir>() -> Vec<(Box<dyn LateLintPass<'hir>>, &'static [SolLint])> {
86            [
87                $(
88                    register_lints!(@late_create $pass_id, $pass_type),
89                )*
90            ]
91            .into_iter()
92            .flatten()
93            .collect()
94        }
95
96        pub fn create_project_lint_passes<'ast>() -> Vec<(Box<dyn $crate::linter::ProjectLintPass<'ast>>, &'static [SolLint])> {
97            [
98                $(
99                    register_lints!(@project_create $pass_id, $pass_type),
100                )*
101            ]
102            .into_iter()
103            .flatten()
104            .collect()
105        }
106    };
107
108    // --- HELPERS ------------------------------------------------------------
109    (@early_impl $_pass_id:ident, late) => {};
110    (@early_impl $_pass_id:ident, project) => {};
111    (@early_impl $pass_id:ident, $other:ident) => {
112        pub fn as_early_lint_pass<'a>() -> Box<dyn EarlyLintPass<'a>> {
113            Box::new(Self::default())
114        }
115    };
116
117    (@late_impl $_pass_id:ident, early) => {};
118    (@late_impl $_pass_id:ident, project) => {};
119    (@late_impl $pass_id:ident, $other:ident) => {
120        pub fn as_late_lint_pass<'hir>() -> Box<dyn LateLintPass<'hir>> {
121            Box::new(Self::default())
122        }
123    };
124
125    (@project_impl $_pass_id:ident, early) => {};
126    (@project_impl $_pass_id:ident, late) => {};
127    (@project_impl $_pass_id:ident, both) => {};
128    (@project_impl $pass_id:ident, $other:ident) => {
129        pub fn as_project_lint_pass<'ast>() -> Box<dyn $crate::linter::ProjectLintPass<'ast>> {
130            Box::new(Self::default())
131        }
132    };
133
134    (@early_create $_pass_id:ident, late) => { None };
135    (@early_create $_pass_id:ident, project) => { None };
136    (@early_create $pass_id:ident, $_other:ident) => {
137        Some(($pass_id::as_early_lint_pass(), $pass_id::LINTS))
138    };
139
140    (@late_create $_pass_id:ident, early) => { None };
141    (@late_create $_pass_id:ident, project) => { None };
142    (@late_create $pass_id:ident, $_other:ident) => {
143        Some(($pass_id::as_late_lint_pass(), $pass_id::LINTS))
144    };
145
146    (@project_create $_pass_id:ident, early) => { None };
147    (@project_create $_pass_id:ident, late) => { None };
148    (@project_create $_pass_id:ident, both) => { None };
149    (@project_create $pass_id:ident, $_other:ident) => {
150        Some(($pass_id::as_project_lint_pass(), $pass_id::LINTS))
151    };
152
153    // --- ENTRY POINT ---------------------------------------------------------
154    ( $($tokens:tt)* ) => {
155        register_lints! { @declare_structs $($tokens)* }
156        register_lints! { @declare_consts  $($tokens)* }
157        register_lints! { @declare_funcs   $($tokens)* }
158    };
159}