padlock_source/frontends/
suppress.rs1pub fn extract_suppressed_kinds(line: &str) -> Vec<String> {
24 let body = line
26 .trim()
27 .trim_start_matches("///")
28 .trim_start_matches("//")
29 .trim_start_matches("/*")
30 .trim_start_matches('*')
31 .trim();
32
33 let rest = if let Some(r) = body.strip_prefix("padlock:") {
35 r.trim_start()
36 } else {
37 return Vec::new();
38 };
39
40 let rest = if let Some(r) = rest.strip_prefix("ignore[") {
41 r
42 } else {
43 return Vec::new();
44 };
45
46 if let Some(end) = rest.find(']') {
47 rest[..end]
48 .split(',')
49 .map(|s| s.trim().to_string())
50 .filter(|s| !s.is_empty())
51 .collect()
52 } else {
53 Vec::new()
54 }
55}
56
57pub fn suppressed_from_preceding_source(source: &str, node_start_byte: usize) -> Vec<String> {
63 let before = &source[..node_start_byte.min(source.len())];
64 let mut result = Vec::new();
65 let mut found_any_comment = false;
66 for line in before.lines().rev() {
67 let trimmed = line.trim();
68 if trimmed.is_empty() {
69 continue; }
71 if trimmed.starts_with("//") || trimmed.starts_with("/*") || trimmed.starts_with('*') {
72 let kinds = extract_suppressed_kinds(trimmed);
73 if !kinds.is_empty() {
74 result.extend(kinds);
75 found_any_comment = true;
76 continue;
77 }
78 break;
81 }
82 break; }
84 let _ = found_any_comment;
85 result
86}
87
88pub fn suppressed_from_source_line(source: &str, struct_line: u32) -> Vec<String> {
94 if struct_line == 0 {
95 return Vec::new();
96 }
97 let mut line_start = 0usize;
99 for (i, line) in source.lines().enumerate() {
100 if i + 1 == struct_line as usize {
101 break;
102 }
103 line_start += line.len() + 1; }
105 suppressed_from_preceding_source(source, line_start)
106}
107
108#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn parses_single_kind() {
116 let kinds = extract_suppressed_kinds("// padlock: ignore[ReorderSuggestion]");
117 assert_eq!(kinds, vec!["ReorderSuggestion"]);
118 }
119
120 #[test]
121 fn parses_multiple_kinds() {
122 let kinds = extract_suppressed_kinds("// padlock: ignore[PaddingWaste, FalseSharing]");
123 assert_eq!(kinds, vec!["PaddingWaste", "FalseSharing"]);
124 }
125
126 #[test]
127 fn parses_no_space_variant() {
128 let kinds = extract_suppressed_kinds("// padlock:ignore[LocalityIssue]");
129 assert_eq!(kinds, vec!["LocalityIssue"]);
130 }
131
132 #[test]
133 fn returns_empty_for_unrelated_comment() {
134 assert!(extract_suppressed_kinds("// some other comment").is_empty());
135 assert!(extract_suppressed_kinds("// padlock:guard=mu").is_empty());
136 assert!(extract_suppressed_kinds("// padlock:ignore").is_empty()); }
138
139 #[test]
140 fn parses_doc_comment_style() {
141 let kinds = extract_suppressed_kinds("/// padlock: ignore[FalseSharing]");
142 assert_eq!(kinds, vec!["FalseSharing"]);
143 }
144
145 #[test]
146 fn preceding_source_finds_immediately_before() {
147 let source =
148 "struct Other { int x; };\n// padlock: ignore[ReorderSuggestion]\nstruct Foo {";
149 let byte = source.find("struct Foo").unwrap();
150 let kinds = suppressed_from_preceding_source(source, byte);
151 assert_eq!(kinds, vec!["ReorderSuggestion"]);
152 }
153
154 #[test]
155 fn preceding_source_skips_blank_lines() {
156 let source = "// padlock: ignore[FalseSharing]\n\nstruct Foo {";
157 let byte = source.find("struct Foo").unwrap();
158 let kinds = suppressed_from_preceding_source(source, byte);
159 assert_eq!(kinds, vec!["FalseSharing"]);
160 }
161
162 #[test]
163 fn preceding_source_stops_at_non_suppress_comment() {
164 let source = "// padlock: ignore[ReorderSuggestion]\n// Some other doc\nstruct Foo {";
166 let byte = source.find("struct Foo").unwrap();
167 let kinds = suppressed_from_preceding_source(source, byte);
168 assert!(kinds.is_empty());
170 }
171
172 #[test]
173 fn preceding_source_returns_empty_when_no_directive() {
174 let source = "struct Bar { int x; };\nstruct Foo {";
175 let byte = source.find("struct Foo").unwrap();
176 let kinds = suppressed_from_preceding_source(source, byte);
177 assert!(kinds.is_empty());
178 }
179}