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 Annotation {
97 pub fn new(kind: AnnotationKind, span: Span) -> Self {
112 Self { message: None, kind, span }
113 }
114
115 pub fn primary(span: Span) -> Self {
130 Self::new(AnnotationKind::Primary, span)
131 }
132
133 pub fn secondary(span: Span) -> Self {
148 Self::new(AnnotationKind::Secondary, span)
149 }
150
151 #[must_use]
166 pub fn with_message(mut self, message: impl Into<String>) -> Self {
167 self.message = Some(message.into());
168
169 self
170 }
171
172 pub fn is_primary(&self) -> bool {
174 self.kind == AnnotationKind::Primary
175 }
176}
177
178impl Level {
179 pub fn downgrade(&self) -> Self {
206 match self {
207 Level::Error => Level::Warning,
208 Level::Warning => Level::Help,
209 Level::Help | Level::Note => Level::Note,
210 }
211 }
212}
213
214impl Issue {
215 pub fn new(level: Level, message: impl Into<String>) -> Self {
225 Self {
226 level,
227 code: None,
228 message: message.into(),
229 annotations: Vec::new(),
230 notes: Vec::new(),
231 help: None,
232 link: None,
233 suggestions: Vec::new(),
234 }
235 }
236
237 pub fn error(message: impl Into<String>) -> Self {
247 Self::new(Level::Error, message)
248 }
249
250 pub fn warning(message: impl Into<String>) -> Self {
260 Self::new(Level::Warning, message)
261 }
262
263 pub fn help(message: impl Into<String>) -> Self {
273 Self::new(Level::Help, message)
274 }
275
276 pub fn note(message: impl Into<String>) -> Self {
286 Self::new(Level::Note, message)
287 }
288
289 #[must_use]
299 pub fn with_code(mut self, code: impl Into<String>) -> Self {
300 self.code = Some(code.into());
301
302 self
303 }
304
305 #[must_use]
321 pub fn with_annotation(mut self, annotation: Annotation) -> Self {
322 self.annotations.push(annotation);
323
324 self
325 }
326
327 #[must_use]
328 pub fn with_annotations(mut self, annotation: impl IntoIterator<Item = Annotation>) -> Self {
329 self.annotations.extend(annotation);
330
331 self
332 }
333
334 #[must_use]
344 pub fn with_note(mut self, note: impl Into<String>) -> Self {
345 self.notes.push(note.into());
346
347 self
348 }
349
350 #[must_use]
362 pub fn with_help(mut self, help: impl Into<String>) -> Self {
363 self.help = Some(help.into());
364
365 self
366 }
367
368 #[must_use]
378 pub fn with_link(mut self, link: impl Into<String>) -> Self {
379 self.link = Some(link.into());
380
381 self
382 }
383
384 #[must_use]
386 pub fn with_suggestion(mut self, source: SourceIdentifier, plan: FixPlan) -> Self {
387 self.suggestions.push((source, plan));
388
389 self
390 }
391
392 #[must_use]
394 pub fn take_suggestions(&mut self) -> Vec<(SourceIdentifier, FixPlan)> {
395 self.suggestions.drain(..).collect()
396 }
397}
398
399impl IssueCollection {
400 pub fn new() -> Self {
401 Self { issues: Vec::new() }
402 }
403
404 pub fn from(issues: impl IntoIterator<Item = Issue>) -> Self {
405 Self { issues: issues.into_iter().collect() }
406 }
407
408 pub fn push(&mut self, issue: Issue) {
409 self.issues.push(issue);
410 }
411
412 pub fn extend(&mut self, issues: impl IntoIterator<Item = Issue>) {
413 self.issues.extend(issues);
414 }
415
416 pub fn shrink_to_fit(&mut self) {
417 self.issues.shrink_to_fit();
418 }
419
420 pub fn is_empty(&self) -> bool {
421 self.issues.is_empty()
422 }
423
424 pub fn len(&self) -> usize {
425 self.issues.len()
426 }
427
428 pub fn with_maximum_level(self, level: Level) -> Self {
431 Self { issues: self.issues.into_iter().filter(|issue| issue.level <= level).collect() }
432 }
433
434 pub fn with_minimum_level(self, level: Level) -> Self {
437 Self { issues: self.issues.into_iter().filter(|issue| issue.level >= level).collect() }
438 }
439
440 pub fn has_minimum_level(&self, level: Level) -> bool {
443 self.issues.iter().any(|issue| issue.level >= level)
444 }
445
446 pub fn get_level_count(&self, level: Level) -> usize {
448 self.issues.iter().filter(|issue| issue.level == level).count()
449 }
450
451 pub fn get_highest_level(&self) -> Option<Level> {
453 self.issues.iter().map(|issue| issue.level).max()
454 }
455
456 pub fn with_code(self, code: impl Into<String>) -> IssueCollection {
457 let code = code.into();
458
459 Self { issues: self.issues.into_iter().map(|issue| issue.with_code(&code)).collect() }
460 }
461
462 pub fn take_suggestions(&mut self) -> impl Iterator<Item = (SourceIdentifier, FixPlan)> + '_ {
463 self.issues.iter_mut().flat_map(|issue| issue.take_suggestions())
464 }
465
466 pub fn only_fixable(self) -> impl Iterator<Item = Issue> {
467 self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty())
468 }
469
470 pub fn sorted(self) -> Self {
475 let mut issues = self.issues;
476
477 issues.sort_by(|a, b| match a.level.cmp(&b.level) {
478 Ordering::Greater => Ordering::Greater,
479 Ordering::Less => Ordering::Less,
480 Ordering::Equal => match a.code.as_deref().cmp(&b.code.as_deref()) {
481 Ordering::Less => Ordering::Less,
482 Ordering::Greater => Ordering::Greater,
483 Ordering::Equal => {
484 let a_span = a
485 .annotations
486 .iter()
487 .find(|annotation| annotation.is_primary())
488 .map(|annotation| annotation.span);
489
490 let b_span = b
491 .annotations
492 .iter()
493 .find(|annotation| annotation.is_primary())
494 .map(|annotation| annotation.span);
495
496 match (a_span, b_span) {
497 (Some(a_span), Some(b_span)) => a_span.cmp(&b_span),
498 (Some(_), None) => Ordering::Less,
499 (None, Some(_)) => Ordering::Greater,
500 (None, None) => Ordering::Equal,
501 }
502 }
503 },
504 });
505
506 Self { issues }
507 }
508
509 pub fn iter(&self) -> impl Iterator<Item = &Issue> {
510 self.issues.iter()
511 }
512
513 pub fn to_fix_plans(self) -> HashMap<SourceIdentifier, FixPlan> {
514 let mut plans: HashMap<SourceIdentifier, FixPlan> = HashMap::default();
515 for issue in self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty()) {
516 for suggestion in issue.suggestions.into_iter() {
517 match plans.entry(suggestion.0) {
518 Entry::Occupied(mut occupied_entry) => {
519 occupied_entry.get_mut().merge(suggestion.1);
520 }
521 Entry::Vacant(vacant_entry) => {
522 vacant_entry.insert(suggestion.1);
523 }
524 }
525 }
526 }
527
528 plans
529 }
530}
531
532impl IntoIterator for IssueCollection {
533 type Item = Issue;
534
535 type IntoIter = std::vec::IntoIter<Issue>;
536
537 fn into_iter(self) -> Self::IntoIter {
538 self.issues.into_iter()
539 }
540}
541
542impl Default for IssueCollection {
543 fn default() -> Self {
544 Self::new()
545 }
546}
547
548impl IntoIterator for Issue {
549 type Item = Issue;
550 type IntoIter = Once<Issue>;
551
552 fn into_iter(self) -> Self::IntoIter {
553 std::iter::once(self)
554 }
555}
556
557impl FromIterator<Issue> for IssueCollection {
558 fn from_iter<T: IntoIterator<Item = Issue>>(iter: T) -> Self {
559 Self { issues: iter.into_iter().collect() }
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566
567 #[test]
568 pub fn test_highest_collection_level() {
569 let mut collection = IssueCollection::from(vec![]);
570 assert_eq!(collection.get_highest_level(), None);
571
572 collection.push(Issue::note("note"));
573 assert_eq!(collection.get_highest_level(), Some(Level::Note));
574
575 collection.push(Issue::help("help"));
576 assert_eq!(collection.get_highest_level(), Some(Level::Help));
577
578 collection.push(Issue::warning("warning"));
579 assert_eq!(collection.get_highest_level(), Some(Level::Warning));
580
581 collection.push(Issue::error("error"));
582 assert_eq!(collection.get_highest_level(), Some(Level::Error));
583 }
584
585 #[test]
586 pub fn test_level_downgrade() {
587 assert_eq!(Level::Error.downgrade(), Level::Warning);
588 assert_eq!(Level::Warning.downgrade(), Level::Help);
589 assert_eq!(Level::Help.downgrade(), Level::Note);
590 assert_eq!(Level::Note.downgrade(), Level::Note);
591 }
592
593 #[test]
594 pub fn test_issue_collection_with_maximum_level() {
595 let mut collection = IssueCollection::from(vec![
596 Issue::error("error"),
597 Issue::warning("warning"),
598 Issue::help("help"),
599 Issue::note("note"),
600 ]);
601
602 collection = collection.with_maximum_level(Level::Warning);
603 assert_eq!(collection.len(), 3);
604 assert_eq!(
605 collection.iter().map(|issue| issue.level).collect::<Vec<_>>(),
606 vec![Level::Warning, Level::Help, Level::Note]
607 );
608 }
609
610 #[test]
611 pub fn test_issue_collection_with_minimum_level() {
612 let mut collection = IssueCollection::from(vec![
613 Issue::error("error"),
614 Issue::warning("warning"),
615 Issue::help("help"),
616 Issue::note("note"),
617 ]);
618
619 collection = collection.with_minimum_level(Level::Warning);
620 assert_eq!(collection.len(), 2);
621 assert_eq!(collection.iter().map(|issue| issue.level).collect::<Vec<_>>(), vec![Level::Error, Level::Warning,]);
622 }
623
624 #[test]
625 pub fn test_issue_collection_has_minimum_level() {
626 let mut collection = IssueCollection::from(vec![]);
627
628 assert!(!collection.has_minimum_level(Level::Error));
629 assert!(!collection.has_minimum_level(Level::Warning));
630 assert!(!collection.has_minimum_level(Level::Help));
631 assert!(!collection.has_minimum_level(Level::Note));
632
633 collection.push(Issue::note("note"));
634
635 assert!(!collection.has_minimum_level(Level::Error));
636 assert!(!collection.has_minimum_level(Level::Warning));
637 assert!(!collection.has_minimum_level(Level::Help));
638 assert!(collection.has_minimum_level(Level::Note));
639
640 collection.push(Issue::help("help"));
641
642 assert!(!collection.has_minimum_level(Level::Error));
643 assert!(!collection.has_minimum_level(Level::Warning));
644 assert!(collection.has_minimum_level(Level::Help));
645 assert!(collection.has_minimum_level(Level::Note));
646
647 collection.push(Issue::warning("warning"));
648
649 assert!(!collection.has_minimum_level(Level::Error));
650 assert!(collection.has_minimum_level(Level::Warning));
651 assert!(collection.has_minimum_level(Level::Help));
652 assert!(collection.has_minimum_level(Level::Note));
653
654 collection.push(Issue::error("error"));
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
662 #[test]
663 pub fn test_issue_collection_level_count() {
664 let mut collection = IssueCollection::from(vec![]);
665
666 assert_eq!(collection.get_level_count(Level::Error), 0);
667 assert_eq!(collection.get_level_count(Level::Warning), 0);
668 assert_eq!(collection.get_level_count(Level::Help), 0);
669 assert_eq!(collection.get_level_count(Level::Note), 0);
670
671 collection.push(Issue::error("error"));
672
673 assert_eq!(collection.get_level_count(Level::Error), 1);
674 assert_eq!(collection.get_level_count(Level::Warning), 0);
675 assert_eq!(collection.get_level_count(Level::Help), 0);
676 assert_eq!(collection.get_level_count(Level::Note), 0);
677
678 collection.push(Issue::warning("warning"));
679
680 assert_eq!(collection.get_level_count(Level::Error), 1);
681 assert_eq!(collection.get_level_count(Level::Warning), 1);
682 assert_eq!(collection.get_level_count(Level::Help), 0);
683 assert_eq!(collection.get_level_count(Level::Note), 0);
684
685 collection.push(Issue::help("help"));
686
687 assert_eq!(collection.get_level_count(Level::Error), 1);
688 assert_eq!(collection.get_level_count(Level::Warning), 1);
689 assert_eq!(collection.get_level_count(Level::Help), 1);
690 assert_eq!(collection.get_level_count(Level::Note), 0);
691
692 collection.push(Issue::note("note"));
693
694 assert_eq!(collection.get_level_count(Level::Error), 1);
695 assert_eq!(collection.get_level_count(Level::Warning), 1);
696 assert_eq!(collection.get_level_count(Level::Help), 1);
697 assert_eq!(collection.get_level_count(Level::Note), 1);
698 }
699}