Skip to main content

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}