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 with_code(self, code: impl Into<String>) -> IssueCollection {
485 let code = code.into();
486
487 Self { issues: self.issues.into_iter().map(|issue| issue.with_code(&code)).collect() }
488 }
489
490 pub fn take_suggestions(&mut self) -> impl Iterator<Item = (FileId, FixPlan)> + '_ {
491 self.issues.iter_mut().flat_map(|issue| issue.take_suggestions())
492 }
493
494 pub fn only_fixable(self) -> impl Iterator<Item = Issue> {
495 self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty())
496 }
497
498 pub fn sorted(self) -> Self {
503 let mut issues = self.issues;
504
505 issues.sort_by(|a, b| match a.level.cmp(&b.level) {
506 Ordering::Greater => Ordering::Greater,
507 Ordering::Less => Ordering::Less,
508 Ordering::Equal => match a.code.as_deref().cmp(&b.code.as_deref()) {
509 Ordering::Less => Ordering::Less,
510 Ordering::Greater => Ordering::Greater,
511 Ordering::Equal => {
512 let a_span = a
513 .annotations
514 .iter()
515 .find(|annotation| annotation.is_primary())
516 .map(|annotation| annotation.span);
517
518 let b_span = b
519 .annotations
520 .iter()
521 .find(|annotation| annotation.is_primary())
522 .map(|annotation| annotation.span);
523
524 match (a_span, b_span) {
525 (Some(a_span), Some(b_span)) => a_span.cmp(&b_span),
526 (Some(_), None) => Ordering::Less,
527 (None, Some(_)) => Ordering::Greater,
528 (None, None) => Ordering::Equal,
529 }
530 }
531 },
532 });
533
534 Self { issues }
535 }
536
537 pub fn iter(&self) -> impl Iterator<Item = &Issue> {
538 self.issues.iter()
539 }
540
541 pub fn to_fix_plans(self) -> HashMap<FileId, FixPlan> {
542 let mut plans: HashMap<FileId, FixPlan> = HashMap::default();
543 for issue in self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty()) {
544 for suggestion in issue.suggestions.into_iter() {
545 match plans.entry(suggestion.0) {
546 Entry::Occupied(mut occupied_entry) => {
547 occupied_entry.get_mut().merge(suggestion.1);
548 }
549 Entry::Vacant(vacant_entry) => {
550 vacant_entry.insert(suggestion.1);
551 }
552 }
553 }
554 }
555
556 plans
557 }
558}
559
560impl IntoIterator for IssueCollection {
561 type Item = Issue;
562
563 type IntoIter = std::vec::IntoIter<Issue>;
564
565 fn into_iter(self) -> Self::IntoIter {
566 self.issues.into_iter()
567 }
568}
569
570impl Default for IssueCollection {
571 fn default() -> Self {
572 Self::new()
573 }
574}
575
576impl IntoIterator for Issue {
577 type Item = Issue;
578 type IntoIter = Once<Issue>;
579
580 fn into_iter(self) -> Self::IntoIter {
581 std::iter::once(self)
582 }
583}
584
585impl FromIterator<Issue> for IssueCollection {
586 fn from_iter<T: IntoIterator<Item = Issue>>(iter: T) -> Self {
587 Self { issues: iter.into_iter().collect() }
588 }
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594
595 #[test]
596 pub fn test_highest_collection_level() {
597 let mut collection = IssueCollection::from(vec![]);
598 assert_eq!(collection.get_highest_level(), None);
599
600 collection.push(Issue::note("note"));
601 assert_eq!(collection.get_highest_level(), Some(Level::Note));
602
603 collection.push(Issue::help("help"));
604 assert_eq!(collection.get_highest_level(), Some(Level::Help));
605
606 collection.push(Issue::warning("warning"));
607 assert_eq!(collection.get_highest_level(), Some(Level::Warning));
608
609 collection.push(Issue::error("error"));
610 assert_eq!(collection.get_highest_level(), Some(Level::Error));
611 }
612
613 #[test]
614 pub fn test_level_downgrade() {
615 assert_eq!(Level::Error.downgrade(), Level::Warning);
616 assert_eq!(Level::Warning.downgrade(), Level::Help);
617 assert_eq!(Level::Help.downgrade(), Level::Note);
618 assert_eq!(Level::Note.downgrade(), Level::Note);
619 }
620
621 #[test]
622 pub fn test_issue_collection_with_maximum_level() {
623 let mut collection = IssueCollection::from(vec![
624 Issue::error("error"),
625 Issue::warning("warning"),
626 Issue::help("help"),
627 Issue::note("note"),
628 ]);
629
630 collection = collection.with_maximum_level(Level::Warning);
631 assert_eq!(collection.len(), 3);
632 assert_eq!(
633 collection.iter().map(|issue| issue.level).collect::<Vec<_>>(),
634 vec![Level::Warning, Level::Help, Level::Note]
635 );
636 }
637
638 #[test]
639 pub fn test_issue_collection_with_minimum_level() {
640 let mut collection = IssueCollection::from(vec![
641 Issue::error("error"),
642 Issue::warning("warning"),
643 Issue::help("help"),
644 Issue::note("note"),
645 ]);
646
647 collection = collection.with_minimum_level(Level::Warning);
648 assert_eq!(collection.len(), 2);
649 assert_eq!(collection.iter().map(|issue| issue.level).collect::<Vec<_>>(), vec![Level::Error, Level::Warning,]);
650 }
651
652 #[test]
653 pub fn test_issue_collection_has_minimum_level() {
654 let mut collection = IssueCollection::from(vec![]);
655
656 assert!(!collection.has_minimum_level(Level::Error));
657 assert!(!collection.has_minimum_level(Level::Warning));
658 assert!(!collection.has_minimum_level(Level::Help));
659 assert!(!collection.has_minimum_level(Level::Note));
660
661 collection.push(Issue::note("note"));
662
663 assert!(!collection.has_minimum_level(Level::Error));
664 assert!(!collection.has_minimum_level(Level::Warning));
665 assert!(!collection.has_minimum_level(Level::Help));
666 assert!(collection.has_minimum_level(Level::Note));
667
668 collection.push(Issue::help("help"));
669
670 assert!(!collection.has_minimum_level(Level::Error));
671 assert!(!collection.has_minimum_level(Level::Warning));
672 assert!(collection.has_minimum_level(Level::Help));
673 assert!(collection.has_minimum_level(Level::Note));
674
675 collection.push(Issue::warning("warning"));
676
677 assert!(!collection.has_minimum_level(Level::Error));
678 assert!(collection.has_minimum_level(Level::Warning));
679 assert!(collection.has_minimum_level(Level::Help));
680 assert!(collection.has_minimum_level(Level::Note));
681
682 collection.push(Issue::error("error"));
683
684 assert!(collection.has_minimum_level(Level::Error));
685 assert!(collection.has_minimum_level(Level::Warning));
686 assert!(collection.has_minimum_level(Level::Help));
687 assert!(collection.has_minimum_level(Level::Note));
688 }
689
690 #[test]
691 pub fn test_issue_collection_level_count() {
692 let mut collection = IssueCollection::from(vec![]);
693
694 assert_eq!(collection.get_level_count(Level::Error), 0);
695 assert_eq!(collection.get_level_count(Level::Warning), 0);
696 assert_eq!(collection.get_level_count(Level::Help), 0);
697 assert_eq!(collection.get_level_count(Level::Note), 0);
698
699 collection.push(Issue::error("error"));
700
701 assert_eq!(collection.get_level_count(Level::Error), 1);
702 assert_eq!(collection.get_level_count(Level::Warning), 0);
703 assert_eq!(collection.get_level_count(Level::Help), 0);
704 assert_eq!(collection.get_level_count(Level::Note), 0);
705
706 collection.push(Issue::warning("warning"));
707
708 assert_eq!(collection.get_level_count(Level::Error), 1);
709 assert_eq!(collection.get_level_count(Level::Warning), 1);
710 assert_eq!(collection.get_level_count(Level::Help), 0);
711 assert_eq!(collection.get_level_count(Level::Note), 0);
712
713 collection.push(Issue::help("help"));
714
715 assert_eq!(collection.get_level_count(Level::Error), 1);
716 assert_eq!(collection.get_level_count(Level::Warning), 1);
717 assert_eq!(collection.get_level_count(Level::Help), 1);
718 assert_eq!(collection.get_level_count(Level::Note), 0);
719
720 collection.push(Issue::note("note"));
721
722 assert_eq!(collection.get_level_count(Level::Error), 1);
723 assert_eq!(collection.get_level_count(Level::Warning), 1);
724 assert_eq!(collection.get_level_count(Level::Help), 1);
725 assert_eq!(collection.get_level_count(Level::Note), 1);
726 }
727}