1#![deny(missing_docs)]
7
8use std::borrow::Cow;
9use std::ops::Index;
10
11pub mod apt;
13pub mod autopkgtest;
15pub mod brz;
17pub mod cudf;
19pub mod lines;
21pub mod problems;
23
24#[cfg(feature = "chatgpt")]
25pub mod chatgpt;
27
28pub mod common;
30
31pub mod r#match;
33
34pub mod sbuild;
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
42 fn test_singlelinematch_line() {
43 let m = SingleLineMatch {
44 origin: Origin("test".to_string()),
45 offset: 10,
46 line: "test line".to_string(),
47 };
48 assert_eq!(m.line(), "test line");
49 }
50
51 #[test]
52 fn test_singlelinematch_origin() {
53 let m = SingleLineMatch {
54 origin: Origin("test".to_string()),
55 offset: 10,
56 line: "test line".to_string(),
57 };
58 let origin = m.origin();
59 assert_eq!(origin.as_str(), "test");
60 }
61
62 #[test]
63 fn test_singlelinematch_offset() {
64 let m = SingleLineMatch {
65 origin: Origin("test".to_string()),
66 offset: 10,
67 line: "test line".to_string(),
68 };
69 assert_eq!(m.offset(), 10);
70 }
71
72 #[test]
73 fn test_singlelinematch_lineno() {
74 let m = SingleLineMatch {
75 origin: Origin("test".to_string()),
76 offset: 10,
77 line: "test line".to_string(),
78 };
79 assert_eq!(m.lineno(), 11);
80 }
81
82 #[test]
83 fn test_singlelinematch_linenos() {
84 let m = SingleLineMatch {
85 origin: Origin("test".to_string()),
86 offset: 10,
87 line: "test line".to_string(),
88 };
89 assert_eq!(m.linenos(), vec![11]);
90 }
91
92 #[test]
93 fn test_singlelinematch_offsets() {
94 let m = SingleLineMatch {
95 origin: Origin("test".to_string()),
96 offset: 10,
97 line: "test line".to_string(),
98 };
99 assert_eq!(m.offsets(), vec![10]);
100 }
101
102 #[test]
103 fn test_singlelinematch_lines() {
104 let m = SingleLineMatch {
105 origin: Origin("test".to_string()),
106 offset: 10,
107 line: "test line".to_string(),
108 };
109 assert_eq!(m.lines(), vec!["test line"]);
110 }
111
112 #[test]
113 fn test_singlelinematch_add_offset() {
114 let m = SingleLineMatch {
115 origin: Origin("test".to_string()),
116 offset: 10,
117 line: "test line".to_string(),
118 };
119 let new_m = m.add_offset(5);
120 assert_eq!(new_m.offset(), 15);
121 }
122
123 #[test]
124 fn test_multilinelmatch_line() {
125 let m = MultiLineMatch {
126 origin: Origin("test".to_string()),
127 offsets: vec![10, 11, 12],
128 lines: vec![
129 "line 1".to_string(),
130 "line 2".to_string(),
131 "line 3".to_string(),
132 ],
133 };
134 assert_eq!(m.line(), "line 3");
135 }
136
137 #[test]
138 fn test_multilinelmatch_origin() {
139 let m = MultiLineMatch {
140 origin: Origin("test".to_string()),
141 offsets: vec![10, 11, 12],
142 lines: vec![
143 "line 1".to_string(),
144 "line 2".to_string(),
145 "line 3".to_string(),
146 ],
147 };
148 let origin = m.origin();
149 assert_eq!(origin.as_str(), "test");
150 }
151
152 #[test]
153 fn test_multilinelmatch_offset() {
154 let m = MultiLineMatch {
155 origin: Origin("test".to_string()),
156 offsets: vec![10, 11, 12],
157 lines: vec![
158 "line 1".to_string(),
159 "line 2".to_string(),
160 "line 3".to_string(),
161 ],
162 };
163 assert_eq!(m.offset(), 12);
164 }
165
166 #[test]
167 fn test_multilinelmatch_lineno() {
168 let m = MultiLineMatch {
169 origin: Origin("test".to_string()),
170 offsets: vec![10, 11, 12],
171 lines: vec![
172 "line 1".to_string(),
173 "line 2".to_string(),
174 "line 3".to_string(),
175 ],
176 };
177 assert_eq!(m.lineno(), 13);
178 }
179
180 #[test]
181 fn test_multilinelmatch_offsets() {
182 let m = MultiLineMatch {
183 origin: Origin("test".to_string()),
184 offsets: vec![10, 11, 12],
185 lines: vec![
186 "line 1".to_string(),
187 "line 2".to_string(),
188 "line 3".to_string(),
189 ],
190 };
191 assert_eq!(m.offsets(), vec![10, 11, 12]);
192 }
193
194 #[test]
195 fn test_multilinelmatch_lines() {
196 let m = MultiLineMatch {
197 origin: Origin("test".to_string()),
198 offsets: vec![10, 11, 12],
199 lines: vec![
200 "line 1".to_string(),
201 "line 2".to_string(),
202 "line 3".to_string(),
203 ],
204 };
205 assert_eq!(m.lines(), vec!["line 1", "line 2", "line 3"]);
206 }
207
208 #[test]
209 fn test_multilinelmatch_add_offset() {
210 let m = MultiLineMatch {
211 origin: Origin("test".to_string()),
212 offsets: vec![10, 11, 12],
213 lines: vec![
214 "line 1".to_string(),
215 "line 2".to_string(),
216 "line 3".to_string(),
217 ],
218 };
219 let new_m = m.add_offset(5);
220 assert_eq!(new_m.offsets(), vec![15, 16, 17]);
221 }
222
223 #[test]
224 fn test_highlight_lines() {
225 let lines = vec!["line 1", "line 2", "line 3", "line 4", "line 5"];
226 let m = SingleLineMatch {
227 origin: Origin("test".to_string()),
228 offset: 2,
229 line: "line 3".to_string(),
230 };
231 highlight_lines(&lines, &m, 1);
233 }
234}
235
236pub trait Match: Send + Sync + std::fmt::Debug + std::fmt::Display {
241 fn line(&self) -> &str;
243
244 fn origin(&self) -> &Origin;
246
247 fn offset(&self) -> usize;
249
250 fn lineno(&self) -> usize {
252 self.offset() + 1
253 }
254
255 fn linenos(&self) -> Vec<usize> {
257 self.offsets().iter().map(|&x| x + 1).collect()
258 }
259
260 fn offsets(&self) -> Vec<usize>;
262
263 fn lines(&self) -> Vec<&str>;
265
266 fn add_offset(&self, offset: usize) -> Box<dyn Match>;
268}
269
270#[derive(Clone, Debug)]
274pub struct Origin(String);
275
276impl Origin {
277 pub fn as_str(&self) -> &str {
279 &self.0
280 }
281}
282
283impl std::fmt::Display for Origin {
284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285 f.write_str(&self.0)
286 }
287}
288
289#[derive(Clone, Debug)]
293pub struct SingleLineMatch {
294 pub origin: Origin,
296 pub offset: usize,
298 pub line: String,
300}
301
302impl Match for SingleLineMatch {
303 fn line(&self) -> &str {
304 &self.line
305 }
306
307 fn origin(&self) -> &Origin {
308 &self.origin
309 }
310
311 fn offset(&self) -> usize {
312 self.offset
313 }
314
315 fn offsets(&self) -> Vec<usize> {
316 vec![self.offset]
317 }
318
319 fn lines(&self) -> Vec<&str> {
320 vec![&self.line]
321 }
322
323 fn add_offset(&self, offset: usize) -> Box<dyn Match> {
324 Box::new(Self {
325 origin: self.origin.clone(),
326 offset: self.offset + offset,
327 line: self.line.clone(),
328 })
329 }
330}
331
332impl std::fmt::Display for SingleLineMatch {
333 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334 write!(f, "{}:{}: {}", self.origin.0, self.lineno(), self.line)
335 }
336}
337
338impl SingleLineMatch {
339 pub fn from_lines<'a>(
349 lines: &impl Index<usize, Output = &'a str>,
350 offset: usize,
351 origin: Option<&str>,
352 ) -> Self {
353 let line = &lines[offset];
354 let origin = origin
355 .map(|s| Origin(s.to_string()))
356 .unwrap_or_else(|| Origin("".to_string()));
357 Self {
358 origin,
359 offset,
360 line: line.to_string(),
361 }
362 }
363}
364
365#[derive(Clone, Debug)]
369pub struct MultiLineMatch {
370 pub origin: Origin,
372 pub offsets: Vec<usize>,
374 pub lines: Vec<String>,
376}
377
378impl MultiLineMatch {
379 pub fn new(origin: Origin, offsets: Vec<usize>, lines: Vec<String>) -> Self {
389 assert!(!offsets.is_empty());
390 assert!(offsets.len() == lines.len());
391 Self {
392 origin,
393 offsets,
394 lines,
395 }
396 }
397
398 pub fn from_lines<'a>(
408 lines: &impl Index<usize, Output = &'a str>,
409 offsets: Vec<usize>,
410 origin: Option<&str>,
411 ) -> Self {
412 let lines = offsets
413 .iter()
414 .map(|&offset| lines[offset].to_string())
415 .collect();
416 let origin = origin
417 .map(|s| Origin(s.to_string()))
418 .unwrap_or_else(|| Origin("".to_string()));
419 Self::new(origin, offsets, lines)
420 }
421}
422
423impl Match for MultiLineMatch {
424 fn line(&self) -> &str {
425 self.lines
426 .last()
427 .expect("MultiLineMatch should have at least one line")
428 }
429
430 fn origin(&self) -> &Origin {
431 &self.origin
432 }
433
434 fn offset(&self) -> usize {
435 *self
436 .offsets
437 .last()
438 .expect("MultiLineMatch should have at least one offset")
439 }
440
441 fn lineno(&self) -> usize {
442 self.offset() + 1
443 }
444
445 fn offsets(&self) -> Vec<usize> {
446 self.offsets.clone()
447 }
448
449 fn lines(&self) -> Vec<&str> {
450 self.lines.iter().map(|s| s.as_str()).collect()
451 }
452
453 fn add_offset(&self, extra: usize) -> Box<dyn Match> {
454 let offsets = self.offsets.iter().map(|&offset| offset + extra).collect();
455 Box::new(Self {
456 origin: self.origin.clone(),
457 offsets,
458 lines: self.lines.clone(),
459 })
460 }
461}
462
463impl std::fmt::Display for MultiLineMatch {
464 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
465 write!(f, "{}:{}: {}", self.origin.0, self.lineno(), self.line())
466 }
467}
468
469pub trait Problem: std::fmt::Display + Send + Sync + std::fmt::Debug {
474 fn kind(&self) -> Cow<'_, str>;
476
477 fn json(&self) -> serde_json::Value;
479
480 fn as_any(&self) -> &dyn std::any::Any;
482
483 fn is_universal(&self) -> bool {
487 false
488 }
489}
490
491impl PartialEq for dyn Problem {
492 fn eq(&self, other: &Self) -> bool {
493 self.kind() == other.kind() && self.json() == other.json()
494 }
495}
496
497impl Eq for dyn Problem {}
498
499impl serde::Serialize for dyn Problem {
500 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
501 let mut map = serde_json::Map::new();
502 map.insert(
503 "kind".to_string(),
504 serde_json::Value::String(self.kind().to_string()),
505 );
506 map.insert("details".to_string(), self.json());
507 map.serialize(serializer)
508 }
509}
510
511impl std::hash::Hash for dyn Problem {
512 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
513 self.kind().hash(state);
514 self.json().hash(state);
515 }
516}
517
518pub fn highlight_lines(lines: &[&str], m: &dyn Match, context: usize) {
525 use std::cmp::{max, min};
526 if m.linenos().len() == 1 {
527 println!("Issue found at line {}:", m.lineno());
528 } else {
529 println!(
530 "Issue found at lines {}-{}:",
531 m.linenos().first().unwrap(),
532 m.linenos().last().unwrap()
533 );
534 }
535 let start = max(0, m.offsets()[0].saturating_sub(context));
536 let end = min(lines.len(), m.offsets().last().unwrap() + context + 1);
537
538 for (i, line) in lines.iter().enumerate().take(end).skip(start) {
539 println!(
540 " {} {}",
541 if m.offsets().contains(&i) { ">" } else { " " },
542 line.trim_end_matches('\n')
543 );
544 }
545}