syncable_cli/analyzer/dclint/rules/
mod.rs1use crate::analyzer::dclint::parser::ComposeFile;
10use crate::analyzer::dclint::types::{CheckFailure, RuleCategory, RuleCode, RuleMeta, Severity};
11
12pub mod dcl001;
14pub mod dcl002;
15pub mod dcl003;
16pub mod dcl004;
17pub mod dcl005;
18pub mod dcl006;
19pub mod dcl007;
20pub mod dcl008;
21pub mod dcl009;
22pub mod dcl010;
23pub mod dcl011;
24pub mod dcl012;
25pub mod dcl013;
26pub mod dcl014;
27pub mod dcl015;
28
29#[derive(Debug, Clone)]
31pub struct LintContext<'a> {
32 pub compose: &'a ComposeFile,
34 pub source: &'a str,
36 pub path: &'a str,
38}
39
40impl<'a> LintContext<'a> {
41 pub fn new(compose: &'a ComposeFile, source: &'a str, path: &'a str) -> Self {
42 Self {
43 compose,
44 source,
45 path,
46 }
47 }
48}
49
50pub trait Rule: Send + Sync {
52 fn code(&self) -> &RuleCode;
54
55 fn name(&self) -> &str;
57
58 fn severity(&self) -> Severity;
60
61 fn category(&self) -> RuleCategory;
63
64 fn meta(&self) -> &RuleMeta;
66
67 fn is_fixable(&self) -> bool {
69 false
70 }
71
72 fn check(&self, context: &LintContext) -> Vec<CheckFailure>;
74
75 fn fix(&self, _source: &str) -> Option<String> {
78 None
79 }
80
81 fn get_message(&self, _details: &std::collections::HashMap<String, String>) -> String {
83 self.meta().description.clone()
84 }
85}
86
87pub struct SimpleRule<F>
89where
90 F: Fn(&LintContext) -> Vec<CheckFailure> + Send + Sync,
91{
92 code: RuleCode,
93 name: String,
94 severity: Severity,
95 category: RuleCategory,
96 meta: RuleMeta,
97 check_fn: F,
98}
99
100impl<F> SimpleRule<F>
101where
102 F: Fn(&LintContext) -> Vec<CheckFailure> + Send + Sync,
103{
104 pub fn new(
105 code: impl Into<RuleCode>,
106 name: impl Into<String>,
107 severity: Severity,
108 category: RuleCategory,
109 description: impl Into<String>,
110 url: impl Into<String>,
111 check_fn: F,
112 ) -> Self {
113 Self {
114 code: code.into(),
115 name: name.into(),
116 severity,
117 category,
118 meta: RuleMeta::new(description, url),
119 check_fn,
120 }
121 }
122}
123
124impl<F> Rule for SimpleRule<F>
125where
126 F: Fn(&LintContext) -> Vec<CheckFailure> + Send + Sync,
127{
128 fn code(&self) -> &RuleCode {
129 &self.code
130 }
131
132 fn name(&self) -> &str {
133 &self.name
134 }
135
136 fn severity(&self) -> Severity {
137 self.severity
138 }
139
140 fn category(&self) -> RuleCategory {
141 self.category
142 }
143
144 fn meta(&self) -> &RuleMeta {
145 &self.meta
146 }
147
148 fn check(&self, context: &LintContext) -> Vec<CheckFailure> {
149 (self.check_fn)(context)
150 }
151}
152
153pub struct FixableRule<C, X>
155where
156 C: Fn(&LintContext) -> Vec<CheckFailure> + Send + Sync,
157 X: Fn(&str) -> Option<String> + Send + Sync,
158{
159 code: RuleCode,
160 name: String,
161 severity: Severity,
162 category: RuleCategory,
163 meta: RuleMeta,
164 check_fn: C,
165 fix_fn: X,
166}
167
168impl<C, X> FixableRule<C, X>
169where
170 C: Fn(&LintContext) -> Vec<CheckFailure> + Send + Sync,
171 X: Fn(&str) -> Option<String> + Send + Sync,
172{
173 pub fn new(
174 code: impl Into<RuleCode>,
175 name: impl Into<String>,
176 severity: Severity,
177 category: RuleCategory,
178 description: impl Into<String>,
179 url: impl Into<String>,
180 check_fn: C,
181 fix_fn: X,
182 ) -> Self {
183 Self {
184 code: code.into(),
185 name: name.into(),
186 severity,
187 category,
188 meta: RuleMeta::new(description, url),
189 check_fn,
190 fix_fn,
191 }
192 }
193}
194
195impl<C, X> Rule for FixableRule<C, X>
196where
197 C: Fn(&LintContext) -> Vec<CheckFailure> + Send + Sync,
198 X: Fn(&str) -> Option<String> + Send + Sync,
199{
200 fn code(&self) -> &RuleCode {
201 &self.code
202 }
203
204 fn name(&self) -> &str {
205 &self.name
206 }
207
208 fn severity(&self) -> Severity {
209 self.severity
210 }
211
212 fn category(&self) -> RuleCategory {
213 self.category
214 }
215
216 fn meta(&self) -> &RuleMeta {
217 &self.meta
218 }
219
220 fn is_fixable(&self) -> bool {
221 true
222 }
223
224 fn check(&self, context: &LintContext) -> Vec<CheckFailure> {
225 (self.check_fn)(context)
226 }
227
228 fn fix(&self, source: &str) -> Option<String> {
229 (self.fix_fn)(source)
230 }
231}
232
233pub fn make_failure(
235 code: &RuleCode,
236 name: &str,
237 severity: Severity,
238 category: RuleCategory,
239 message: impl Into<String>,
240 line: u32,
241 column: u32,
242 fixable: bool,
243) -> CheckFailure {
244 CheckFailure::new(
245 code.clone(),
246 name,
247 severity,
248 category,
249 message,
250 line,
251 column,
252 )
253 .with_fixable(fixable)
254}
255
256pub fn all_rules() -> Vec<Box<dyn Rule>> {
258 vec![
259 Box::new(dcl001::rule()),
260 Box::new(dcl002::rule()),
261 Box::new(dcl003::rule()),
262 Box::new(dcl004::rule()),
263 Box::new(dcl005::rule()),
264 Box::new(dcl006::rule()),
265 Box::new(dcl007::rule()),
266 Box::new(dcl008::rule()),
267 Box::new(dcl009::rule()),
268 Box::new(dcl010::rule()),
269 Box::new(dcl011::rule()),
270 Box::new(dcl012::rule()),
271 Box::new(dcl013::rule()),
272 Box::new(dcl014::rule()),
273 Box::new(dcl015::rule()),
274 ]
275}
276
277pub fn rule_definitions() -> Vec<RuleDefinition> {
279 all_rules()
280 .iter()
281 .map(|r| RuleDefinition {
282 code: r.code().clone(),
283 name: r.name().to_string(),
284 severity: r.severity(),
285 category: r.category(),
286 description: r.meta().description.clone(),
287 url: r.meta().url.clone(),
288 fixable: r.is_fixable(),
289 })
290 .collect()
291}
292
293#[derive(Debug, Clone)]
295pub struct RuleDefinition {
296 pub code: RuleCode,
297 pub name: String,
298 pub severity: Severity,
299 pub category: RuleCategory,
300 pub description: String,
301 pub url: String,
302 pub fixable: bool,
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn test_all_rules_count() {
311 let rules = all_rules();
312 assert_eq!(rules.len(), 15, "Expected 15 rules");
313 }
314
315 #[test]
316 fn test_rule_codes_unique() {
317 let rules = all_rules();
318 let mut codes: Vec<String> = rules.iter().map(|r| r.code().to_string()).collect();
319 codes.sort();
320 codes.dedup();
321 assert_eq!(codes.len(), 15, "Rule codes should be unique");
322 }
323
324 #[test]
325 fn test_rule_names_unique() {
326 let rules = all_rules();
327 let mut names: Vec<String> = rules.iter().map(|r| r.name().to_string()).collect();
328 names.sort();
329 names.dedup();
330 assert_eq!(names.len(), 15, "Rule names should be unique");
331 }
332}