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 if ignored.contains(code) {
38 return true;
39 }
40 }
41
42 if line > 0 {
44 if let Some(ignored) = self.ignored.get(&(line - 1)) {
45 if ignored.contains(code) {
46 return true;
47 }
48 }
49 }
50
51 false
52 }
53}
54
55pub fn parse_pragma(comment: &str) -> Option<Pragma> {
58 let comment = comment.trim();
59
60 let pragma_start = comment.find("hadolint")?;
62 let pragma_content = &comment[pragma_start + "hadolint".len()..].trim();
63
64 if pragma_content.starts_with("global") {
66 let rest = &pragma_content["global".len()..].trim();
67 if let Some(codes) = parse_ignore_list(rest) {
68 return Some(Pragma::GlobalIgnore(codes));
69 }
70 }
71
72 if let Some(codes) = parse_ignore_list(pragma_content) {
74 return Some(Pragma::Ignore(codes));
75 }
76
77 if pragma_content.starts_with("shell=") {
79 let shell = &pragma_content["shell=".len()..].trim();
80 return Some(Pragma::Shell(shell.to_string()));
81 }
82
83 None
84}
85
86fn parse_ignore_list(s: &str) -> Option<Vec<RuleCode>> {
88 let s = s.trim();
89
90 if !s.starts_with("ignore=") && !s.starts_with("ignore =") {
92 return None;
93 }
94
95 let eq_pos = s.find('=')?;
97 let codes_str = &s[eq_pos + 1..].trim();
98
99 let codes: Vec<RuleCode> = codes_str
101 .split(',')
102 .map(|s| s.trim())
103 .filter(|s| !s.is_empty())
104 .map(|s| RuleCode::new(s))
105 .collect();
106
107 if codes.is_empty() {
108 None
109 } else {
110 Some(codes)
111 }
112}
113
114#[derive(Debug, Clone)]
116pub enum Pragma {
117 Ignore(Vec<RuleCode>),
119 GlobalIgnore(Vec<RuleCode>),
121 Shell(String),
123}
124
125pub fn extract_pragmas(instructions: &[crate::analyzer::hadolint::parser::InstructionPos]) -> PragmaState {
127 let mut state = PragmaState::new();
128
129 for instr in instructions {
130 if let crate::analyzer::hadolint::parser::instruction::Instruction::Comment(comment) = &instr.instruction {
131 if let Some(pragma) = parse_pragma(comment) {
132 match pragma {
133 Pragma::Ignore(codes) => {
134 let entry = state.ignored.entry(instr.line_number).or_default();
136 for code in codes {
137 entry.insert(code);
138 }
139 }
140 Pragma::GlobalIgnore(codes) => {
141 for code in codes {
142 state.global_ignored.insert(code);
143 }
144 }
145 Pragma::Shell(shell) => {
146 state.shell = Some(shell);
147 }
148 }
149 }
150 }
151 }
152
153 state
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_parse_ignore() {
162 let pragma = parse_pragma("# hadolint ignore=DL3008,DL3009").unwrap();
163 match pragma {
164 Pragma::Ignore(codes) => {
165 assert_eq!(codes.len(), 2);
166 assert_eq!(codes[0].as_str(), "DL3008");
167 assert_eq!(codes[1].as_str(), "DL3009");
168 }
169 _ => panic!("Expected Ignore pragma"),
170 }
171 }
172
173 #[test]
174 fn test_parse_global_ignore() {
175 let pragma = parse_pragma("# hadolint global ignore=DL3008").unwrap();
176 match pragma {
177 Pragma::GlobalIgnore(codes) => {
178 assert_eq!(codes.len(), 1);
179 assert_eq!(codes[0].as_str(), "DL3008");
180 }
181 _ => panic!("Expected GlobalIgnore pragma"),
182 }
183 }
184
185 #[test]
186 fn test_parse_shell() {
187 let pragma = parse_pragma("# hadolint shell=/bin/bash").unwrap();
188 match pragma {
189 Pragma::Shell(shell) => {
190 assert_eq!(shell, "/bin/bash");
191 }
192 _ => panic!("Expected Shell pragma"),
193 }
194 }
195
196 #[test]
197 fn test_no_pragma() {
198 assert!(parse_pragma("# This is a regular comment").is_none());
199 }
200
201 #[test]
202 fn test_pragma_state_is_ignored() {
203 let mut state = PragmaState::new();
204
205 let mut codes = HashSet::new();
207 codes.insert(RuleCode::new("DL3008"));
208 state.ignored.insert(5, codes);
209
210 state.global_ignored.insert(RuleCode::new("DL3009"));
212
213 assert!(state.is_ignored(&RuleCode::new("DL3008"), 6));
215 assert!(!state.is_ignored(&RuleCode::new("DL3008"), 10));
216
217 assert!(state.is_ignored(&RuleCode::new("DL3009"), 1));
219 assert!(state.is_ignored(&RuleCode::new("DL3009"), 100));
220
221 assert!(!state.is_ignored(&RuleCode::new("DL3010"), 1));
223 }
224}