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
16pub use termcolor::ColorChoice;
17
18mod internal;
19
20pub mod error;
21pub mod reporter;
22
23#[derive(Debug, PartialEq, Eq, Ord, Copy, Clone, Hash, PartialOrd, Deserialize, Serialize)]
25pub enum AnnotationKind {
26 Primary,
28 Secondary,
30}
31
32#[derive(Debug, PartialEq, Eq, Ord, Clone, Hash, PartialOrd, Deserialize, Serialize)]
34pub struct Annotation {
35 pub message: Option<String>,
37 pub kind: AnnotationKind,
39 pub span: Span,
41}
42
43#[derive(Debug, PartialEq, Eq, Ord, Copy, Clone, Hash, PartialOrd, Deserialize, Serialize, Display, VariantNames)]
45#[strum(serialize_all = "lowercase")]
46pub enum Level {
47 #[serde(alias = "note")]
49 Note,
50 #[serde(alias = "help")]
52 Help,
53 #[serde(alias = "warning", alias = "warn")]
55 Warning,
56 #[serde(alias = "error", alias = "err")]
58 Error,
59}
60
61impl FromStr for Level {
62 type Err = ();
63
64 fn from_str(s: &str) -> Result<Self, Self::Err> {
65 match s.to_lowercase().as_str() {
66 "note" => Ok(Self::Note),
67 "help" => Ok(Self::Help),
68 "warning" => Ok(Self::Warning),
69 "error" => Ok(Self::Error),
70 _ => Err(()),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
77pub struct Issue {
78 pub level: Level,
80 pub code: Option<String>,
82 pub message: String,
84 pub notes: Vec<String>,
86 pub help: Option<String>,
88 pub link: Option<String>,
90 pub annotations: Vec<Annotation>,
92 pub suggestions: Vec<(FileId, FixPlan)>,
94}
95
96#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
98pub struct IssueCollection {
99 issues: Vec<Issue>,
100}
101
102impl AnnotationKind {
103 #[inline]
105 pub const fn is_primary(&self) -> bool {
106 matches!(self, AnnotationKind::Primary)
107 }
108
109 #[inline]
111 pub const fn is_secondary(&self) -> bool {
112 matches!(self, AnnotationKind::Secondary)
113 }
114}
115
116impl Annotation {
117 pub fn new(kind: AnnotationKind, span: Span) -> Self {
134 Self { message: None, kind, span }
135 }
136
137 pub fn primary(span: Span) -> Self {
154 Self::new(AnnotationKind::Primary, span)
155 }
156
157 pub fn secondary(span: Span) -> Self {
174 Self::new(AnnotationKind::Secondary, span)
175 }
176
177 #[must_use]
194 pub fn with_message(mut self, message: impl Into<String>) -> Self {
195 self.message = Some(message.into());
196
197 self
198 }
199
200 pub fn is_primary(&self) -> bool {
202 self.kind == AnnotationKind::Primary
203 }
204}
205
206impl Level {
207 pub fn downgrade(&self) -> Self {
234 match self {
235 Level::Error => Level::Warning,
236 Level::Warning => Level::Help,
237 Level::Help | Level::Note => Level::Note,
238 }
239 }
240}
241
242impl Issue {
243 pub fn new(level: Level, message: impl Into<String>) -> Self {
253 Self {
254 level,
255 code: None,
256 message: message.into(),
257 annotations: Vec::new(),
258 notes: Vec::new(),
259 help: None,
260 link: None,
261 suggestions: Vec::new(),
262 }
263 }
264
265 pub fn error(message: impl Into<String>) -> Self {
275 Self::new(Level::Error, message)
276 }
277
278 pub fn warning(message: impl Into<String>) -> Self {
288 Self::new(Level::Warning, message)
289 }
290
291 pub fn help(message: impl Into<String>) -> Self {
301 Self::new(Level::Help, message)
302 }
303
304 pub fn note(message: impl Into<String>) -> Self {
314 Self::new(Level::Note, message)
315 }
316
317 #[must_use]
327 pub fn with_code(mut self, code: impl Into<String>) -> Self {
328 self.code = Some(code.into());
329
330 self
331 }
332
333 #[must_use]
351 pub fn with_annotation(mut self, annotation: Annotation) -> Self {
352 self.annotations.push(annotation);
353
354 self
355 }
356
357 #[must_use]
358 pub fn with_annotations(mut self, annotation: impl IntoIterator<Item = Annotation>) -> Self {
359 self.annotations.extend(annotation);
360
361 self
362 }
363
364 #[must_use]
374 pub fn with_note(mut self, note: impl Into<String>) -> Self {
375 self.notes.push(note.into());
376
377 self
378 }
379
380 #[must_use]
392 pub fn with_help(mut self, help: impl Into<String>) -> Self {
393 self.help = Some(help.into());
394
395 self
396 }
397
398 #[must_use]
408 pub fn with_link(mut self, link: impl Into<String>) -> Self {
409 self.link = Some(link.into());
410
411 self
412 }
413
414 #[must_use]
416 pub fn with_suggestion(mut self, file_id: FileId, plan: FixPlan) -> Self {
417 self.suggestions.push((file_id, plan));
418
419 self
420 }
421
422 #[must_use]
424 pub fn take_suggestions(&mut self) -> Vec<(FileId, FixPlan)> {
425 self.suggestions.drain(..).collect()
426 }
427}
428
429impl IssueCollection {
430 pub fn new() -> Self {
431 Self { issues: Vec::new() }
432 }
433
434 pub fn from(issues: impl IntoIterator<Item = Issue>) -> Self {
435 Self { issues: issues.into_iter().collect() }
436 }
437
438 pub fn push(&mut self, issue: Issue) {
439 if self.issues.contains(&issue) {
440 return; }
442
443 self.issues.push(issue);
444 }
445
446 pub fn extend(&mut self, issues: impl IntoIterator<Item = Issue>) {
447 self.issues.extend(issues);
448 }
449
450 pub fn shrink_to_fit(&mut self) {
451 self.issues.shrink_to_fit();
452 }
453
454 pub fn is_empty(&self) -> bool {
455 self.issues.is_empty()
456 }
457
458 pub fn len(&self) -> usize {
459 self.issues.len()
460 }
461
462 pub fn with_maximum_level(self, level: Level) -> Self {
465 Self { issues: self.issues.into_iter().filter(|issue| issue.level <= level).collect() }
466 }
467
468 pub fn with_minimum_level(self, level: Level) -> Self {
471 Self { issues: self.issues.into_iter().filter(|issue| issue.level >= level).collect() }
472 }
473
474 pub fn has_minimum_level(&self, level: Level) -> bool {
477 self.issues.iter().any(|issue| issue.level >= level)
478 }
479
480 pub fn get_level_count(&self, level: Level) -> usize {
482 self.issues.iter().filter(|issue| issue.level == level).count()
483 }
484
485 pub fn get_highest_level(&self) -> Option<Level> {
487 self.issues.iter().map(|issue| issue.level).max()
488 }
489
490 pub fn filter_out_ignored(&mut self, ignore: &[String]) {
491 self.issues.retain(|issue| if let Some(code) = &issue.code { !ignore.contains(code) } else { true });
492 }
493
494 pub fn take_suggestions(&mut self) -> impl Iterator<Item = (FileId, FixPlan)> + '_ {
495 self.issues.iter_mut().flat_map(|issue| issue.take_suggestions())
496 }
497
498 pub fn only_fixable(self) -> impl Iterator<Item = Issue> {
499 self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty())
500 }
501
502 pub fn sorted(self) -> Self {
507 let mut issues = self.issues;
508
509 issues.sort_by(|a, b| match a.level.cmp(&b.level) {
510 Ordering::Greater => Ordering::Greater,
511 Ordering::Less => Ordering::Less,
512 Ordering::Equal => match a.code.as_deref().cmp(&b.code.as_deref()) {
513 Ordering::Less => Ordering::Less,
514 Ordering::Greater => Ordering::Greater,
515 Ordering::Equal => {
516 let a_span = a
517 .annotations
518 .iter()
519 .find(|annotation| annotation.is_primary())
520 .map(|annotation| annotation.span);
521
522 let b_span = b
523 .annotations
524 .iter()
525 .find(|annotation| annotation.is_primary())
526 .map(|annotation| annotation.span);
527
528 match (a_span, b_span) {
529 (Some(a_span), Some(b_span)) => a_span.cmp(&b_span),
530 (Some(_), None) => Ordering::Less,
531 (None, Some(_)) => Ordering::Greater,
532 (None, None) => Ordering::Equal,
533 }
534 }
535 },
536 });
537
538 Self { issues }
539 }
540
541 pub fn iter(&self) -> impl Iterator<Item = &Issue> {
542 self.issues.iter()
543 }
544
545 pub fn to_fix_plans(self) -> HashMap<FileId, FixPlan> {
546 let mut plans: HashMap<FileId, FixPlan> = HashMap::default();
547 for issue in self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty()) {
548 for suggestion in issue.suggestions.into_iter() {
549 match plans.entry(suggestion.0) {
550 Entry::Occupied(mut occupied_entry) => {
551 occupied_entry.get_mut().merge(suggestion.1);
552 }
553 Entry::Vacant(vacant_entry) => {
554 vacant_entry.insert(suggestion.1);
555 }
556 }
557 }
558 }
559
560 plans
561 }
562}
563
564impl IntoIterator for IssueCollection {
565 type Item = Issue;
566
567 type IntoIter = std::vec::IntoIter<Issue>;
568
569 fn into_iter(self) -> Self::IntoIter {
570 self.issues.into_iter()
571 }
572}
573
574impl Default for IssueCollection {
575 fn default() -> Self {
576 Self::new()
577 }
578}
579
580impl IntoIterator for Issue {
581 type Item = Issue;
582 type IntoIter = Once<Issue>;
583
584 fn into_iter(self) -> Self::IntoIter {
585 std::iter::once(self)
586 }
587}
588
589impl FromIterator<Issue> for IssueCollection {
590 fn from_iter<T: IntoIterator<Item = Issue>>(iter: T) -> Self {
591 Self { issues: iter.into_iter().collect() }
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598
599 #[test]
600 pub fn test_highest_collection_level() {
601 let mut collection = IssueCollection::from(vec![]);
602 assert_eq!(collection.get_highest_level(), None);
603
604 collection.push(Issue::note("note"));
605 assert_eq!(collection.get_highest_level(), Some(Level::Note));
606
607 collection.push(Issue::help("help"));
608 assert_eq!(collection.get_highest_level(), Some(Level::Help));
609
610 collection.push(Issue::warning("warning"));
611 assert_eq!(collection.get_highest_level(), Some(Level::Warning));
612
613 collection.push(Issue::error("error"));
614 assert_eq!(collection.get_highest_level(), Some(Level::Error));
615 }
616
617 #[test]
618 pub fn test_level_downgrade() {
619 assert_eq!(Level::Error.downgrade(), Level::Warning);
620 assert_eq!(Level::Warning.downgrade(), Level::Help);
621 assert_eq!(Level::Help.downgrade(), Level::Note);
622 assert_eq!(Level::Note.downgrade(), Level::Note);
623 }
624
625 #[test]
626 pub fn test_issue_collection_with_maximum_level() {
627 let mut collection = IssueCollection::from(vec![
628 Issue::error("error"),
629 Issue::warning("warning"),
630 Issue::help("help"),
631 Issue::note("note"),
632 ]);
633
634 collection = collection.with_maximum_level(Level::Warning);
635 assert_eq!(collection.len(), 3);
636 assert_eq!(
637 collection.iter().map(|issue| issue.level).collect::<Vec<_>>(),
638 vec![Level::Warning, Level::Help, Level::Note]
639 );
640 }
641
642 #[test]
643 pub fn test_issue_collection_with_minimum_level() {
644 let mut collection = IssueCollection::from(vec![
645 Issue::error("error"),
646 Issue::warning("warning"),
647 Issue::help("help"),
648 Issue::note("note"),
649 ]);
650
651 collection = collection.with_minimum_level(Level::Warning);
652 assert_eq!(collection.len(), 2);
653 assert_eq!(collection.iter().map(|issue| issue.level).collect::<Vec<_>>(), vec![Level::Error, Level::Warning,]);
654 }
655
656 #[test]
657 pub fn test_issue_collection_has_minimum_level() {
658 let mut collection = IssueCollection::from(vec![]);
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::note("note"));
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::help("help"));
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 collection.push(Issue::warning("warning"));
680
681 assert!(!collection.has_minimum_level(Level::Error));
682 assert!(collection.has_minimum_level(Level::Warning));
683 assert!(collection.has_minimum_level(Level::Help));
684 assert!(collection.has_minimum_level(Level::Note));
685
686 collection.push(Issue::error("error"));
687
688 assert!(collection.has_minimum_level(Level::Error));
689 assert!(collection.has_minimum_level(Level::Warning));
690 assert!(collection.has_minimum_level(Level::Help));
691 assert!(collection.has_minimum_level(Level::Note));
692 }
693
694 #[test]
695 pub fn test_issue_collection_level_count() {
696 let mut collection = IssueCollection::from(vec![]);
697
698 assert_eq!(collection.get_level_count(Level::Error), 0);
699 assert_eq!(collection.get_level_count(Level::Warning), 0);
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::error("error"));
704
705 assert_eq!(collection.get_level_count(Level::Error), 1);
706 assert_eq!(collection.get_level_count(Level::Warning), 0);
707 assert_eq!(collection.get_level_count(Level::Help), 0);
708 assert_eq!(collection.get_level_count(Level::Note), 0);
709
710 collection.push(Issue::warning("warning"));
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), 0);
715 assert_eq!(collection.get_level_count(Level::Note), 0);
716
717 collection.push(Issue::help("help"));
718
719 assert_eq!(collection.get_level_count(Level::Error), 1);
720 assert_eq!(collection.get_level_count(Level::Warning), 1);
721 assert_eq!(collection.get_level_count(Level::Help), 1);
722 assert_eq!(collection.get_level_count(Level::Note), 0);
723
724 collection.push(Issue::note("note"));
725
726 assert_eq!(collection.get_level_count(Level::Error), 1);
727 assert_eq!(collection.get_level_count(Level::Warning), 1);
728 assert_eq!(collection.get_level_count(Level::Help), 1);
729 assert_eq!(collection.get_level_count(Level::Note), 1);
730 }
731}