1use std::cmp::Ordering;
2use std::collections::hash_map::Entry;
3use std::iter::Once;
4use std::str::FromStr;
5
6use ahash::HashMap;
7use serde::Deserialize;
8use serde::Serialize;
9use strum::Display;
10use strum::VariantNames;
11
12use mago_database::file::FileId;
13use mago_fixer::FixPlan;
14use mago_span::Span;
15
16mod internal;
17
18pub mod error;
19pub mod reporter;
20
21#[derive(Debug, PartialEq, Eq, Ord, Copy, Clone, Hash, PartialOrd, Deserialize, Serialize)]
23pub enum AnnotationKind {
24 Primary,
26 Secondary,
28}
29
30#[derive(Debug, PartialEq, Eq, Ord, Clone, Hash, PartialOrd, Deserialize, Serialize)]
32pub struct Annotation {
33 pub message: Option<String>,
35 pub kind: AnnotationKind,
37 pub span: Span,
39}
40
41#[derive(Debug, PartialEq, Eq, Ord, Copy, Clone, Hash, PartialOrd, Deserialize, Serialize, Display, VariantNames)]
43#[strum(serialize_all = "lowercase")]
44pub enum Level {
45 Note,
47 Help,
49 Warning,
51 Error,
53}
54
55impl FromStr for Level {
56 type Err = ();
57
58 fn from_str(s: &str) -> Result<Self, Self::Err> {
59 match s.to_lowercase().as_str() {
60 "note" => Ok(Self::Note),
61 "help" => Ok(Self::Help),
62 "warning" => Ok(Self::Warning),
63 "error" => Ok(Self::Error),
64 _ => Err(()),
65 }
66 }
67}
68
69#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
71pub struct Issue {
72 pub level: Level,
74 pub code: Option<String>,
76 pub message: String,
78 pub notes: Vec<String>,
80 pub help: Option<String>,
82 pub link: Option<String>,
84 pub annotations: Vec<Annotation>,
86 pub suggestions: Vec<(FileId, FixPlan)>,
88}
89
90#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
92pub struct IssueCollection {
93 issues: Vec<Issue>,
94}
95
96impl AnnotationKind {
97 #[inline]
99 pub const fn is_primary(&self) -> bool {
100 matches!(self, AnnotationKind::Primary)
101 }
102
103 #[inline]
105 pub const fn is_secondary(&self) -> bool {
106 matches!(self, AnnotationKind::Secondary)
107 }
108}
109
110impl Annotation {
111 pub fn new(kind: AnnotationKind, span: Span) -> Self {
128 Self { message: None, kind, span }
129 }
130
131 pub fn primary(span: Span) -> Self {
148 Self::new(AnnotationKind::Primary, span)
149 }
150
151 pub fn secondary(span: Span) -> Self {
168 Self::new(AnnotationKind::Secondary, span)
169 }
170
171 #[must_use]
188 pub fn with_message(mut self, message: impl Into<String>) -> Self {
189 self.message = Some(message.into());
190
191 self
192 }
193
194 pub fn is_primary(&self) -> bool {
196 self.kind == AnnotationKind::Primary
197 }
198}
199
200impl Level {
201 pub fn downgrade(&self) -> Self {
228 match self {
229 Level::Error => Level::Warning,
230 Level::Warning => Level::Help,
231 Level::Help | Level::Note => Level::Note,
232 }
233 }
234}
235
236impl Issue {
237 pub fn new(level: Level, message: impl Into<String>) -> Self {
247 Self {
248 level,
249 code: None,
250 message: message.into(),
251 annotations: Vec::new(),
252 notes: Vec::new(),
253 help: None,
254 link: None,
255 suggestions: Vec::new(),
256 }
257 }
258
259 pub fn error(message: impl Into<String>) -> Self {
269 Self::new(Level::Error, message)
270 }
271
272 pub fn warning(message: impl Into<String>) -> Self {
282 Self::new(Level::Warning, message)
283 }
284
285 pub fn help(message: impl Into<String>) -> Self {
295 Self::new(Level::Help, message)
296 }
297
298 pub fn note(message: impl Into<String>) -> Self {
308 Self::new(Level::Note, message)
309 }
310
311 #[must_use]
321 pub fn with_code(mut self, code: impl Into<String>) -> Self {
322 self.code = Some(code.into());
323
324 self
325 }
326
327 #[must_use]
345 pub fn with_annotation(mut self, annotation: Annotation) -> Self {
346 self.annotations.push(annotation);
347
348 self
349 }
350
351 #[must_use]
352 pub fn with_annotations(mut self, annotation: impl IntoIterator<Item = Annotation>) -> Self {
353 self.annotations.extend(annotation);
354
355 self
356 }
357
358 #[must_use]
368 pub fn with_note(mut self, note: impl Into<String>) -> Self {
369 self.notes.push(note.into());
370
371 self
372 }
373
374 #[must_use]
386 pub fn with_help(mut self, help: impl Into<String>) -> Self {
387 self.help = Some(help.into());
388
389 self
390 }
391
392 #[must_use]
402 pub fn with_link(mut self, link: impl Into<String>) -> Self {
403 self.link = Some(link.into());
404
405 self
406 }
407
408 #[must_use]
410 pub fn with_suggestion(mut self, file_id: FileId, plan: FixPlan) -> Self {
411 self.suggestions.push((file_id, plan));
412
413 self
414 }
415
416 #[must_use]
418 pub fn take_suggestions(&mut self) -> Vec<(FileId, FixPlan)> {
419 self.suggestions.drain(..).collect()
420 }
421}
422
423impl IssueCollection {
424 pub fn new() -> Self {
425 Self { issues: Vec::new() }
426 }
427
428 pub fn from(issues: impl IntoIterator<Item = Issue>) -> Self {
429 Self { issues: issues.into_iter().collect() }
430 }
431
432 pub fn push(&mut self, issue: Issue) {
433 if self.issues.contains(&issue) {
434 return; }
436
437 self.issues.push(issue);
438 }
439
440 pub fn extend(&mut self, issues: impl IntoIterator<Item = Issue>) {
441 self.issues.extend(issues);
442 }
443
444 pub fn shrink_to_fit(&mut self) {
445 self.issues.shrink_to_fit();
446 }
447
448 pub fn is_empty(&self) -> bool {
449 self.issues.is_empty()
450 }
451
452 pub fn len(&self) -> usize {
453 self.issues.len()
454 }
455
456 pub fn with_maximum_level(self, level: Level) -> Self {
459 Self { issues: self.issues.into_iter().filter(|issue| issue.level <= level).collect() }
460 }
461
462 pub fn with_minimum_level(self, level: Level) -> Self {
465 Self { issues: self.issues.into_iter().filter(|issue| issue.level >= level).collect() }
466 }
467
468 pub fn has_minimum_level(&self, level: Level) -> bool {
471 self.issues.iter().any(|issue| issue.level >= level)
472 }
473
474 pub fn get_level_count(&self, level: Level) -> usize {
476 self.issues.iter().filter(|issue| issue.level == level).count()
477 }
478
479 pub fn get_highest_level(&self) -> Option<Level> {
481 self.issues.iter().map(|issue| issue.level).max()
482 }
483
484 pub fn filter_out_ignored(&mut self, ignore: &[String]) {
485 self.issues.retain(|issue| if let Some(code) = &issue.code { !ignore.contains(code) } else { true });
486 }
487
488 pub fn take_suggestions(&mut self) -> impl Iterator<Item = (FileId, FixPlan)> + '_ {
489 self.issues.iter_mut().flat_map(|issue| issue.take_suggestions())
490 }
491
492 pub fn only_fixable(self) -> impl Iterator<Item = Issue> {
493 self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty())
494 }
495
496 pub fn sorted(self) -> Self {
501 let mut issues = self.issues;
502
503 issues.sort_by(|a, b| match a.level.cmp(&b.level) {
504 Ordering::Greater => Ordering::Greater,
505 Ordering::Less => Ordering::Less,
506 Ordering::Equal => match a.code.as_deref().cmp(&b.code.as_deref()) {
507 Ordering::Less => Ordering::Less,
508 Ordering::Greater => Ordering::Greater,
509 Ordering::Equal => {
510 let a_span = a
511 .annotations
512 .iter()
513 .find(|annotation| annotation.is_primary())
514 .map(|annotation| annotation.span);
515
516 let b_span = b
517 .annotations
518 .iter()
519 .find(|annotation| annotation.is_primary())
520 .map(|annotation| annotation.span);
521
522 match (a_span, b_span) {
523 (Some(a_span), Some(b_span)) => a_span.cmp(&b_span),
524 (Some(_), None) => Ordering::Less,
525 (None, Some(_)) => Ordering::Greater,
526 (None, None) => Ordering::Equal,
527 }
528 }
529 },
530 });
531
532 Self { issues }
533 }
534
535 pub fn iter(&self) -> impl Iterator<Item = &Issue> {
536 self.issues.iter()
537 }
538
539 pub fn to_fix_plans(self) -> HashMap<FileId, FixPlan> {
540 let mut plans: HashMap<FileId, FixPlan> = HashMap::default();
541 for issue in self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty()) {
542 for suggestion in issue.suggestions.into_iter() {
543 match plans.entry(suggestion.0) {
544 Entry::Occupied(mut occupied_entry) => {
545 occupied_entry.get_mut().merge(suggestion.1);
546 }
547 Entry::Vacant(vacant_entry) => {
548 vacant_entry.insert(suggestion.1);
549 }
550 }
551 }
552 }
553
554 plans
555 }
556}
557
558impl IntoIterator for IssueCollection {
559 type Item = Issue;
560
561 type IntoIter = std::vec::IntoIter<Issue>;
562
563 fn into_iter(self) -> Self::IntoIter {
564 self.issues.into_iter()
565 }
566}
567
568impl Default for IssueCollection {
569 fn default() -> Self {
570 Self::new()
571 }
572}
573
574impl IntoIterator for Issue {
575 type Item = Issue;
576 type IntoIter = Once<Issue>;
577
578 fn into_iter(self) -> Self::IntoIter {
579 std::iter::once(self)
580 }
581}
582
583impl FromIterator<Issue> for IssueCollection {
584 fn from_iter<T: IntoIterator<Item = Issue>>(iter: T) -> Self {
585 Self { issues: iter.into_iter().collect() }
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592
593 #[test]
594 pub fn test_highest_collection_level() {
595 let mut collection = IssueCollection::from(vec![]);
596 assert_eq!(collection.get_highest_level(), None);
597
598 collection.push(Issue::note("note"));
599 assert_eq!(collection.get_highest_level(), Some(Level::Note));
600
601 collection.push(Issue::help("help"));
602 assert_eq!(collection.get_highest_level(), Some(Level::Help));
603
604 collection.push(Issue::warning("warning"));
605 assert_eq!(collection.get_highest_level(), Some(Level::Warning));
606
607 collection.push(Issue::error("error"));
608 assert_eq!(collection.get_highest_level(), Some(Level::Error));
609 }
610
611 #[test]
612 pub fn test_level_downgrade() {
613 assert_eq!(Level::Error.downgrade(), Level::Warning);
614 assert_eq!(Level::Warning.downgrade(), Level::Help);
615 assert_eq!(Level::Help.downgrade(), Level::Note);
616 assert_eq!(Level::Note.downgrade(), Level::Note);
617 }
618
619 #[test]
620 pub fn test_issue_collection_with_maximum_level() {
621 let mut collection = IssueCollection::from(vec![
622 Issue::error("error"),
623 Issue::warning("warning"),
624 Issue::help("help"),
625 Issue::note("note"),
626 ]);
627
628 collection = collection.with_maximum_level(Level::Warning);
629 assert_eq!(collection.len(), 3);
630 assert_eq!(
631 collection.iter().map(|issue| issue.level).collect::<Vec<_>>(),
632 vec![Level::Warning, Level::Help, Level::Note]
633 );
634 }
635
636 #[test]
637 pub fn test_issue_collection_with_minimum_level() {
638 let mut collection = IssueCollection::from(vec![
639 Issue::error("error"),
640 Issue::warning("warning"),
641 Issue::help("help"),
642 Issue::note("note"),
643 ]);
644
645 collection = collection.with_minimum_level(Level::Warning);
646 assert_eq!(collection.len(), 2);
647 assert_eq!(collection.iter().map(|issue| issue.level).collect::<Vec<_>>(), vec![Level::Error, Level::Warning,]);
648 }
649
650 #[test]
651 pub fn test_issue_collection_has_minimum_level() {
652 let mut collection = IssueCollection::from(vec![]);
653
654 assert!(!collection.has_minimum_level(Level::Error));
655 assert!(!collection.has_minimum_level(Level::Warning));
656 assert!(!collection.has_minimum_level(Level::Help));
657 assert!(!collection.has_minimum_level(Level::Note));
658
659 collection.push(Issue::note("note"));
660
661 assert!(!collection.has_minimum_level(Level::Error));
662 assert!(!collection.has_minimum_level(Level::Warning));
663 assert!(!collection.has_minimum_level(Level::Help));
664 assert!(collection.has_minimum_level(Level::Note));
665
666 collection.push(Issue::help("help"));
667
668 assert!(!collection.has_minimum_level(Level::Error));
669 assert!(!collection.has_minimum_level(Level::Warning));
670 assert!(collection.has_minimum_level(Level::Help));
671 assert!(collection.has_minimum_level(Level::Note));
672
673 collection.push(Issue::warning("warning"));
674
675 assert!(!collection.has_minimum_level(Level::Error));
676 assert!(collection.has_minimum_level(Level::Warning));
677 assert!(collection.has_minimum_level(Level::Help));
678 assert!(collection.has_minimum_level(Level::Note));
679
680 collection.push(Issue::error("error"));
681
682 assert!(collection.has_minimum_level(Level::Error));
683 assert!(collection.has_minimum_level(Level::Warning));
684 assert!(collection.has_minimum_level(Level::Help));
685 assert!(collection.has_minimum_level(Level::Note));
686 }
687
688 #[test]
689 pub fn test_issue_collection_level_count() {
690 let mut collection = IssueCollection::from(vec![]);
691
692 assert_eq!(collection.get_level_count(Level::Error), 0);
693 assert_eq!(collection.get_level_count(Level::Warning), 0);
694 assert_eq!(collection.get_level_count(Level::Help), 0);
695 assert_eq!(collection.get_level_count(Level::Note), 0);
696
697 collection.push(Issue::error("error"));
698
699 assert_eq!(collection.get_level_count(Level::Error), 1);
700 assert_eq!(collection.get_level_count(Level::Warning), 0);
701 assert_eq!(collection.get_level_count(Level::Help), 0);
702 assert_eq!(collection.get_level_count(Level::Note), 0);
703
704 collection.push(Issue::warning("warning"));
705
706 assert_eq!(collection.get_level_count(Level::Error), 1);
707 assert_eq!(collection.get_level_count(Level::Warning), 1);
708 assert_eq!(collection.get_level_count(Level::Help), 0);
709 assert_eq!(collection.get_level_count(Level::Note), 0);
710
711 collection.push(Issue::help("help"));
712
713 assert_eq!(collection.get_level_count(Level::Error), 1);
714 assert_eq!(collection.get_level_count(Level::Warning), 1);
715 assert_eq!(collection.get_level_count(Level::Help), 1);
716 assert_eq!(collection.get_level_count(Level::Note), 0);
717
718 collection.push(Issue::note("note"));
719
720 assert_eq!(collection.get_level_count(Level::Error), 1);
721 assert_eq!(collection.get_level_count(Level::Warning), 1);
722 assert_eq!(collection.get_level_count(Level::Help), 1);
723 assert_eq!(collection.get_level_count(Level::Note), 1);
724 }
725}