ailint_core/rules/
registry.rs1use std::collections::HashMap;
4use std::sync::OnceLock;
5
6use crate::config::Config;
7use crate::parser::ParsedDocument;
8use crate::rules::{consistency, security, semantic, structural};
9use crate::rules::{BatchRule, Rule, RuleContext, RuleId, Severity, Violation};
10
11#[derive(Debug, Clone, Copy)]
13pub struct RuleMeta {
14 pub description: &'static str,
16 pub fix_hint: &'static str,
18 pub default_severity: Severity,
20}
21
22pub fn rule_meta(id: RuleId) -> Option<RuleMeta> {
25 rule_meta_cache().get(&id).copied()
26}
27
28fn rule_meta_cache() -> &'static HashMap<RuleId, RuleMeta> {
29 static CACHE: OnceLock<HashMap<RuleId, RuleMeta>> = OnceLock::new();
30 CACHE.get_or_init(|| {
31 let mut m = HashMap::new();
32 for r in all_rules() {
33 m.insert(
34 r.id(),
35 RuleMeta {
36 description: r.description(),
37 fix_hint: r.fix_hint(),
38 default_severity: r.default_severity(),
39 },
40 );
41 }
42 for r in all_batch_rules() {
43 m.insert(
44 r.id(),
45 RuleMeta {
46 description: r.description(),
47 fix_hint: r.fix_hint(),
48 default_severity: r.default_severity(),
49 },
50 );
51 }
52 m
53 })
54}
55
56pub fn all_rules() -> Vec<Box<dyn Rule>> {
58 let mut out: Vec<Box<dyn Rule>> = Vec::new();
59 out.extend(structural::all_rules());
60 out.extend(semantic::all_rules());
61 out.extend(security::all_rules());
62 out
63}
64
65pub fn all_batch_rules() -> Vec<Box<dyn BatchRule>> {
67 consistency::all_batch_rules()
68}
69
70pub fn run_all(doc: &ParsedDocument, config: &Config) -> Vec<Violation> {
72 let mut out = Vec::new();
73 for rule in all_rules() {
74 let id = rule.id();
75 if is_disabled(&id, &config.rules.disabled) {
76 continue;
77 }
78 if !rule.applies_to(doc.file_type) {
79 continue;
80 }
81 let severity = effective_severity(
82 &id,
83 rule.default_severity(),
84 &config.rules.severity_overrides,
85 );
86 let options = lookup_options(&id, &config.rules.options);
87 let ctx = RuleContext {
88 config,
89 options,
90 severity,
91 };
92 let mut vs = rule.run(doc, &ctx);
93 for v in &mut vs {
94 v.severity = severity;
95 }
96 out.extend(vs);
97 }
98 out
99}
100
101pub fn run_all_batch(docs: &[ParsedDocument], config: &Config) -> Vec<Violation> {
103 let mut out = Vec::new();
104 for rule in all_batch_rules() {
105 let id = rule.id();
106 if is_disabled(&id, &config.rules.disabled) {
107 continue;
108 }
109 let severity = effective_severity(
110 &id,
111 rule.default_severity(),
112 &config.rules.severity_overrides,
113 );
114 let options = lookup_options(&id, &config.rules.options);
115 let ctx = RuleContext {
116 config,
117 options,
118 severity,
119 };
120 let filtered: Vec<ParsedDocument> = docs
121 .iter()
122 .filter(|d| rule.applies_to(d.file_type))
123 .cloned()
124 .collect();
125 let mut vs = rule.run_batch(&filtered, &ctx);
126 for v in &mut vs {
127 v.severity = severity;
128 }
129 out.extend(vs);
130 }
131 out
132}
133
134fn is_disabled(id: &RuleId, disabled: &[String]) -> bool {
135 let code_str = id.code_str();
136 disabled.iter().any(|d| d == &code_str || d == id.slug)
137}
138
139fn effective_severity(
140 id: &RuleId,
141 default: Severity,
142 overrides: &std::collections::BTreeMap<String, Severity>,
143) -> Severity {
144 let code_str = id.code_str();
145 overrides
146 .get(&code_str)
147 .or_else(|| overrides.get(id.slug))
148 .copied()
149 .unwrap_or(default)
150}
151
152fn lookup_options<'a>(
153 id: &RuleId,
154 options: &'a std::collections::BTreeMap<String, serde_yaml::Value>,
155) -> Option<&'a serde_yaml::Value> {
156 options.get(id.slug).or_else(|| options.get(&id.code_str()))
157}