Skip to main content

alint_core/
rule.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use crate::error::Result;
5use crate::facts::FactValues;
6use crate::level::Level;
7use crate::registry::RuleRegistry;
8use crate::walker::FileIndex;
9
10/// A single linting violation produced by a rule.
11#[derive(Debug, Clone)]
12pub struct Violation {
13    pub path: Option<PathBuf>,
14    pub message: String,
15    pub line: Option<usize>,
16    pub column: Option<usize>,
17}
18
19impl Violation {
20    pub fn new(message: impl Into<String>) -> Self {
21        Self {
22            path: None,
23            message: message.into(),
24            line: None,
25            column: None,
26        }
27    }
28
29    #[must_use]
30    pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
31        self.path = Some(path.into());
32        self
33    }
34
35    #[must_use]
36    pub fn with_location(mut self, line: usize, column: usize) -> Self {
37        self.line = Some(line);
38        self.column = Some(column);
39        self
40    }
41}
42
43/// The collected outcome of evaluating a single rule.
44#[derive(Debug, Clone)]
45pub struct RuleResult {
46    pub rule_id: String,
47    pub level: Level,
48    pub policy_url: Option<String>,
49    pub violations: Vec<Violation>,
50}
51
52impl RuleResult {
53    pub fn passed(&self) -> bool {
54        self.violations.is_empty()
55    }
56}
57
58/// Execution context handed to each rule during evaluation.
59///
60/// - `registry` — available for rules that need to build and evaluate nested
61///   rules at runtime (e.g. `for_each_dir`). Tests that don't exercise
62///   nested evaluation can set this to `None`.
63/// - `facts` — resolved fact values, computed once per `Engine::run`.
64/// - `vars` — user-supplied string variables from the config's `vars:` section.
65#[derive(Debug)]
66pub struct Context<'a> {
67    pub root: &'a Path,
68    pub index: &'a FileIndex,
69    pub registry: Option<&'a RuleRegistry>,
70    pub facts: Option<&'a FactValues>,
71    pub vars: Option<&'a HashMap<String, String>>,
72}
73
74/// Trait every built-in and plugin rule implements.
75pub trait Rule: Send + Sync + std::fmt::Debug {
76    fn id(&self) -> &str;
77    fn level(&self) -> Level;
78    fn policy_url(&self) -> Option<&str> {
79        None
80    }
81    fn evaluate(&self, ctx: &Context<'_>) -> Result<Vec<Violation>>;
82
83    /// Optional automatic-fix strategy. Rules whose violations can be
84    /// mechanically corrected (e.g. creating a missing file, removing a
85    /// forbidden one, renaming to the correct case) return a
86    /// [`Fixer`] here; the default implementation reports the rule as
87    /// unfixable.
88    fn fixer(&self) -> Option<&dyn Fixer> {
89        None
90    }
91}
92
93/// Runtime context for applying a fix.
94#[derive(Debug)]
95pub struct FixContext<'a> {
96    pub root: &'a Path,
97    /// When true, fixers must describe what they would do without
98    /// touching the filesystem.
99    pub dry_run: bool,
100}
101
102/// The result of applying (or simulating) one fix against one violation.
103#[derive(Debug, Clone)]
104pub enum FixOutcome {
105    /// The fix was applied (or would be, under `dry_run`). The string
106    /// is a human-readable one-liner — e.g. `"created LICENSE"`,
107    /// `"would remove target/debug.log"`.
108    Applied(String),
109    /// The fixer intentionally did nothing; the string explains why
110    /// (e.g. `"already exists"`, `"no path on violation"`). This is
111    /// distinct from a hard error returned via `Result::Err`.
112    Skipped(String),
113}
114
115/// A mechanical corrector for a specific rule's violations.
116pub trait Fixer: Send + Sync + std::fmt::Debug {
117    /// Short human-readable summary of what this fixer does,
118    /// independent of any specific violation.
119    fn describe(&self) -> String;
120
121    /// Apply the fix against a single violation.
122    fn apply(&self, violation: &Violation, ctx: &FixContext<'_>) -> Result<FixOutcome>;
123}