Skip to main content

wowlab_tidy/
macros.rs

1#[macro_export]
2#[doc(hidden)]
3macro_rules! _declare_params {
4    () => {
5        const PARAMS: &[$crate::RuleParam] = &[];
6    };
7    ($($pname:ident : $ptype:tt = $default:tt),+ $(,)?) => {
8        const PARAMS: &[$crate::RuleParam] = &[
9            $($crate::_param!($pname : $ptype = $default)),+
10        ];
11    };
12}
13
14#[macro_export]
15#[doc(hidden)]
16macro_rules! _param {
17    ($pname:ident : i64 = $default:expr) => {
18        $crate::RuleParam {
19            name: stringify!($pname),
20            param_type: $crate::ParamType::Int,
21            default: $crate::ParamDefault::Int($default),
22        }
23    };
24    ($pname:ident : [String] = [$($val:expr),* $(,)?]) => {
25        $crate::RuleParam {
26            name: stringify!($pname),
27            param_type: $crate::ParamType::StringArray,
28            default: $crate::ParamDefault::StringArray(&[$($val),*]),
29        }
30    };
31}
32
33/// Run every `&[Example]` as one test, asserting each pass/fail expectation.
34#[macro_export]
35macro_rules! example_tests {
36    ($examples:expr, $check_fn:ident) => {
37        #[$crate::_private::gtest]
38        fn examples() -> $crate::_private::TestResult {
39            for (i, ex) in $examples.iter().enumerate() {
40                $crate::_private::scoped_trace!("example[{i}] {:?}:\n{}", ex.label, ex.code);
41                let violations = run(ex.code);
42                if ex.pass {
43                    $crate::_private::verify_true!(violations.is_empty())?;
44                } else {
45                    $crate::_private::verify_false!(violations.is_empty())?;
46                }
47            }
48
49            Ok(())
50        }
51    };
52}
53
54#[macro_export]
55macro_rules! tidy_test {
56    ($check_fn:ident, { $($body:tt)* }) => {
57        #[cfg(test)]
58        mod tests {
59            use super::*;
60            fn run(source: &str) -> Vec<$crate::Violation> {
61                $crate::check_source(source, $check_fn)
62            }
63            $($body)*
64        }
65    };
66}
67
68#[macro_export]
69macro_rules! tidy_ast_test {
70    ($check_fn:ident, { $($body:tt)* }) => {
71        #[cfg(test)]
72        mod tests {
73            use super::*;
74            fn run(source: &str) -> Vec<$crate::Violation> {
75                $crate::check_source_ast(source, $check_fn)
76            }
77            $($body)*
78        }
79    };
80}
81
82#[macro_export]
83macro_rules! tidy_toml_test {
84    ($check_fn:ident, { $($body:tt)* }) => {
85        #[cfg(test)]
86        mod tests {
87            use super::*;
88            fn run(source: &str) -> Vec<$crate::Violation> {
89                $crate::check_source_toml(source, $check_fn)
90            }
91            $($body)*
92        }
93    };
94}
95
96#[macro_export]
97macro_rules! tidy_workspace_test {
98    ($check_fn:ident, { $($body:tt)* }) => {
99        #[cfg(test)]
100        mod tests {
101            use super::*;
102            fn run(source: &str) -> Vec<$crate::Violation> {
103                $crate::check_workspace_sources(&[("test.rs", source)], $check_fn)
104            }
105            $($body)*
106        }
107    };
108}
109
110#[macro_export]
111#[doc(hidden)]
112macro_rules! _rule_info {
113    ($name:expr, $desc:expr, $just:expr, $sev:ident, $params:expr) => {
114        $crate::RuleInfo {
115            name: $name,
116            description: $desc,
117            justification: $just,
118            severity: $crate::Severity::$sev,
119            examples: EXAMPLES,
120            params: $params,
121        }
122    };
123}
124
125#[macro_export]
126#[doc(hidden)]
127macro_rules! _submit_line_rule {
128    ($name:ident, $desc:expr, $just:expr, $sev:ident, $params:expr, $fix:expr) => {
129        paste::paste! {
130            inventory::submit! {
131                $crate::Rule {
132                    info: $crate::_rule_info!(concat!("rust_", stringify!($name)), $desc, $just, $sev, $params),
133                    check: $crate::RuleCheck::RustLine([<check_ $name>]),
134                    fix: $fix,
135                }
136            }
137        }
138    };
139}
140
141#[macro_export]
142#[doc(hidden)]
143macro_rules! _submit_ast_rule {
144    ($name:ident, $desc:expr, $just:expr, $sev:ident, $params:expr, $fix:expr) => {
145        paste::paste! {
146            inventory::submit! {
147                $crate::Rule {
148                    info: $crate::_rule_info!(concat!("rust_", stringify!($name)), $desc, $just, $sev, $params),
149                    check: $crate::RuleCheck::RustAst([<check_ $name>]),
150                    fix: $fix,
151                }
152            }
153        }
154    };
155}
156
157#[macro_export]
158#[doc(hidden)]
159macro_rules! _submit_ast_tree_rule {
160    ($name:ident, $desc:expr, $just:expr, $sev:ident, $params:expr, $fix:expr) => {
161        paste::paste! {
162            inventory::submit! {
163                $crate::Rule {
164                    info: $crate::_rule_info!(concat!("rust_", stringify!($name)), $desc, $just, $sev, $params),
165                    check: $crate::RuleCheck::RustAst([<check_ $name>]),
166                    fix: Some($crate::RuleFix::RustAstTree($fix)),
167                }
168            }
169        }
170    };
171}
172
173#[macro_export]
174#[doc(hidden)]
175macro_rules! _submit_workspace_rule {
176    ($name:ident, $desc:expr, $just:expr, $sev:ident, $params:expr) => {
177        paste::paste! {
178            inventory::submit! {
179                $crate::Rule {
180                    info: $crate::_rule_info!(concat!("rust_", stringify!($name)), $desc, $just, $sev, $params),
181                    check: $crate::RuleCheck::RustWorkspace([<check_ $name>]),
182                    fix: None,
183                }
184            }
185        }
186    };
187}
188
189#[macro_export]
190#[doc(hidden)]
191macro_rules! _submit_language_workspace_rule {
192    ($name:ident, $desc:expr, $just:expr, $sev:ident, $params:expr) => {
193        paste::paste! {
194            inventory::submit! {
195                $crate::Rule {
196                    info: $crate::_rule_info!(stringify!($name), $desc, $just, $sev, $params),
197                    check: $crate::RuleCheck::Workspace([<check_ $name>]),
198                    fix: None,
199                }
200            }
201        }
202    };
203}
204
205#[macro_export]
206#[doc(hidden)]
207macro_rules! _submit_toml_rule {
208    ($name:ident, $desc:expr, $just:expr, $sev:ident, $params:expr, $fix:expr) => {
209        paste::paste! {
210            inventory::submit! {
211                $crate::Rule {
212                    info: $crate::_rule_info!(stringify!($name), $desc, $just, $sev, $params),
213                    check: $crate::RuleCheck::Toml([<check_ $name>]),
214                    fix: $fix,
215                }
216            }
217        }
218    };
219}
220
221/// Register a line-based lint rule.
222#[macro_export]
223macro_rules! line_rule {
224    ($name:ident, $desc:expr, $just:expr, $sev:ident, $fix:expr, params { $($params:tt)* } $(,)?) => {
225        $crate::_declare_params!($($params)*);
226        $crate::_submit_line_rule!($name, $desc, $just, $sev, PARAMS, Some($crate::RuleFix::RustLine($fix)));
227    };
228    ($name:ident, $desc:expr, $just:expr, $sev:ident, params { $($params:tt)* } $(,)?) => {
229        $crate::_declare_params!($($params)*);
230        $crate::_submit_line_rule!($name, $desc, $just, $sev, PARAMS, None);
231    };
232    ($name:ident, $desc:expr, $just:expr, params { $($params:tt)* } $(,)?) => {
233        $crate::_declare_params!($($params)*);
234        $crate::_submit_line_rule!($name, $desc, $just, Low, PARAMS, None);
235    };
236    ($name:ident, $desc:expr, $just:expr, $sev:ident, $fix:expr $(,)?) => {
237        $crate::_submit_line_rule!($name, $desc, $just, $sev, &[], Some($crate::RuleFix::RustLine($fix)));
238    };
239    ($name:ident, $desc:expr, $just:expr, $sev:ident $(,)?) => {
240        $crate::_submit_line_rule!($name, $desc, $just, $sev, &[], None);
241    };
242    ($name:ident, $desc:expr, $just:expr $(,)?) => {
243        $crate::_submit_line_rule!($name, $desc, $just, Low, &[], None);
244    };
245}
246
247/// Register an AST-based lint rule.
248#[macro_export]
249macro_rules! ast_rule {
250    ($name:ident, $desc:expr, $just:expr, $sev:ident, $fix:expr, params { $($params:tt)* } $(,)?) => {
251        $crate::_declare_params!($($params)*);
252        $crate::_submit_ast_rule!($name, $desc, $just, $sev, PARAMS, Some($crate::RuleFix::RustAst($fix)));
253    };
254    ($name:ident, $desc:expr, $just:expr, $sev:ident, params { $($params:tt)* } $(,)?) => {
255        $crate::_declare_params!($($params)*);
256        $crate::_submit_ast_rule!($name, $desc, $just, $sev, PARAMS, None);
257    };
258    ($name:ident, $desc:expr, $just:expr, params { $($params:tt)* } $(,)?) => {
259        $crate::_declare_params!($($params)*);
260        $crate::_submit_ast_rule!($name, $desc, $just, Low, PARAMS, None);
261    };
262    ($name:ident, $desc:expr, $just:expr, $sev:ident, $fix:expr $(,)?) => {
263        $crate::_submit_ast_rule!($name, $desc, $just, $sev, &[], Some($crate::RuleFix::RustAst($fix)));
264    };
265    ($name:ident, $desc:expr, $just:expr, $sev:ident $(,)?) => {
266        $crate::_submit_ast_rule!($name, $desc, $just, $sev, &[], None);
267    };
268    ($name:ident, $desc:expr, $just:expr $(,)?) => {
269        $crate::_submit_ast_rule!($name, $desc, $just, Low, &[], None);
270    };
271}
272
273/// Register a rust-analyzer AST rule whose fixer produces one coordinated tree edit per file.
274#[macro_export]
275macro_rules! ast_tree_rule {
276    ($name:ident, $desc:expr, $just:expr, $sev:ident, $fix:expr, params { $($params:tt)* } $(,)?) => {
277        $crate::_declare_params!($($params)*);
278        $crate::_submit_ast_tree_rule!($name, $desc, $just, $sev, PARAMS, $fix);
279    };
280    ($name:ident, $desc:expr, $just:expr, $sev:ident, $fix:expr $(,)?) => {
281        $crate::_submit_ast_tree_rule!($name, $desc, $just, $sev, &[], $fix);
282    };
283}
284
285/// Register a cross-file Rust rule.
286#[macro_export]
287macro_rules! workspace_rule {
288    ($name:ident, $desc:expr, $just:expr, $sev:ident, params { $($params:tt)* } $(,)?) => {
289        $crate::_declare_params!($($params)*);
290        $crate::_submit_workspace_rule!($name, $desc, $just, $sev, PARAMS);
291    };
292}
293
294/// Register a language-neutral cross-file workspace rule.
295#[macro_export]
296macro_rules! language_workspace_rule {
297    ($name:ident, $desc:expr, $just:expr, $sev:ident, params { $($params:tt)* } $(,)?) => {
298        $crate::_declare_params!($($params)*);
299        $crate::_submit_language_workspace_rule!($name, $desc, $just, $sev, PARAMS);
300    };
301}
302
303/// Register a Taplo-backed TOML rule.
304#[macro_export]
305macro_rules! toml_rule {
306    ($name:ident, $desc:expr, $just:expr, $sev:ident, $fix:expr, params { $($params:tt)* } $(,)?) => {
307        $crate::_declare_params!($($params)*);
308        $crate::_submit_toml_rule!($name, $desc, $just, $sev, PARAMS, Some($crate::RuleFix::Toml($fix)));
309    };
310    ($name:ident, $desc:expr, $just:expr, $sev:ident, params { $($params:tt)* } $(,)?) => {
311        $crate::_declare_params!($($params)*);
312        $crate::_submit_toml_rule!($name, $desc, $just, $sev, PARAMS, None);
313    };
314    ($name:ident, $desc:expr, $just:expr, $sev:ident, $fix:expr $(,)?) => {
315        $crate::_submit_toml_rule!($name, $desc, $just, $sev, &[], Some($crate::RuleFix::Toml($fix)));
316    };
317    ($name:ident, $desc:expr, $just:expr, $sev:ident $(,)?) => {
318        $crate::_submit_toml_rule!($name, $desc, $just, $sev, &[], None);
319    };
320}
321
322/// Verify fix functions resolve all violations in failing examples.
323#[macro_export]
324macro_rules! fix_tests {
325    (line, $check_fn:ident, $fix_fn:ident) => {
326        #[$crate::_private::gtest]
327        fn fixes() -> $crate::_private::TestResult {
328            for (i, ex) in EXAMPLES.iter().enumerate() {
329                if ex.pass {
330                    continue;
331                }
332                let fixed = $crate::apply_line_fixes(ex.code, $check_fn, $fix_fn);
333                let remaining = run(&fixed);
334                $crate::_private::scoped_trace!("example {i} ({:?})", ex.label);
335                $crate::_private::verify_true!(remaining.is_empty())?;
336            }
337
338            Ok(())
339        }
340    };
341    (ast, $check_fn:ident, $fix_fn:ident) => {
342        #[$crate::_private::gtest]
343        fn fixes() -> $crate::_private::TestResult {
344            for (i, ex) in EXAMPLES.iter().enumerate() {
345                if ex.pass {
346                    continue;
347                }
348                let fixed = $crate::apply_ast_fixes(ex.code, $check_fn, $fix_fn);
349                let remaining = run(&fixed);
350                $crate::_private::scoped_trace!("example {i} ({:?})", ex.label);
351                $crate::_private::verify_true!(remaining.is_empty())?;
352            }
353
354            Ok(())
355        }
356    };
357    (ast_tree, $check_fn:ident, $fix_fn:ident) => {
358        #[$crate::_private::gtest]
359        fn fixes() -> $crate::_private::TestResult {
360            for (i, ex) in EXAMPLES.iter().enumerate() {
361                if ex.pass {
362                    continue;
363                }
364                let fixed = $crate::apply_ast_tree_fix(ex.code, $check_fn, $fix_fn);
365                let remaining = run(&fixed);
366                $crate::_private::scoped_trace!("example {i} ({:?})", ex.label);
367                $crate::_private::verify_true!(remaining.is_empty())?;
368            }
369
370            Ok(())
371        }
372    };
373}