1use std::cmp::Ordering;
2use std::collections::hash_map::Entry;
3use std::iter::Once;
4
5use ahash::HashMap;
6use serde::Deserialize;
7use serde::Serialize;
8use strum::Display;
9
10use mago_fixer::FixPlan;
11use mago_source::SourceIdentifier;
12use mago_span::Span;
13
14mod internal;
15
16pub mod error;
17pub mod reporter;
18
19#[derive(Debug, PartialEq, Eq, Ord, Copy, Clone, Hash, PartialOrd, Deserialize, Serialize)]
21pub enum AnnotationKind {
22 Primary,
24 Secondary,
26}
27
28#[derive(Debug, PartialEq, Eq, Ord, Clone, Hash, PartialOrd, Deserialize, Serialize)]
30pub struct Annotation {
31 pub message: Option<String>,
33 pub kind: AnnotationKind,
35 pub span: Span,
37}
38
39#[derive(Debug, PartialEq, Eq, Ord, Copy, Clone, Hash, PartialOrd, Deserialize, Serialize, Display)]
41pub enum Level {
42 Note,
44 Help,
46 Warning,
48 Error,
50}
51
52#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
54pub struct Issue {
55 pub level: Level,
57 pub code: Option<String>,
59 pub message: String,
61 pub notes: Vec<String>,
63 pub help: Option<String>,
65 pub link: Option<String>,
67 pub annotations: Vec<Annotation>,
69 pub suggestions: Vec<(SourceIdentifier, FixPlan)>,
71}
72
73#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
75pub struct IssueCollection {
76 issues: Vec<Issue>,
77}
78
79impl Annotation {
80 pub fn new(kind: AnnotationKind, span: Span) -> Self {
95 Self { message: None, kind, span }
96 }
97
98 pub fn primary(span: Span) -> Self {
113 Self::new(AnnotationKind::Primary, span)
114 }
115
116 pub fn secondary(span: Span) -> Self {
131 Self::new(AnnotationKind::Secondary, span)
132 }
133
134 #[must_use]
149 pub fn with_message(mut self, message: impl Into<String>) -> Self {
150 self.message = Some(message.into());
151
152 self
153 }
154
155 pub fn is_primary(&self) -> bool {
157 self.kind == AnnotationKind::Primary
158 }
159}
160
161impl Level {
162 pub fn downgrade(&self) -> Self {
189 match self {
190 Level::Error => Level::Warning,
191 Level::Warning => Level::Help,
192 Level::Help | Level::Note => Level::Note,
193 }
194 }
195}
196
197impl Issue {
198 pub fn new(level: Level, message: impl Into<String>) -> Self {
208 Self {
209 level,
210 code: None,
211 message: message.into(),
212 annotations: Vec::new(),
213 notes: Vec::new(),
214 help: None,
215 link: None,
216 suggestions: Vec::new(),
217 }
218 }
219
220 pub fn error(message: impl Into<String>) -> Self {
230 Self::new(Level::Error, message)
231 }
232
233 pub fn warning(message: impl Into<String>) -> Self {
243 Self::new(Level::Warning, message)
244 }
245
246 pub fn help(message: impl Into<String>) -> Self {
256 Self::new(Level::Help, message)
257 }
258
259 pub fn note(message: impl Into<String>) -> Self {
269 Self::new(Level::Note, message)
270 }
271
272 #[must_use]
282 pub fn with_code(mut self, code: impl Into<String>) -> Self {
283 self.code = Some(code.into());
284
285 self
286 }
287
288 #[must_use]
304 pub fn with_annotation(mut self, annotation: Annotation) -> Self {
305 self.annotations.push(annotation);
306
307 self
308 }
309
310 #[must_use]
311 pub fn with_annotations(mut self, annotation: impl IntoIterator<Item = Annotation>) -> Self {
312 self.annotations.extend(annotation);
313
314 self
315 }
316
317 #[must_use]
327 pub fn with_note(mut self, note: impl Into<String>) -> Self {
328 self.notes.push(note.into());
329
330 self
331 }
332
333 #[must_use]
345 pub fn with_help(mut self, help: impl Into<String>) -> Self {
346 self.help = Some(help.into());
347
348 self
349 }
350
351 #[must_use]
361 pub fn with_link(mut self, link: impl Into<String>) -> Self {
362 self.link = Some(link.into());
363
364 self
365 }
366
367 #[must_use]
369 pub fn with_suggestion(mut self, source: SourceIdentifier, plan: FixPlan) -> Self {
370 self.suggestions.push((source, plan));
371
372 self
373 }
374
375 #[must_use]
377 pub fn take_suggestions(&mut self) -> Vec<(SourceIdentifier, FixPlan)> {
378 self.suggestions.drain(..).collect()
379 }
380}
381
382impl IssueCollection {
383 pub fn new() -> Self {
384 Self { issues: Vec::new() }
385 }
386
387 pub fn from(issues: impl IntoIterator<Item = Issue>) -> Self {
388 Self { issues: issues.into_iter().collect() }
389 }
390
391 pub fn push(&mut self, issue: Issue) {
392 self.issues.push(issue);
393 }
394
395 pub fn extend(&mut self, issues: impl IntoIterator<Item = Issue>) {
396 self.issues.extend(issues);
397 }
398
399 pub fn shrink_to_fit(&mut self) {
400 self.issues.shrink_to_fit();
401 }
402
403 pub fn is_empty(&self) -> bool {
404 self.issues.is_empty()
405 }
406
407 pub fn len(&self) -> usize {
408 self.issues.len()
409 }
410
411 pub fn with_maximum_level(self, level: Level) -> Self {
414 Self { issues: self.issues.into_iter().filter(|issue| issue.level <= level).collect() }
415 }
416
417 pub fn with_minimum_level(self, level: Level) -> Self {
420 Self { issues: self.issues.into_iter().filter(|issue| issue.level >= level).collect() }
421 }
422
423 pub fn has_minimum_level(&self, level: Level) -> bool {
426 self.issues.iter().any(|issue| issue.level >= level)
427 }
428
429 pub fn get_level_count(&self, level: Level) -> usize {
431 self.issues.iter().filter(|issue| issue.level == level).count()
432 }
433
434 pub fn get_highest_level(&self) -> Option<Level> {
436 self.issues.iter().map(|issue| issue.level).max()
437 }
438
439 pub fn with_code(self, code: impl Into<String>) -> IssueCollection {
440 let code = code.into();
441
442 Self { issues: self.issues.into_iter().map(|issue| issue.with_code(&code)).collect() }
443 }
444
445 pub fn take_suggestions(&mut self) -> impl Iterator<Item = (SourceIdentifier, FixPlan)> + '_ {
446 self.issues.iter_mut().flat_map(|issue| issue.take_suggestions())
447 }
448
449 pub fn only_fixable(self) -> impl Iterator<Item = Issue> {
450 self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty())
451 }
452
453 pub fn sorted(self) -> Self {
458 let mut issues = self.issues;
459
460 issues.sort_by(|a, b| match a.level.cmp(&b.level) {
461 Ordering::Greater => Ordering::Greater,
462 Ordering::Less => Ordering::Less,
463 Ordering::Equal => match a.code.as_deref().cmp(&b.code.as_deref()) {
464 Ordering::Less => Ordering::Less,
465 Ordering::Greater => Ordering::Greater,
466 Ordering::Equal => {
467 let a_span = a
468 .annotations
469 .iter()
470 .find(|annotation| annotation.is_primary())
471 .map(|annotation| annotation.span);
472
473 let b_span = b
474 .annotations
475 .iter()
476 .find(|annotation| annotation.is_primary())
477 .map(|annotation| annotation.span);
478
479 match (a_span, b_span) {
480 (Some(a_span), Some(b_span)) => a_span.cmp(&b_span),
481 (Some(_), None) => Ordering::Less,
482 (None, Some(_)) => Ordering::Greater,
483 (None, None) => Ordering::Equal,
484 }
485 }
486 },
487 });
488
489 Self { issues }
490 }
491
492 pub fn iter(&self) -> impl Iterator<Item = &Issue> {
493 self.issues.iter()
494 }
495
496 pub fn to_fix_plans(self) -> HashMap<SourceIdentifier, FixPlan> {
497 let mut plans: HashMap<SourceIdentifier, FixPlan> = HashMap::default();
498 for issue in self.issues.into_iter().filter(|issue| !issue.suggestions.is_empty()) {
499 for suggestion in issue.suggestions.into_iter() {
500 match plans.entry(suggestion.0) {
501 Entry::Occupied(mut occupied_entry) => {
502 occupied_entry.get_mut().merge(suggestion.1);
503 }
504 Entry::Vacant(vacant_entry) => {
505 vacant_entry.insert(suggestion.1);
506 }
507 }
508 }
509 }
510
511 plans
512 }
513}
514
515impl IntoIterator for IssueCollection {
516 type Item = Issue;
517
518 type IntoIter = std::vec::IntoIter<Issue>;
519
520 fn into_iter(self) -> Self::IntoIter {
521 self.issues.into_iter()
522 }
523}
524
525impl Default for IssueCollection {
526 fn default() -> Self {
527 Self::new()
528 }
529}
530
531impl IntoIterator for Issue {
532 type Item = Issue;
533 type IntoIter = Once<Issue>;
534
535 fn into_iter(self) -> Self::IntoIter {
536 std::iter::once(self)
537 }
538}
539
540impl FromIterator<Issue> for IssueCollection {
541 fn from_iter<T: IntoIterator<Item = Issue>>(iter: T) -> Self {
542 Self { issues: iter.into_iter().collect() }
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
551 pub fn test_highest_collection_level() {
552 let mut collection = IssueCollection::from(vec![]);
553 assert_eq!(collection.get_highest_level(), None);
554
555 collection.push(Issue::note("note"));
556 assert_eq!(collection.get_highest_level(), Some(Level::Note));
557
558 collection.push(Issue::help("help"));
559 assert_eq!(collection.get_highest_level(), Some(Level::Help));
560
561 collection.push(Issue::warning("warning"));
562 assert_eq!(collection.get_highest_level(), Some(Level::Warning));
563
564 collection.push(Issue::error("error"));
565 assert_eq!(collection.get_highest_level(), Some(Level::Error));
566 }
567
568 #[test]
569 pub fn test_level_downgrade() {
570 assert_eq!(Level::Error.downgrade(), Level::Warning);
571 assert_eq!(Level::Warning.downgrade(), Level::Help);
572 assert_eq!(Level::Help.downgrade(), Level::Note);
573 assert_eq!(Level::Note.downgrade(), Level::Note);
574 }
575
576 #[test]
577 pub fn test_issue_collection_with_maximum_level() {
578 let mut collection = IssueCollection::from(vec![
579 Issue::error("error"),
580 Issue::warning("warning"),
581 Issue::help("help"),
582 Issue::note("note"),
583 ]);
584
585 collection = collection.with_maximum_level(Level::Warning);
586 assert_eq!(collection.len(), 3);
587 assert_eq!(
588 collection.iter().map(|issue| issue.level).collect::<Vec<_>>(),
589 vec![Level::Warning, Level::Help, Level::Note]
590 );
591 }
592
593 #[test]
594 pub fn test_issue_collection_with_minimum_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_minimum_level(Level::Warning);
603 assert_eq!(collection.len(), 2);
604 assert_eq!(collection.iter().map(|issue| issue.level).collect::<Vec<_>>(), vec![Level::Error, Level::Warning,]);
605 }
606
607 #[test]
608 pub fn test_issue_collection_has_minimum_level() {
609 let mut collection = IssueCollection::from(vec![]);
610
611 assert!(!collection.has_minimum_level(Level::Error));
612 assert!(!collection.has_minimum_level(Level::Warning));
613 assert!(!collection.has_minimum_level(Level::Help));
614 assert!(!collection.has_minimum_level(Level::Note));
615
616 collection.push(Issue::note("note"));
617
618 assert!(!collection.has_minimum_level(Level::Error));
619 assert!(!collection.has_minimum_level(Level::Warning));
620 assert!(!collection.has_minimum_level(Level::Help));
621 assert!(collection.has_minimum_level(Level::Note));
622
623 collection.push(Issue::help("help"));
624
625 assert!(!collection.has_minimum_level(Level::Error));
626 assert!(!collection.has_minimum_level(Level::Warning));
627 assert!(collection.has_minimum_level(Level::Help));
628 assert!(collection.has_minimum_level(Level::Note));
629
630 collection.push(Issue::warning("warning"));
631
632 assert!(!collection.has_minimum_level(Level::Error));
633 assert!(collection.has_minimum_level(Level::Warning));
634 assert!(collection.has_minimum_level(Level::Help));
635 assert!(collection.has_minimum_level(Level::Note));
636
637 collection.push(Issue::error("error"));
638
639 assert!(collection.has_minimum_level(Level::Error));
640 assert!(collection.has_minimum_level(Level::Warning));
641 assert!(collection.has_minimum_level(Level::Help));
642 assert!(collection.has_minimum_level(Level::Note));
643 }
644
645 #[test]
646 pub fn test_issue_collection_level_count() {
647 let mut collection = IssueCollection::from(vec![]);
648
649 assert_eq!(collection.get_level_count(Level::Error), 0);
650 assert_eq!(collection.get_level_count(Level::Warning), 0);
651 assert_eq!(collection.get_level_count(Level::Help), 0);
652 assert_eq!(collection.get_level_count(Level::Note), 0);
653
654 collection.push(Issue::error("error"));
655
656 assert_eq!(collection.get_level_count(Level::Error), 1);
657 assert_eq!(collection.get_level_count(Level::Warning), 0);
658 assert_eq!(collection.get_level_count(Level::Help), 0);
659 assert_eq!(collection.get_level_count(Level::Note), 0);
660
661 collection.push(Issue::warning("warning"));
662
663 assert_eq!(collection.get_level_count(Level::Error), 1);
664 assert_eq!(collection.get_level_count(Level::Warning), 1);
665 assert_eq!(collection.get_level_count(Level::Help), 0);
666 assert_eq!(collection.get_level_count(Level::Note), 0);
667
668 collection.push(Issue::help("help"));
669
670 assert_eq!(collection.get_level_count(Level::Error), 1);
671 assert_eq!(collection.get_level_count(Level::Warning), 1);
672 assert_eq!(collection.get_level_count(Level::Help), 1);
673 assert_eq!(collection.get_level_count(Level::Note), 0);
674
675 collection.push(Issue::note("note"));
676
677 assert_eq!(collection.get_level_count(Level::Error), 1);
678 assert_eq!(collection.get_level_count(Level::Warning), 1);
679 assert_eq!(collection.get_level_count(Level::Help), 1);
680 assert_eq!(collection.get_level_count(Level::Note), 1);
681 }
682}