syncable_cli/analyzer/hadolint/
pragma.rs1use crate::analyzer::hadolint::types::RuleCode;
9use std::collections::{HashMap, HashSet};
10
11#[derive(Debug, Clone, Default)]
13pub struct PragmaState {
14 pub ignored: HashMap<u32, HashSet<RuleCode>>,
16 pub global_ignored: HashSet<RuleCode>,
18 pub shell: Option<String>,
20}
21
22impl PragmaState {
23 pub fn new() -> Self {
25 Self::default()
26 }
27
28 pub fn is_ignored(&self, code: &RuleCode, line: u32) -> bool {
30 if self.global_ignored.contains(code) {
32 return true;
33 }
34
35 if let Some(ignored) = self.ignored.get(&line)
37 && ignored.contains(code)
38 {
39 return true;
40 }
41
42 if line > 0
44 && let Some(ignored) = self.ignored.get(&(line - 1))
45 && ignored.contains(code)
46 {
47 return true;
48 }
49
50 false
51 }
52}
53
54pub fn parse_pragma(comment: &str) -> Option<Pragma> {
57 let comment = comment.trim();
58
59 let pragma_start = comment.find("hadolint")?;
61 let pragma_content = &comment[pragma_start + "hadolint".len()..].trim();
62
63 if let Some(rest) = pragma_content.strip_prefix("global") {
65 let rest = rest.trim();
66 if let Some(codes) = parse_ignore_list(rest) {
67 return Some(Pragma::GlobalIgnore(codes));
68 }
69 }
70
71 if let Some(codes) = parse_ignore_list(pragma_content) {
73 return Some(Pragma::Ignore(codes));
74 }
75
76 if let Some(shell) = pragma_content.strip_prefix("shell=") {
78 let shell = shell.trim();
79 return Some(Pragma::Shell(shell.to_string()));
80 }
81
82 None
83}
84
85fn parse_ignore_list(s: &str) -> Option<Vec<RuleCode>> {
87 let s = s.trim();
88
89 if !s.starts_with("ignore=") && !s.starts_with("ignore =") {
91 return None;
92 }
93
94 let eq_pos = s.find('=')?;
96 let codes_str = &s[eq_pos + 1..].trim();
97
98 let codes: Vec<RuleCode> = codes_str
100 .split(',')
101 .map(|s| s.trim())
102 .filter(|s| !s.is_empty())
103 .map(RuleCode::new)
104 .collect();
105
106 if codes.is_empty() { None } else { Some(codes) }
107}
108
109#[derive(Debug, Clone)]
111pub enum Pragma {
112 Ignore(Vec<RuleCode>),
114 GlobalIgnore(Vec<RuleCode>),
116 Shell(String),
118}
119
120pub fn extract_pragmas(
122 instructions: &[crate::analyzer::hadolint::parser::InstructionPos],
123) -> PragmaState {
124 let mut state = PragmaState::new();
125
126 for instr in instructions {
127 if let crate::analyzer::hadolint::parser::instruction::Instruction::Comment(comment) =
128 &instr.instruction
129 && let Some(pragma) = parse_pragma(comment)
130 {
131 match pragma {
132 Pragma::Ignore(codes) => {
133 let entry = state.ignored.entry(instr.line_number).or_default();
135 for code in codes {
136 entry.insert(code);
137 }
138 }
139 Pragma::GlobalIgnore(codes) => {
140 for code in codes {
141 state.global_ignored.insert(code);
142 }
143 }
144 Pragma::Shell(shell) => {
145 state.shell = Some(shell);
146 }
147 }
148 }
149 }
150
151 state
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_parse_ignore() {
160 let pragma = parse_pragma("# hadolint ignore=DL3008,DL3009").unwrap();
161 match pragma {
162 Pragma::Ignore(codes) => {
163 assert_eq!(codes.len(), 2);
164 assert_eq!(codes[0].as_str(), "DL3008");
165 assert_eq!(codes[1].as_str(), "DL3009");
166 }
167 _ => panic!("Expected Ignore pragma"),
168 }
169 }
170
171 #[test]
172 fn test_parse_global_ignore() {
173 let pragma = parse_pragma("# hadolint global ignore=DL3008").unwrap();
174 match pragma {
175 Pragma::GlobalIgnore(codes) => {
176 assert_eq!(codes.len(), 1);
177 assert_eq!(codes[0].as_str(), "DL3008");
178 }
179 _ => panic!("Expected GlobalIgnore pragma"),
180 }
181 }
182
183 #[test]
184 fn test_parse_shell() {
185 let pragma = parse_pragma("# hadolint shell=/bin/bash").unwrap();
186 match pragma {
187 Pragma::Shell(shell) => {
188 assert_eq!(shell, "/bin/bash");
189 }
190 _ => panic!("Expected Shell pragma"),
191 }
192 }
193
194 #[test]
195 fn test_no_pragma() {
196 assert!(parse_pragma("# This is a regular comment").is_none());
197 }
198
199 #[test]
200 fn test_pragma_state_is_ignored() {
201 let mut state = PragmaState::new();
202
203 let mut codes = HashSet::new();
205 codes.insert(RuleCode::new("DL3008"));
206 state.ignored.insert(5, codes);
207
208 state.global_ignored.insert(RuleCode::new("DL3009"));
210
211 assert!(state.is_ignored(&RuleCode::new("DL3008"), 6));
213 assert!(!state.is_ignored(&RuleCode::new("DL3008"), 10));
214
215 assert!(state.is_ignored(&RuleCode::new("DL3009"), 1));
217 assert!(state.is_ignored(&RuleCode::new("DL3009"), 100));
218
219 assert!(!state.is_ignored(&RuleCode::new("DL3010"), 1));
221 }
222}