pine_lint/pass.rs
1//! The lint-pass abstraction and the driver that runs every registered pass.
2
3use pine_ast::Program;
4
5use crate::passes;
6use pine_ast::visitor::Visitor;
7use pine_diagnostics::Diagnostic;
8
9/// A single check.
10///
11/// A pass is a [`Visitor`] (so it can walk the tree and collect findings as it
12/// goes) plus a way to hand back what it found. Most passes accumulate into a
13/// `Vec<Diagnostic>` field and return it from [`finish`](LintPass::finish); a
14/// pass that needs a whole-program view can instead ignore the visitor methods
15/// and do its work in `finish`.
16pub trait LintPass: Visitor {
17 /// The rule identifier, matching the `rule` field of the diagnostics it
18 /// emits (e.g. `"eq-na"`).
19 fn name(&self) -> &'static str;
20
21 /// Consume everything collected during the walk. Called once, after the
22 /// driver has run this pass over the program.
23 fn finish(&mut self) -> Vec<Diagnostic>;
24}
25
26/// Every built-in pass, freshly constructed. Add new checks here.
27fn all_passes() -> Vec<Box<dyn LintPass>> {
28 vec![
29 Box::new(passes::EqNa::default()),
30 Box::new(passes::ConstantCondition::default()),
31 Box::new(passes::RequestLookahead::default()),
32 Box::new(passes::CalcOnEveryTick::default()),
33 Box::new(passes::SecurityRepaint::default()),
34 ]
35}
36
37/// Run every built-in lint pass over `program` and return all findings, sorted
38/// by line for stable, readable output. Findings silenced by a `// @skip(...)`
39/// comment on the program are dropped.
40pub fn lint(program: &Program) -> Vec<Diagnostic> {
41 let suppressions = crate::suppress::Suppressions::from_comments(&program.comments);
42 lint_with(program, all_passes())
43 .into_iter()
44 .filter(|diagnostic| !suppressions.suppresses(diagnostic))
45 .collect()
46}
47
48/// Run a specific set of passes. Useful for tests that want to exercise one
49/// check in isolation.
50pub fn lint_with(program: &Program, mut passes: Vec<Box<dyn LintPass>>) -> Vec<Diagnostic> {
51 let mut diagnostics = Vec::new();
52 for pass in &mut passes {
53 pass.visit_program(program);
54 diagnostics.extend(pass.finish());
55 }
56 // Stable ordering: located findings by position, then unlocated, preserving
57 // the pass registration order within a position.
58 diagnostics.sort_by_key(|d| d.pos.unwrap_or((u32::MAX, u32::MAX)));
59 diagnostics
60}