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_fixer::FixPlan;
13use mago_source::SourceIdentifier;
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<(SourceIdentifier, 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 {
126 Self { message: None, kind, span }
127 }
128
129 pub fn primary(span: Span) -> Self {
144 Self::new(AnnotationKind::Primary, span)
145 }
146
147 pub fn secondary(span: Span) -> Self {
162 Self::new(AnnotationKind::Secondary, span)
163 }
164
165 #[must_use]
180 pub fn with_message(mut self, message: impl Into<String>) -> Self {
181 self.message = Some(message.into());
182
183 self
184 }
185
186 pub fn is_primary(&self) -> bool {
188 self.kind == AnnotationKind::Primary
189 }
190}
191
192impl Level {
193 pub fn downgrade(&self) -> Self {
220 match self {
221 Level::Error => Level::Warning,
222 Level::Warning => Level::Help,
223 Level::Help | Level::Note => Level::Note,
224 }
225 }
226}
227
228impl Issue {
229 pub fn new(level: Level, message: impl Into<String>) -> Self {
239 Self {
240 level,
241 code: None,
242 message: message.into(),
243 annotations: Vec::new(),
244 notes: Vec::new(),
245 help: None,
246 link: None,
247 suggestions: Vec::new(),
248 }
249 }
250
251 pub fn error(message: impl Into<String>) -> Self {
261 Self::new(Level::Error, message)
262 }
263
264 pub fn warning(message: impl Into<String>) -> Self {
274 Self::new(Level::Warning, message)
275 }
276
277 pub fn help(message: impl Into<String>) -> Self {
287 Self::new(Level::Help, message)
288 }
289
290 pub fn note(message: impl Into<String>) -> Self {
300 Self::new(Level::Note, message)
301 }
302
303 #[must_use]
313 pub fn with_code(mut self, code: impl Into<String>) -> Self {
314 self.code = Some(code.into());
315
316 self
317 }
318
319 #[must_use]
335 pub fn with_annotation(mut self, annotation: Annotation) -> Self {
336 self.annotations.push(annotation);
337
338 self
339 }
340
341 #[must_use]
342 pub fn with_annotations(mut self, annotation: impl IntoIterator<Item = Annotation>) -> Self {
343 self.annotations.extend(annotation);
344
345 self
346 }
347
348 #[must_use]
358 pub fn with_note(mut self, note: impl Into<String>) -> Self {
359 self.notes.push(note.into());
360
361 self
362 }
363
364 #[must_use]
376 pub fn with_help(mut self, help: impl Into<String>) -> Self {
377 self.help = Some(help.into());
378
379 self
380 }
381
382 #[must_use]
392 pub fn with_link(mut self, link: impl Into<String>) -> Self {
393 self.link = Some(link.into());
394
395 self
396 }
397
398 #[must_use]
400 pub fn with_suggestion(mut self, source: SourceIdentifier, plan: FixPlan) -> Self {
401 self.suggestions.push((source, plan));
402
403 self
404 }
405
406 #[must_use]
408 pub fn take_suggestions(&mut self) -> Vec<(SourceIdentifier, FixPlan)> {
409 self.suggestions.drain(..).collect()
410 }
411}
412
413impl IssueCollection {
414 pub fn new() -> Self {
415 Self { issues: Vec::new() }
416 }
417
418 pub fn from(issues: impl IntoIterator<Item = Issue>) -> Self {
419 Self { issues: issues.into_iter().collect() }
420 }
421
422 pub fn push(&mut self, issue: Issue) {
423 if self.issues.contains(&issue) {
424 return; }
426
427 self.issues.push(issue);
428 }
429
430 pub fn extend(&mut self, issues: impl IntoIterator<Item = Issue>) {
431 self.issues.extend(issues);
432 }
433
434 pub fn shrink_to_fit(&mut self) {
435 self.issues.shrink_to_fit();
436 }
437
438 pub fn is_empty(&self) -> bool {
439 self.issues.is_empty()
440 }
441
442 pub fn len(&self) -> usize {
443 self.issues.len()
444 }
445
446 pub fn with_maximum_level(self, level: Level) -> Self {
449 Self { issues: self.issues.into_iter().filter(|issue| issue.level <= level).collect() }
450 }
451
452 pub fn with_minimum_level(self, level: Level) -> Self {
455 Self { issues: self.issues.into_iter().filter(|issue| issue.level >= level).collect() }
456 }
457
458 pub fn has_minimum_level(&self, level: Level) -> bool {
461 self.issues.iter().any(|issue| issue.level >= level)
462 }
463
464 pub fn get_level_count(&self, level: Level) -> usize {
466 self.issues.iter().filter(|issue| issue.level == level).count()
467 }
468
469 pub fn get_highest_level(&self) -> Option<Level> {
471 self.issues.iter().map(|issue| issue.level).max()
472 }
473
474 pub fn with_code(self, code: impl Into<String>) -> IssueCollection {
475 let code = code.into();
476
477 Self { issues: self.issues.into_iter().map(|issue| issue.with_code(&code)).collect() }
478 }
479
480 pub fn take_suggestions(&mut self) -> impl Iterator<Item = (SourceIdentifier, FixPlan)> + '_ {
481 self.issues.iter_mut().flat_map(|issue| issue.take_suggestions())
482 }
483
484 pub fn only_fixable(self) -> impl Iterator<Item = Issue> {
485 self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty())
486 }
487
488 pub fn sorted(self) -> Self {
493 let mut issues = self.issues;
494
495 issues.sort_by(|a, b| match a.level.cmp(&b.level) {
496 Ordering::Greater => Ordering::Greater,
497 Ordering::Less => Ordering::Less,
498 Ordering::Equal => match a.code.as_deref().cmp(&b.code.as_deref()) {
499 Ordering::Less => Ordering::Less,
500 Ordering::Greater => Ordering::Greater,
501 Ordering::Equal => {
502 let a_span = a
503 .annotations
504 .iter()
505 .find(|annotation| annotation.is_primary())
506 .map(|annotation| annotation.span);
507
508 let b_span = b
509 .annotations
510 .iter()
511 .find(|annotation| annotation.is_primary())
512 .map(|annotation| annotation.span);
513
514 match (a_span, b_span) {
515 (Some(a_span), Some(b_span)) => a_span.cmp(&b_span),
516 (Some(_), None) => Ordering::Less,
517 (None, Some(_)) => Ordering::Greater,
518 (None, None) => Ordering::Equal,
519 }
520 }
521 },
522 });
523
524 Self { issues }
525 }
526
527 pub fn iter(&self) -> impl Iterator<Item = &Issue> {
528 self.issues.iter()
529 }
530
531 pub fn to_fix_plans(self) -> HashMap<SourceIdentifier, FixPlan> {
532 let mut plans: HashMap<SourceIdentifier, FixPlan> = HashMap::default();
533 for issue in self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty()) {
534 for suggestion in issue.suggestions.into_iter() {
535 match plans.entry(suggestion.0) {
536 Entry::Occupied(mut occupied_entry) => {
537 occupied_entry.get_mut().merge(suggestion.1);
538 }
539 Entry::Vacant(vacant_entry) => {
540 vacant_entry.insert(suggestion.1);
541 }
542 }
543 }
544 }
545
546 plans
547 }
548}
549
550impl IntoIterator for IssueCollection {
551 type Item = Issue;
552
553 type IntoIter = std::vec::IntoIter<Issue>;
554
555 fn into_iter(self) -> Self::IntoIter {
556 self.issues.into_iter()
557 }
558}
559
560impl Default for IssueCollection {
561 fn default() -> Self {
562 Self::new()
563 }
564}
565
566impl IntoIterator for Issue {
567 type Item = Issue;
568 type IntoIter = Once<Issue>;
569
570 fn into_iter(self) -> Self::IntoIter {
571 std::iter::once(self)
572 }
573}
574
575impl FromIterator<Issue> for IssueCollection {
576 fn from_iter<T: IntoIterator<Item = Issue>>(iter: T) -> Self {
577 Self { issues: iter.into_iter().collect() }
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584
585 #[test]
586 pub fn test_highest_collection_level() {
587 let mut collection = IssueCollection::from(vec![]);
588 assert_eq!(collection.get_highest_level(), None);
589
590 collection.push(Issue::note("note"));
591 assert_eq!(collection.get_highest_level(), Some(Level::Note));
592
593 collection.push(Issue::help("help"));
594 assert_eq!(collection.get_highest_level(), Some(Level::Help));
595
596 collection.push(Issue::warning("warning"));
597 assert_eq!(collection.get_highest_level(), Some(Level::Warning));
598
599 collection.push(Issue::error("error"));
600 assert_eq!(collection.get_highest_level(), Some(Level::Error));
601 }
602
603 #[test]
604 pub fn test_level_downgrade() {
605 assert_eq!(Level::Error.downgrade(), Level::Warning);
606 assert_eq!(Level::Warning.downgrade(), Level::Help);
607 assert_eq!(Level::Help.downgrade(), Level::Note);
608 assert_eq!(Level::Note.downgrade(), Level::Note);
609 }
610
611 #[test]
612 pub fn test_issue_collection_with_maximum_level() {
613 let mut collection = IssueCollection::from(vec![
614 Issue::error("error"),
615 Issue::warning("warning"),
616 Issue::help("help"),
617 Issue::note("note"),
618 ]);
619
620 collection = collection.with_maximum_level(Level::Warning);
621 assert_eq!(collection.len(), 3);
622 assert_eq!(
623 collection.iter().map(|issue| issue.level).collect::<Vec<_>>(),
624 vec![Level::Warning, Level::Help, Level::Note]
625 );
626 }
627
628 #[test]
629 pub fn test_issue_collection_with_minimum_level() {
630 let mut collection = IssueCollection::from(vec![
631 Issue::error("error"),
632 Issue::warning("warning"),
633 Issue::help("help"),
634 Issue::note("note"),
635 ]);
636
637 collection = collection.with_minimum_level(Level::Warning);
638 assert_eq!(collection.len(), 2);
639 assert_eq!(collection.iter().map(|issue| issue.level).collect::<Vec<_>>(), vec![Level::Error, Level::Warning,]);
640 }
641
642 #[test]
643 pub fn test_issue_collection_has_minimum_level() {
644 let mut collection = IssueCollection::from(vec![]);
645
646 assert!(!collection.has_minimum_level(Level::Error));
647 assert!(!collection.has_minimum_level(Level::Warning));
648 assert!(!collection.has_minimum_level(Level::Help));
649 assert!(!collection.has_minimum_level(Level::Note));
650
651 collection.push(Issue::note("note"));
652
653 assert!(!collection.has_minimum_level(Level::Error));
654 assert!(!collection.has_minimum_level(Level::Warning));
655 assert!(!collection.has_minimum_level(Level::Help));
656 assert!(collection.has_minimum_level(Level::Note));
657
658 collection.push(Issue::help("help"));
659
660 assert!(!collection.has_minimum_level(Level::Error));
661 assert!(!collection.has_minimum_level(Level::Warning));
662 assert!(collection.has_minimum_level(Level::Help));
663 assert!(collection.has_minimum_level(Level::Note));
664
665 collection.push(Issue::warning("warning"));
666
667 assert!(!collection.has_minimum_level(Level::Error));
668 assert!(collection.has_minimum_level(Level::Warning));
669 assert!(collection.has_minimum_level(Level::Help));
670 assert!(collection.has_minimum_level(Level::Note));
671
672 collection.push(Issue::error("error"));
673
674 assert!(collection.has_minimum_level(Level::Error));
675 assert!(collection.has_minimum_level(Level::Warning));
676 assert!(collection.has_minimum_level(Level::Help));
677 assert!(collection.has_minimum_level(Level::Note));
678 }
679
680 #[test]
681 pub fn test_issue_collection_level_count() {
682 let mut collection = IssueCollection::from(vec![]);
683
684 assert_eq!(collection.get_level_count(Level::Error), 0);
685 assert_eq!(collection.get_level_count(Level::Warning), 0);
686 assert_eq!(collection.get_level_count(Level::Help), 0);
687 assert_eq!(collection.get_level_count(Level::Note), 0);
688
689 collection.push(Issue::error("error"));
690
691 assert_eq!(collection.get_level_count(Level::Error), 1);
692 assert_eq!(collection.get_level_count(Level::Warning), 0);
693 assert_eq!(collection.get_level_count(Level::Help), 0);
694 assert_eq!(collection.get_level_count(Level::Note), 0);
695
696 collection.push(Issue::warning("warning"));
697
698 assert_eq!(collection.get_level_count(Level::Error), 1);
699 assert_eq!(collection.get_level_count(Level::Warning), 1);
700 assert_eq!(collection.get_level_count(Level::Help), 0);
701 assert_eq!(collection.get_level_count(Level::Note), 0);
702
703 collection.push(Issue::help("help"));
704
705 assert_eq!(collection.get_level_count(Level::Error), 1);
706 assert_eq!(collection.get_level_count(Level::Warning), 1);
707 assert_eq!(collection.get_level_count(Level::Help), 1);
708 assert_eq!(collection.get_level_count(Level::Note), 0);
709
710 collection.push(Issue::note("note"));
711
712 assert_eq!(collection.get_level_count(Level::Error), 1);
713 assert_eq!(collection.get_level_count(Level::Warning), 1);
714 assert_eq!(collection.get_level_count(Level::Help), 1);
715 assert_eq!(collection.get_level_count(Level::Note), 1);
716 }
717}