syncable_cli/analyzer/helmlint/rules/
hl3xxx.rs1use crate::analyzer::helmlint::rules::{LintContext, Rule};
6use crate::analyzer::helmlint::types::{CheckFailure, RuleCategory, Severity};
7
8pub fn rules() -> Vec<Box<dyn Rule>> {
10 vec![
11 Box::new(HL3001),
12 Box::new(HL3002),
13 Box::new(HL3004),
14 Box::new(HL3005),
15 Box::new(HL3006),
16 Box::new(HL3007),
17 Box::new(HL3008),
18 Box::new(HL3009),
19 Box::new(HL3010),
20 Box::new(HL3011),
21 ]
22}
23
24pub struct HL3001;
26
27impl Rule for HL3001 {
28 fn code(&self) -> &'static str {
29 "HL3001"
30 }
31
32 fn severity(&self) -> Severity {
33 Severity::Error
34 }
35
36 fn name(&self) -> &'static str {
37 "unclosed-action"
38 }
39
40 fn description(&self) -> &'static str {
41 "Template has unclosed action (missing }})"
42 }
43
44 fn check(&self, ctx: &LintContext) -> Vec<CheckFailure> {
45 let mut failures = Vec::new();
46
47 for template in ctx.templates {
48 for error in &template.errors {
49 if error.message.contains("Unclosed template action") {
50 failures.push(CheckFailure::new(
51 "HL3001",
52 Severity::Error,
53 "Unclosed template action (missing }})".to_string(),
54 &template.path,
55 error.line,
56 RuleCategory::Template,
57 ));
58 }
59 }
60 }
61
62 failures
63 }
64}
65
66pub struct HL3002;
68
69impl Rule for HL3002 {
70 fn code(&self) -> &'static str {
71 "HL3002"
72 }
73
74 fn severity(&self) -> Severity {
75 Severity::Error
76 }
77
78 fn name(&self) -> &'static str {
79 "unclosed-block"
80 }
81
82 fn description(&self) -> &'static str {
83 "Template has unclosed control block (if/range/with)"
84 }
85
86 fn check(&self, ctx: &LintContext) -> Vec<CheckFailure> {
87 let mut failures = Vec::new();
88
89 for template in ctx.templates {
90 for (structure, line) in &template.unclosed_blocks {
91 failures.push(CheckFailure::new(
92 "HL3002",
93 Severity::Error,
94 format!("Unclosed {:?} block (missing {{{{- end }}}}))", structure),
95 &template.path,
96 *line,
97 RuleCategory::Template,
98 ));
99 }
100 }
101
102 failures
103 }
104}
105
106pub struct HL3004;
108
109impl Rule for HL3004 {
110 fn code(&self) -> &'static str {
111 "HL3004"
112 }
113
114 fn severity(&self) -> Severity {
115 Severity::Error
116 }
117
118 fn name(&self) -> &'static str {
119 "missing-end"
120 }
121
122 fn description(&self) -> &'static str {
123 "Control structure is missing closing 'end'"
124 }
125
126 fn check(&self, ctx: &LintContext) -> Vec<CheckFailure> {
127 let mut failures = Vec::new();
129
130 for template in ctx.templates {
131 for error in &template.errors {
132 if error.message.contains("Unclosed") && error.message.contains("block") {
133 failures.push(CheckFailure::new(
134 "HL3004",
135 Severity::Error,
136 error.message.clone(),
137 &template.path,
138 error.line,
139 RuleCategory::Template,
140 ));
141 }
142 }
143 }
144
145 failures
146 }
147}
148
149pub struct HL3005;
151
152impl Rule for HL3005 {
153 fn code(&self) -> &'static str {
154 "HL3005"
155 }
156
157 fn severity(&self) -> Severity {
158 Severity::Warning
159 }
160
161 fn name(&self) -> &'static str {
162 "deprecated-function"
163 }
164
165 fn description(&self) -> &'static str {
166 "Template uses deprecated function"
167 }
168
169 fn check(&self, ctx: &LintContext) -> Vec<CheckFailure> {
170 let deprecated_functions = [
171 ("dateInZone", "Use 'mustDateModify' instead"),
172 ("genCA", "Use 'genSelfSignedCert' for better control"),
173 ];
174
175 let mut failures = Vec::new();
176
177 for template in ctx.templates {
178 for (func, suggestion) in &deprecated_functions {
179 if template.calls_function(func) {
180 failures.push(CheckFailure::new(
181 "HL3005",
182 Severity::Warning,
183 format!("Function '{}' is deprecated. {}", func, suggestion),
184 &template.path,
185 1, RuleCategory::Template,
187 ));
188 }
189 }
190 }
191
192 failures
193 }
194}
195
196pub struct HL3006;
198
199impl Rule for HL3006 {
200 fn code(&self) -> &'static str {
201 "HL3006"
202 }
203
204 fn severity(&self) -> Severity {
205 Severity::Warning
206 }
207
208 fn name(&self) -> &'static str {
209 "potential-nil"
210 }
211
212 fn description(&self) -> &'static str {
213 "Value access may fail if value is nil. Consider using 'default'"
214 }
215
216 fn check(&self, ctx: &LintContext) -> Vec<CheckFailure> {
217 let failures = Vec::new();
219
220 for template in ctx.templates {
221 for var in &template.variables_used {
223 if var.starts_with(".Values.") {
224 let parts: Vec<&str> = var.split('.').collect();
225 if parts.len() > 4 && !template.calls_function("default") {
227 }
230 }
231 }
232 }
233
234 failures
235 }
236}
237
238pub struct HL3007;
240
241impl Rule for HL3007 {
242 fn code(&self) -> &'static str {
243 "HL3007"
244 }
245
246 fn severity(&self) -> Severity {
247 Severity::Warning
248 }
249
250 fn name(&self) -> &'static str {
251 "invalid-template-extension"
252 }
253
254 fn description(&self) -> &'static str {
255 "Template file should have .yaml, .yml, or .tpl extension"
256 }
257
258 fn check(&self, ctx: &LintContext) -> Vec<CheckFailure> {
259 let valid_extensions = [".yaml", ".yml", ".tpl", ".txt"];
260 let mut failures = Vec::new();
261
262 for file in ctx.files {
263 if file.contains("templates/") && !file.contains("templates/tests/") {
264 let has_valid_ext = valid_extensions.iter().any(|ext| file.ends_with(ext));
265 let is_helper = file.contains("_helpers");
266 let is_notes = file.contains("NOTES.txt");
267
268 if !has_valid_ext && !is_helper && !is_notes && !file.ends_with('/') {
269 failures.push(CheckFailure::new(
270 "HL3007",
271 Severity::Warning,
272 format!("Template file '{}' has unexpected extension", file),
273 file,
274 1,
275 RuleCategory::Template,
276 ));
277 }
278 }
279 }
280
281 failures
282 }
283}
284
285pub struct HL3008;
287
288impl Rule for HL3008 {
289 fn code(&self) -> &'static str {
290 "HL3008"
291 }
292
293 fn severity(&self) -> Severity {
294 Severity::Info
295 }
296
297 fn name(&self) -> &'static str {
298 "missing-notes"
299 }
300
301 fn description(&self) -> &'static str {
302 "Chart should have a NOTES.txt for post-install instructions"
303 }
304
305 fn check(&self, ctx: &LintContext) -> Vec<CheckFailure> {
306 if let Some(chart) = ctx.chart_metadata
308 && chart.is_library()
309 {
310 return vec![];
311 }
312
313 let has_notes = ctx.files.iter().any(|f| f.ends_with("NOTES.txt"));
314 if !has_notes {
315 return vec![CheckFailure::new(
316 "HL3008",
317 Severity::Info,
318 "Chart is missing templates/NOTES.txt for post-install instructions",
319 "templates/NOTES.txt",
320 1,
321 RuleCategory::Template,
322 )];
323 }
324
325 vec![]
326 }
327}
328
329pub struct HL3009;
331
332impl Rule for HL3009 {
333 fn code(&self) -> &'static str {
334 "HL3009"
335 }
336
337 fn severity(&self) -> Severity {
338 Severity::Info
339 }
340
341 fn name(&self) -> &'static str {
342 "helper-missing-comment"
343 }
344
345 fn description(&self) -> &'static str {
346 "Helper template should have a description comment"
347 }
348
349 fn check(&self, ctx: &LintContext) -> Vec<CheckFailure> {
350 let mut failures = Vec::new();
351
352 if let Some(helpers) = ctx.helpers {
353 for helper in &helpers.helpers {
354 if helper.doc_comment.is_none() {
355 failures.push(CheckFailure::new(
356 "HL3009",
357 Severity::Info,
358 format!("Helper '{}' is missing a description comment", helper.name),
359 &helpers.path,
360 helper.line,
361 RuleCategory::Template,
362 ));
363 }
364 }
365 }
366
367 failures
368 }
369}
370
371pub struct HL3010;
373
374impl Rule for HL3010 {
375 fn code(&self) -> &'static str {
376 "HL3010"
377 }
378
379 fn severity(&self) -> Severity {
380 Severity::Info
381 }
382
383 fn name(&self) -> &'static str {
384 "unused-helper"
385 }
386
387 fn description(&self) -> &'static str {
388 "Helper template is defined but never used"
389 }
390
391 fn check(&self, ctx: &LintContext) -> Vec<CheckFailure> {
392 let mut failures = Vec::new();
393
394 let helpers = match ctx.helpers {
395 Some(h) => h,
396 None => return failures,
397 };
398
399 let referenced = ctx.template_references();
400
401 for helper in &helpers.helpers {
402 if !referenced.contains(helper.name.as_str()) {
403 let used_in_helpers = helpers
405 .helpers
406 .iter()
407 .any(|h| h.name != helper.name && h.content.contains(&helper.name));
408
409 if !used_in_helpers {
410 failures.push(CheckFailure::new(
411 "HL3010",
412 Severity::Info,
413 format!("Helper '{}' is defined but never used", helper.name),
414 &helpers.path,
415 helper.line,
416 RuleCategory::Template,
417 ));
418 }
419 }
420 }
421
422 failures
423 }
424}
425
426pub struct HL3011;
428
429impl Rule for HL3011 {
430 fn code(&self) -> &'static str {
431 "HL3011"
432 }
433
434 fn severity(&self) -> Severity {
435 Severity::Error
436 }
437
438 fn name(&self) -> &'static str {
439 "include-not-found"
440 }
441
442 fn description(&self) -> &'static str {
443 "Template includes a helper that is not defined"
444 }
445
446 fn check(&self, ctx: &LintContext) -> Vec<CheckFailure> {
447 let mut failures = Vec::new();
448
449 let defined_helpers: std::collections::HashSet<&str> =
450 ctx.helper_names().into_iter().collect();
451 let referenced = ctx.template_references();
452
453 for ref_name in referenced {
454 if !defined_helpers.contains(ref_name) {
455 for template in ctx.templates {
457 if template.referenced_templates.contains(ref_name) {
458 failures.push(CheckFailure::new(
459 "HL3011",
460 Severity::Error,
461 format!("Template includes '{}' which is not defined", ref_name),
462 &template.path,
463 1,
464 RuleCategory::Template,
465 ));
466 break;
467 }
468 }
469 }
470 }
471
472 failures
473 }
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479
480 #[test]
484 fn test_rules_exist() {
485 let all_rules = rules();
486 assert!(!all_rules.is_empty());
487 }
488}