1use crate::{SourceMap, Span};
6use anstyle::{AnsiColor, Color};
7use std::{
8 borrow::Cow,
9 fmt::{self, Write},
10 hash::{Hash, Hasher},
11 iter,
12 ops::Deref,
13 panic::Location,
14};
15
16mod builder;
17pub use builder::{DiagBuilder, EmissionGuarantee};
18
19mod context;
20pub use context::{DiagCtxt, DiagCtxtFlags};
21
22mod emitter;
23pub use emitter::{
24 DynEmitter, Emitter, HumanBufferEmitter, HumanEmitter, InMemoryEmitter, LocalEmitter,
25 SilentEmitter,
26};
27#[cfg(feature = "json")]
28pub use emitter::{JsonEmitter, Severity, SolcDiagnostic, SourceLocation};
29
30mod message;
31pub use message::{DiagMsg, MultiSpan, SpanLabel};
32
33pub struct EmittedDiagnostics(pub(crate) String);
37
38impl fmt::Debug for EmittedDiagnostics {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 f.write_str(&self.0)
41 }
42}
43
44impl fmt::Display for EmittedDiagnostics {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 f.write_str(&self.0)
47 }
48}
49
50impl std::error::Error for EmittedDiagnostics {}
51
52impl EmittedDiagnostics {
53 pub fn is_empty(&self) -> bool {
55 self.0.is_empty()
56 }
57}
58
59#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub struct ErrorGuaranteed(());
63
64impl fmt::Debug for ErrorGuaranteed {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.write_str("ErrorGuaranteed")
67 }
68}
69
70impl ErrorGuaranteed {
71 #[inline]
75 pub const fn new_unchecked() -> Self {
76 Self(())
77 }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
83pub struct BugAbort;
84
85pub struct ExplicitBug;
88
89pub struct FatalAbort;
91
92#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
103pub struct DiagId {
104 s: Cow<'static, str>,
105}
106
107impl DiagId {
108 pub fn new_str(s: impl Into<Cow<'static, str>>) -> Self {
113 Self { s: s.into() }
114 }
115
116 #[doc(hidden)]
120 #[cfg_attr(debug_assertions, track_caller)]
121 pub fn new_from_macro(id: u32) -> Self {
122 debug_assert!((1..=9999).contains(&id), "error code must be in range 0001-9999");
123 Self { s: Cow::Owned(format!("{id:04}")) }
124 }
125
126 pub fn as_str(&self) -> &str {
128 &self.s
129 }
130}
131
132#[macro_export]
141macro_rules! error_code {
142 ($id:literal) => {
143 $crate::diagnostics::DiagId::new_from_macro($id)
144 };
145}
146
147#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
149pub enum Level {
150 Bug,
154
155 Fatal,
160
161 Error,
166
167 Warning,
171
172 Note,
177
178 OnceNote,
182
183 Help,
188
189 OnceHelp,
193
194 FailureNote,
198
199 Allow,
203}
204
205impl Level {
206 pub fn to_str(self) -> &'static str {
208 match self {
209 Self::Bug => "error: internal compiler error",
210 Self::Fatal | Self::Error => "error",
211 Self::Warning => "warning",
212 Self::Note | Self::OnceNote => "note",
213 Self::Help | Self::OnceHelp => "help",
214 Self::FailureNote => "failure-note",
215 Self::Allow
216 => unreachable!(),
218 }
219 }
220
221 #[inline]
223 pub fn is_error(self) -> bool {
224 match self {
225 Self::Bug | Self::Fatal | Self::Error | Self::FailureNote => true,
226
227 Self::Warning
228 | Self::Note
229 | Self::OnceNote
230 | Self::Help
231 | Self::OnceHelp
232 | Self::Allow => false,
233 }
234 }
235
236 #[inline]
238 pub fn is_note(self) -> bool {
239 match self {
240 Self::Note | Self::OnceNote => true,
241
242 Self::Bug
243 | Self::Fatal
244 | Self::Error
245 | Self::FailureNote
246 | Self::Warning
247 | Self::Help
248 | Self::OnceHelp
249 | Self::Allow => false,
250 }
251 }
252
253 pub fn is_failure_note(&self) -> bool {
254 matches!(*self, Self::FailureNote)
255 }
256
257 #[inline]
259 pub const fn style(self) -> anstyle::Style {
260 anstyle::Style::new().fg_color(self.color()).bold()
261 }
262
263 #[inline]
265 pub const fn color(self) -> Option<Color> {
266 match self.ansi_color() {
267 Some(c) => Some(Color::Ansi(c)),
268 None => None,
269 }
270 }
271
272 #[inline]
274 pub const fn ansi_color(self) -> Option<AnsiColor> {
275 match self {
277 Self::Bug | Self::Fatal | Self::Error => Some(AnsiColor::BrightRed),
278 Self::Warning => {
279 Some(if cfg!(windows) { AnsiColor::BrightYellow } else { AnsiColor::Yellow })
280 }
281 Self::Note | Self::OnceNote => Some(AnsiColor::BrightGreen),
282 Self::Help | Self::OnceHelp => Some(AnsiColor::BrightCyan),
283 Self::FailureNote | Self::Allow => None,
284 }
285 }
286}
287
288#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
289pub enum Style {
290 MainHeaderMsg,
291 HeaderMsg,
292 LineAndColumn,
293 LineNumber,
294 Quotation,
295 UnderlinePrimary,
296 UnderlineSecondary,
297 LabelPrimary,
298 LabelSecondary,
299 NoStyle,
300 Level(Level),
301 Highlight,
302 Addition,
303 Removal,
304}
305
306impl Style {
307 pub const fn to_color_spec(self, level: Level) -> anstyle::Style {
309 use AnsiColor::*;
310
311 const BRIGHT_BLUE: Color = Color::Ansi(if cfg!(windows) { BrightCyan } else { BrightBlue });
315 const GREEN: Color = Color::Ansi(BrightGreen);
316 const MAGENTA: Color = Color::Ansi(BrightMagenta);
317 const RED: Color = Color::Ansi(BrightRed);
318 const WHITE: Color = Color::Ansi(BrightWhite);
319
320 let s = anstyle::Style::new();
321 match self {
322 Self::Addition => s.fg_color(Some(GREEN)),
323 Self::Removal => s.fg_color(Some(RED)),
324 Self::LineAndColumn => s,
325 Self::LineNumber => s.fg_color(Some(BRIGHT_BLUE)).bold(),
326 Self::Quotation => s,
327 Self::MainHeaderMsg => if cfg!(windows) { s.fg_color(Some(WHITE)) } else { s }.bold(),
328 Self::UnderlinePrimary | Self::LabelPrimary => s.fg_color(level.color()).bold(),
329 Self::UnderlineSecondary | Self::LabelSecondary => s.fg_color(Some(BRIGHT_BLUE)).bold(),
330 Self::HeaderMsg | Self::NoStyle => s,
331 Self::Level(level2) => s.fg_color(level2.color()).bold(),
332 Self::Highlight => s.fg_color(Some(MAGENTA)).bold(),
333 }
334 }
335}
336
337pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
339 let found = match sm.span_to_snippet(sp) {
340 Ok(snippet) => snippet,
341 Err(e) => {
342 warn!(error = ?e, "Invalid span {:?}", sp);
343 return true;
344 }
345 };
346 found != suggested
347}
348
349pub fn detect_confusion_type(sm: &SourceMap, suggested: &str, sp: Span) -> ConfusionType {
351 let found = match sm.span_to_snippet(sp) {
352 Ok(snippet) => snippet,
353 Err(e) => {
354 warn!(error = ?e, "Invalid span {:?}", sp);
355 return ConfusionType::None;
356 }
357 };
358
359 let mut has_case_confusion = false;
360 let mut has_digit_letter_confusion = false;
361
362 if found.len() == suggested.len() {
363 let mut has_case_diff = false;
364 let mut has_digit_letter_confusable = false;
365 let mut has_other_diff = false;
366
367 let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
368
369 let digit_letter_confusables = [('0', 'O'), ('1', 'l'), ('5', 'S'), ('8', 'B'), ('9', 'g')];
370
371 for (f, s) in iter::zip(found.chars(), suggested.chars()) {
372 if f != s {
373 if f.eq_ignore_ascii_case(&s) {
374 if ascii_confusables.contains(&f) || ascii_confusables.contains(&s) {
376 has_case_diff = true;
377 } else {
378 has_other_diff = true;
379 }
380 } else if digit_letter_confusables.contains(&(f, s))
381 || digit_letter_confusables.contains(&(s, f))
382 {
383 has_digit_letter_confusable = true;
385 } else {
386 has_other_diff = true;
387 }
388 }
389 }
390
391 if has_case_diff && !has_other_diff && found != suggested {
393 has_case_confusion = true;
394 }
395 if has_digit_letter_confusable && !has_other_diff && found != suggested {
396 has_digit_letter_confusion = true;
397 }
398 }
399
400 match (has_case_confusion, has_digit_letter_confusion) {
401 (true, true) => ConfusionType::Both,
402 (true, false) => ConfusionType::Case,
403 (false, true) => ConfusionType::DigitLetter,
404 (false, false) => ConfusionType::None,
405 }
406}
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq)]
410pub enum ConfusionType {
411 None,
413 Case,
415 DigitLetter,
417 Both,
419}
420
421impl ConfusionType {
422 pub fn label_text(&self) -> &'static str {
424 match self {
425 Self::None => "",
426 Self::Case => " (notice the capitalization)",
427 Self::DigitLetter => " (notice the digit/letter confusion)",
428 Self::Both => " (notice the capitalization and digit/letter confusion)",
429 }
430 }
431
432 pub fn combine(self, other: Self) -> Self {
436 match (self, other) {
437 (Self::None, other) => other,
438 (this, Self::None) => this,
439 (Self::Both, _) | (_, Self::Both) => Self::Both,
440 (Self::Case, Self::DigitLetter) | (Self::DigitLetter, Self::Case) => Self::Both,
441 (Self::Case, Self::Case) => Self::Case,
442 (Self::DigitLetter, Self::DigitLetter) => Self::DigitLetter,
443 }
444 }
445
446 pub fn has_confusion(&self) -> bool {
448 *self != Self::None
449 }
450}
451
452#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
458#[cfg_attr(feature = "json", serde(rename_all = "kebab-case"))]
459#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
460pub enum Applicability {
461 MachineApplicable,
468
469 MaybeIncorrect,
472
473 HasPlaceholders,
477
478 #[default]
480 Unspecified,
481}
482
483#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]
484pub enum SuggestionStyle {
485 HideCodeInline,
487 HideCodeAlways,
489 CompletelyHidden,
491 #[default]
495 ShowCode,
496 ShowAlways,
498}
499
500impl SuggestionStyle {
501 fn hide_inline(&self) -> bool {
502 !matches!(*self, Self::ShowCode)
503 }
504}
505
506#[derive(Clone, Debug, PartialEq, Hash)]
508pub enum Suggestions {
509 Enabled(Vec<CodeSuggestion>),
514 Sealed(Box<[CodeSuggestion]>),
518 Disabled,
522}
523
524impl Suggestions {
525 pub fn unwrap_tag(&self) -> &[CodeSuggestion] {
527 match self {
528 Self::Enabled(suggestions) => suggestions,
529 Self::Sealed(suggestions) => suggestions,
530 Self::Disabled => &[],
531 }
532 }
533}
534
535impl Default for Suggestions {
536 fn default() -> Self {
537 Self::Enabled(vec![])
538 }
539}
540
541impl Deref for Suggestions {
542 type Target = [CodeSuggestion];
543
544 fn deref(&self) -> &Self::Target {
545 self.unwrap_tag()
546 }
547}
548
549#[derive(Clone, Debug, PartialEq, Eq, Hash)]
552pub struct CodeSuggestion {
553 pub substitutions: Vec<Substitution>,
575 pub msg: DiagMsg,
576 pub style: SuggestionStyle,
578 pub applicability: Applicability,
584}
585
586#[derive(Clone, Debug, PartialEq, Eq, Hash)]
588pub struct SubstitutionPart {
589 pub span: Span,
590 pub snippet: DiagMsg,
591}
592
593impl SubstitutionPart {
594 pub fn is_addition(&self) -> bool {
595 self.span.lo() == self.span.hi() && !self.snippet.is_empty()
596 }
597
598 pub fn is_deletion(&self) -> bool {
599 self.span.lo() != self.span.hi() && self.snippet.is_empty()
600 }
601
602 pub fn is_replacement(&self) -> bool {
603 self.span.lo() != self.span.hi() && !self.snippet.is_empty()
604 }
605}
606
607#[derive(Clone, Debug, PartialEq, Eq, Hash)]
609pub struct Substitution {
610 pub parts: Vec<SubstitutionPart>,
611}
612
613#[derive(Clone, Debug, PartialEq, Hash)]
616pub struct SubDiagnostic {
617 pub level: Level,
618 pub messages: Vec<(DiagMsg, Style)>,
619 pub span: MultiSpan,
620}
621
622impl SubDiagnostic {
623 pub fn label(&self) -> Cow<'_, str> {
625 self.label_with_style(false)
626 }
627
628 pub fn label_with_style(&self, supports_color: bool) -> Cow<'_, str> {
630 flatten_messages(&self.messages, supports_color, self.level)
631 }
632}
633
634#[must_use]
636#[derive(Clone, Debug)]
637pub struct Diag {
638 pub(crate) level: Level,
639
640 pub messages: Vec<(DiagMsg, Style)>,
641 pub span: MultiSpan,
642 pub children: Vec<SubDiagnostic>,
643 pub code: Option<DiagId>,
644 pub suggestions: Suggestions,
645
646 pub created_at: &'static Location<'static>,
647}
648
649impl PartialEq for Diag {
650 fn eq(&self, other: &Self) -> bool {
651 self.keys() == other.keys()
652 }
653}
654
655impl Hash for Diag {
656 fn hash<H: Hasher>(&self, state: &mut H) {
657 self.keys().hash(state);
658 }
659}
660
661impl Diag {
662 #[track_caller]
664 pub fn new<M: Into<DiagMsg>>(level: Level, msg: M) -> Self {
665 Self::new_with_messages(level, vec![(msg.into(), Style::NoStyle)])
666 }
667
668 #[track_caller]
670 pub fn new_with_messages(level: Level, messages: Vec<(DiagMsg, Style)>) -> Self {
671 Self {
672 level,
673 messages,
674 code: None,
675 span: MultiSpan::new(),
676 children: vec![],
677 suggestions: Suggestions::default(),
678 created_at: Location::caller(),
682 }
683 }
684
685 #[inline]
687 pub fn is_error(&self) -> bool {
688 self.level.is_error()
689 }
690
691 #[inline]
693 pub fn is_note(&self) -> bool {
694 self.level.is_note()
695 }
696
697 pub fn label(&self) -> Cow<'_, str> {
699 flatten_messages(&self.messages, false, self.level)
700 }
701
702 pub fn label_with_style(&self, supports_color: bool) -> Cow<'_, str> {
704 flatten_messages(&self.messages, supports_color, self.level)
705 }
706
707 pub fn messages(&self) -> &[(DiagMsg, Style)] {
709 &self.messages
710 }
711
712 pub fn level(&self) -> Level {
714 self.level
715 }
716
717 pub fn id(&self) -> Option<&str> {
719 self.code.as_ref().map(|code| code.as_str())
720 }
721
722 fn keys(&self) -> impl PartialEq + std::hash::Hash {
724 (
725 &self.level,
726 &self.messages,
727 &self.code,
728 &self.span,
729 &self.children,
730 &self.suggestions,
731 )
736 }
737}
738
739impl Diag {
741 pub fn span(&mut self, span: impl Into<MultiSpan>) -> &mut Self {
743 self.span = span.into();
744 self
745 }
746
747 pub fn code(&mut self, code: impl Into<DiagId>) -> &mut Self {
749 self.code = Some(code.into());
750 self
751 }
752
753 pub fn span_label(&mut self, span: Span, label: impl Into<DiagMsg>) -> &mut Self {
762 self.span.push_span_label(span, label);
763 self
764 }
765
766 pub fn span_labels(
769 &mut self,
770 spans: impl IntoIterator<Item = Span>,
771 label: impl Into<DiagMsg>,
772 ) -> &mut Self {
773 let label = label.into();
774 for span in spans {
775 self.span_label(span, label.clone());
776 }
777 self
778 }
779
780 pub(crate) fn locations_note(&mut self, emitted_at: &Location<'_>) -> &mut Self {
782 let msg = format!(
783 "created at {},\n\
784 emitted at {}",
785 self.created_at, emitted_at
786 );
787 self.note(msg)
788 }
789}
790
791impl Diag {
793 pub fn warn(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
795 self.sub(Level::Warning, msg, MultiSpan::new())
796 }
797
798 pub fn span_warn(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
801 self.sub(Level::Warning, msg, span)
802 }
803
804 pub fn note(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
806 self.sub(Level::Note, msg, MultiSpan::new())
807 }
808
809 pub fn span_note(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
812 self.sub(Level::Note, msg, span)
813 }
814
815 pub fn highlighted_note(&mut self, messages: Vec<(impl Into<DiagMsg>, Style)>) -> &mut Self {
816 self.sub_with_highlights(Level::Note, messages, MultiSpan::new())
817 }
818
819 pub fn note_once(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
822 self.sub(Level::OnceNote, msg, MultiSpan::new())
823 }
824
825 pub fn span_note_once(
828 &mut self,
829 span: impl Into<MultiSpan>,
830 msg: impl Into<DiagMsg>,
831 ) -> &mut Self {
832 self.sub(Level::OnceNote, msg, span)
833 }
834
835 pub fn help(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
837 self.sub(Level::Help, msg, MultiSpan::new())
838 }
839
840 pub fn help_once(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
843 self.sub(Level::OnceHelp, msg, MultiSpan::new())
844 }
845
846 pub fn highlighted_help(&mut self, msgs: Vec<(impl Into<DiagMsg>, Style)>) -> &mut Self {
848 self.sub_with_highlights(Level::Help, msgs, MultiSpan::new())
849 }
850
851 pub fn span_help(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
854 self.sub(Level::Help, msg, span)
855 }
856
857 fn sub(
858 &mut self,
859 level: Level,
860 msg: impl Into<DiagMsg>,
861 span: impl Into<MultiSpan>,
862 ) -> &mut Self {
863 self.children.push(SubDiagnostic {
864 level,
865 messages: vec![(msg.into(), Style::NoStyle)],
866 span: span.into(),
867 });
868 self
869 }
870
871 fn sub_with_highlights(
872 &mut self,
873 level: Level,
874 messages: Vec<(impl Into<DiagMsg>, Style)>,
875 span: MultiSpan,
876 ) -> &mut Self {
877 let messages = messages.into_iter().map(|(m, s)| (m.into(), s)).collect();
878 self.children.push(SubDiagnostic { level, messages, span });
879 self
880 }
881}
882
883impl Diag {
885 pub fn disable_suggestions(&mut self) -> &mut Self {
889 self.suggestions = Suggestions::Disabled;
890 self
891 }
892
893 pub fn seal_suggestions(&mut self) -> &mut Self {
898 if let Suggestions::Enabled(suggestions) = &mut self.suggestions {
899 let suggestions_slice = std::mem::take(suggestions).into_boxed_slice();
900 self.suggestions = Suggestions::Sealed(suggestions_slice);
901 }
902 self
903 }
904
905 fn push_suggestion(&mut self, suggestion: CodeSuggestion) {
910 if let Suggestions::Enabled(suggestions) = &mut self.suggestions {
911 suggestions.push(suggestion);
912 }
913 }
914
915 pub fn span_suggestion(
933 &mut self,
934 span: Span,
935 msg: impl Into<DiagMsg>,
936 suggestion: impl Into<DiagMsg>,
937 applicability: Applicability,
938 ) -> &mut Self {
939 self.span_suggestion_with_style(
940 span,
941 msg,
942 suggestion,
943 applicability,
944 SuggestionStyle::ShowCode,
945 );
946 self
947 }
948
949 pub fn span_suggestion_with_style(
951 &mut self,
952 span: Span,
953 msg: impl Into<DiagMsg>,
954 suggestion: impl Into<DiagMsg>,
955 applicability: Applicability,
956 style: SuggestionStyle,
957 ) -> &mut Self {
958 self.push_suggestion(CodeSuggestion {
959 substitutions: vec![Substitution {
960 parts: vec![SubstitutionPart { snippet: suggestion.into(), span }],
961 }],
962 msg: msg.into(),
963 style,
964 applicability,
965 });
966 self
967 }
968
969 pub fn multipart_suggestion(
972 &mut self,
973 msg: impl Into<DiagMsg>,
974 substitutions: Vec<(Span, DiagMsg)>,
975 applicability: Applicability,
976 ) -> &mut Self {
977 self.multipart_suggestion_with_style(
978 msg,
979 substitutions,
980 applicability,
981 SuggestionStyle::ShowCode,
982 );
983 self
984 }
985
986 pub fn multipart_suggestion_with_style(
988 &mut self,
989 msg: impl Into<DiagMsg>,
990 substitutions: Vec<(Span, DiagMsg)>,
991 applicability: Applicability,
992 style: SuggestionStyle,
993 ) -> &mut Self {
994 self.push_suggestion(CodeSuggestion {
995 substitutions: vec![Substitution {
996 parts: substitutions
997 .into_iter()
998 .map(|(span, snippet)| SubstitutionPart { span, snippet })
999 .collect(),
1000 }],
1001 msg: msg.into(),
1002 style,
1003 applicability,
1004 });
1005 self
1006 }
1007}
1008
1009fn flatten_messages(messages: &[(DiagMsg, Style)], with_style: bool, level: Level) -> Cow<'_, str> {
1011 if with_style {
1012 match messages {
1013 [] => Cow::Borrowed(""),
1014 [(msg, Style::NoStyle)] => Cow::Borrowed(msg.as_str()),
1015 [(msg, style)] => {
1016 let mut res = String::new();
1017 write_fmt(&mut res, msg, style, level);
1018 Cow::Owned(res)
1019 }
1020 messages => {
1021 let mut res = String::new();
1022 for (msg, style) in messages {
1023 match style {
1024 Style::NoStyle => res.push_str(msg.as_str()),
1025 _ => write_fmt(&mut res, msg, style, level),
1026 }
1027 }
1028 Cow::Owned(res)
1029 }
1030 }
1031 } else {
1032 match messages {
1033 [] => Cow::Borrowed(""),
1034 [(message, _)] => Cow::Borrowed(message.as_str()),
1035 messages => messages.iter().map(|(msg, _)| msg.as_str()).collect(),
1036 }
1037 }
1038}
1039
1040fn write_fmt(output: &mut String, msg: &DiagMsg, style: &Style, level: Level) {
1041 let style = style.to_color_spec(level);
1042 let _ = write!(output, "{style}{}{style:#}", msg.as_str());
1043}
1044
1045#[cfg(test)]
1046mod tests {
1047 use super::*;
1048 use crate::{BytePos, ColorChoice, Span, source_map};
1049 #[cfg(feature = "json")]
1050 use snapbox::IntoData as _;
1051 use snapbox::{assert_data_eq, str};
1052
1053 #[test]
1054 fn test_styled_messages() {
1055 let mut diag = Diag::new(Level::Note, "test");
1056
1057 diag.highlighted_note(vec![
1058 ("plain text ", Style::NoStyle),
1059 ("removed", Style::Removal),
1060 (" middle ", Style::NoStyle),
1061 ("added", Style::Addition),
1062 ]);
1063
1064 let sub = &diag.children[0];
1065
1066 assert_data_eq!(&*sub.label(), str!["plain text removed middle added"]);
1067
1068 assert_data_eq!(
1069 &*sub.label_with_style(true),
1070 str!["plain text [91mremoved[0m middle [92madded[0m"]
1071 );
1072 }
1073
1074 #[test]
1075 fn test_inline_suggestion() {
1076 let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
1077 let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
1078 diag.span(var_span).span_suggestion(
1079 var_span,
1080 "mutable variables should use mixedCase",
1081 var_sugg,
1082 Applicability::MachineApplicable,
1083 );
1084
1085 assert_eq!(diag.suggestions.len(), 1);
1086 assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
1087 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1088
1089 assert_data_eq!(
1090 emit_human_diagnostics(diag),
1091 str![[r#"
1092note: mutable variables should use mixedCase
1093 ╭▸ <test.sol>:4:17
1094 │
10954 │ uint256 my_var = 0;
1096 ╰╴ ━━━━━━ help: mutable variables should use mixedCase: `myVar`
1097
1098
1099"#]]
1100 );
1101 }
1102
1103 #[test]
1104 fn test_emit_diagnostic_ref_preserves_inline_suggestion() {
1105 let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
1106 let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
1107 diag.span(var_span).span_suggestion(
1108 var_span,
1109 "mutable variables should use mixedCase",
1110 var_sugg,
1111 Applicability::MachineApplicable,
1112 );
1113
1114 let sm = std::sync::Arc::new(source_map::SourceMap::empty());
1115 sm.new_source_file(source_map::FileName::custom("test.sol"), CONTRACT.to_string()).unwrap();
1116 let mut emitter = HumanBufferEmitter::new(ColorChoice::Never).source_map(Some(sm));
1117 emitter.emit_diagnostic_ref(&diag);
1118
1119 assert_eq!(diag.suggestions.len(), 1);
1120 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1121 }
1122
1123 #[test]
1124 fn test_allowed_diagnostic_code_suppresses_warning() {
1125 let dcx = DiagCtxt::with_buffer_emitter(None, ColorChoice::Never)
1126 .with_allowed_diagnostic_codes(["1234".to_string()]);
1127
1128 dcx.warn("suppressed warning").code(crate::error_code!(1234)).emit();
1129 dcx.warn("emitted warning").code(crate::error_code!(5678)).emit();
1130
1131 let diagnostics = dcx.emitted_diagnostics().unwrap().to_string();
1132 assert_eq!(dcx.warn_count(), 1);
1133 assert!(diagnostics.contains("emitted warning"));
1134 assert!(!diagnostics.contains("suppressed warning"));
1135 }
1136
1137 #[test]
1138 fn test_allowed_diagnostic_code_does_not_suppress_error() {
1139 let dcx = DiagCtxt::with_buffer_emitter(None, ColorChoice::Never)
1140 .with_allowed_diagnostic_codes(["1234".to_string()]);
1141
1142 let _ = dcx.err("emitted error").code(crate::error_code!(1234)).emit();
1143
1144 assert!(dcx.has_errors().is_err());
1145 assert!(dcx.emitted_diagnostics().unwrap().to_string().contains("emitted error"));
1146 }
1147
1148 #[test]
1149 fn test_suggestion() {
1150 let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
1151 let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
1152 diag.span(var_span).span_suggestion_with_style(
1153 var_span,
1154 "mutable variables should use mixedCase",
1155 var_sugg,
1156 Applicability::MachineApplicable,
1157 SuggestionStyle::ShowAlways,
1158 );
1159
1160 assert_eq!(diag.suggestions.len(), 1);
1161 assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
1162 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowAlways);
1163
1164 assert_data_eq!(
1165 emit_human_diagnostics(diag),
1166 str![[r#"
1167note: mutable variables should use mixedCase
1168 ╭▸ <test.sol>:4:17
1169 │
11704 │ uint256 my_var = 0;
1171 │ ━━━━━━
1172 ╰╴
1173help: mutable variables should use mixedCase
1174 ╭╴
11754 - uint256 my_var = 0;
11764 + uint256 myVar = 0;
1177 ╰╴
1178
1179
1180"#]]
1181 );
1182 }
1183
1184 #[test]
1185 fn test_suggestion_with_footer() {
1186 let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
1187 let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
1188 diag.span(var_span)
1189 .span_suggestion_with_style(
1190 var_span,
1191 "mutable variables should use mixedCase",
1192 var_sugg,
1193 Applicability::MachineApplicable,
1194 SuggestionStyle::ShowAlways,
1195 )
1196 .help("some footer help msg that should be displayed at the very bottom");
1197
1198 assert_eq!(diag.suggestions.len(), 1);
1199 assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
1200 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowAlways);
1201
1202 assert_data_eq!(
1203 emit_human_diagnostics(diag),
1204 str![[r#"
1205note: mutable variables should use mixedCase
1206 ╭▸ <test.sol>:4:17
1207 │
12084 │ uint256 my_var = 0;
1209 │ ━━━━━━
1210 │
1211 ╰ help: some footer help msg that should be displayed at the very bottom
1212help: mutable variables should use mixedCase
1213 ╭╴
12144 - uint256 my_var = 0;
12154 + uint256 myVar = 0;
1216 ╰╴
1217
1218
1219"#]]
1220 );
1221 }
1222
1223 #[test]
1224 fn test_multispan_suggestion() {
1225 let (pub_span, pub_sugg) = (Span::new(BytePos(36), BytePos(42)), "external".into());
1226 let (view_span, view_sugg) = (Span::new(BytePos(43), BytePos(47)), "pure".into());
1227 let mut diag = Diag::new(Level::Warning, "inefficient visibility and mutability");
1228 diag.span(vec![pub_span, view_span]).multipart_suggestion(
1229 "consider changing visibility and mutability",
1230 vec![(pub_span, pub_sugg), (view_span, view_sugg)],
1231 Applicability::MaybeIncorrect,
1232 );
1233
1234 assert_eq!(diag.suggestions[0].substitutions.len(), 1);
1235 assert_eq!(diag.suggestions[0].substitutions[0].parts.len(), 2);
1236 assert_eq!(diag.suggestions[0].applicability, Applicability::MaybeIncorrect);
1237 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1238
1239 assert_data_eq!(
1240 emit_human_diagnostics(diag),
1241 str![[r#"
1242warning: inefficient visibility and mutability
1243 ╭▸ <test.sol>:3:20
1244 │
12453 │ function foo() public view {
1246 │ ━━━━━━ ━━━━
1247 ╰╴
1248help: consider changing visibility and mutability
1249 ╭╴
12503 - function foo() public view {
12513 + function foo() external pure {
1252 ╰╴
1253
1254
1255"#]]
1256 );
1257 }
1258
1259 #[test]
1260 #[cfg(feature = "json")]
1261 fn test_json_suggestion() {
1262 let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
1263 let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
1264 diag.span(var_span).span_suggestion(
1265 var_span,
1266 "mutable variables should use mixedCase",
1267 var_sugg,
1268 Applicability::MachineApplicable,
1269 );
1270
1271 assert_eq!(diag.suggestions.len(), 1);
1272 assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
1273 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1274
1275 assert_data_eq!(
1276 emit_json_diagnostics(diag),
1277 str![[r#"
1278{
1279 "$message_type": "diagnostic",
1280 "children": [
1281 {
1282 "children": [],
1283 "code": null,
1284 "level": "help",
1285 "message": "mutable variables should use mixedCase",
1286 "rendered": null,
1287 "spans": [
1288 {
1289 "byte_end": 72,
1290 "byte_start": 66,
1291 "column_end": 23,
1292 "column_start": 17,
1293 "file_name": "<test.sol>",
1294 "is_primary": true,
1295 "label": null,
1296 "line_end": 4,
1297 "line_start": 4,
1298 "suggested_replacement": "myVar",
1299 "text": [
1300 {
1301 "highlight_end": 23,
1302 "highlight_start": 17,
1303 "text": " uint256 my_var = 0;"
1304 }
1305 ]
1306 }
1307 ]
1308 }
1309 ],
1310 "code": null,
1311 "level": "note",
1312 "message": "mutable variables should use mixedCase",
1313 "rendered": "note: mutable variables should use mixedCase\n ╭▸ <test.sol>:4:17\n │\n4 │ uint256 my_var = 0;\n ╰╴ ━━━━━━ help: mutable variables should use mixedCase: `myVar`\n\n",
1314 "spans": [
1315 {
1316 "byte_end": 72,
1317 "byte_start": 66,
1318 "column_end": 23,
1319 "column_start": 17,
1320 "file_name": "<test.sol>",
1321 "is_primary": true,
1322 "label": null,
1323 "line_end": 4,
1324 "line_start": 4,
1325 "suggested_replacement": null,
1326 "text": [
1327 {
1328 "highlight_end": 23,
1329 "highlight_start": 17,
1330 "text": " uint256 my_var = 0;"
1331 }
1332 ]
1333 }
1334 ]
1335}
1336"#]].is_json()
1337 );
1338 }
1339
1340 #[test]
1341 #[cfg(feature = "json")]
1342 fn test_multispan_json_suggestion() {
1343 let (pub_span, pub_sugg) = (Span::new(BytePos(36), BytePos(42)), "external".into());
1344 let (view_span, view_sugg) = (Span::new(BytePos(43), BytePos(47)), "pure".into());
1345 let mut diag = Diag::new(Level::Warning, "inefficient visibility and mutability");
1346 diag.span(vec![pub_span, view_span]).multipart_suggestion(
1347 "consider changing visibility and mutability",
1348 vec![(pub_span, pub_sugg), (view_span, view_sugg)],
1349 Applicability::MaybeIncorrect,
1350 );
1351
1352 assert_eq!(diag.suggestions[0].substitutions.len(), 1);
1353 assert_eq!(diag.suggestions[0].substitutions[0].parts.len(), 2);
1354 assert_eq!(diag.suggestions[0].applicability, Applicability::MaybeIncorrect);
1355 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1356
1357 assert_data_eq!(
1358 emit_json_diagnostics(diag),
1359 str![[r#"
1360{
1361 "$message_type": "diagnostic",
1362 "children": [
1363 {
1364 "children": [],
1365 "code": null,
1366 "level": "help",
1367 "message": "consider changing visibility and mutability",
1368 "rendered": null,
1369 "spans": [
1370 {
1371 "byte_end": 42,
1372 "byte_start": 36,
1373 "column_end": 26,
1374 "column_start": 20,
1375 "file_name": "<test.sol>",
1376 "is_primary": true,
1377 "label": null,
1378 "line_end": 3,
1379 "line_start": 3,
1380 "suggested_replacement": "external",
1381 "text": [
1382 {
1383 "highlight_end": 26,
1384 "highlight_start": 20,
1385 "text": " function foo() public view {"
1386 }
1387 ]
1388 },
1389 {
1390 "byte_end": 47,
1391 "byte_start": 43,
1392 "column_end": 31,
1393 "column_start": 27,
1394 "file_name": "<test.sol>",
1395 "is_primary": true,
1396 "label": null,
1397 "line_end": 3,
1398 "line_start": 3,
1399 "suggested_replacement": "pure",
1400 "text": [
1401 {
1402 "highlight_end": 31,
1403 "highlight_start": 27,
1404 "text": " function foo() public view {"
1405 }
1406 ]
1407 }
1408 ]
1409 }
1410 ],
1411 "code": null,
1412 "level": "warning",
1413 "message": "inefficient visibility and mutability",
1414 "rendered": "warning: inefficient visibility and mutability\n ╭▸ <test.sol>:3:20\n │\n3 │ function foo() public view {\n │ ━━━━━━ ━━━━\n ╰╴\nhelp: consider changing visibility and mutability\n ╭╴\n3 - function foo() public view {\n3 + function foo() external pure {\n ╰╴\n\n",
1415 "spans": [
1416 {
1417 "byte_end": 42,
1418 "byte_start": 36,
1419 "column_end": 26,
1420 "column_start": 20,
1421 "file_name": "<test.sol>",
1422 "is_primary": true,
1423 "label": null,
1424 "line_end": 3,
1425 "line_start": 3,
1426 "suggested_replacement": null,
1427 "text": [
1428 {
1429 "highlight_end": 26,
1430 "highlight_start": 20,
1431 "text": " function foo() public view {"
1432 }
1433 ]
1434 },
1435 {
1436 "byte_end": 47,
1437 "byte_start": 43,
1438 "column_end": 31,
1439 "column_start": 27,
1440 "file_name": "<test.sol>",
1441 "is_primary": true,
1442 "label": null,
1443 "line_end": 3,
1444 "line_start": 3,
1445 "suggested_replacement": null,
1446 "text": [
1447 {
1448 "highlight_end": 31,
1449 "highlight_start": 27,
1450 "text": " function foo() public view {"
1451 }
1452 ]
1453 }
1454 ]
1455}
1456"#]].is_json()
1457 );
1458 }
1459
1460 const CONTRACT: &str = r#"
1463contract Test {
1464 function foo() public view {
1465 uint256 my_var = 0;
1466 }
1467}"#;
1468
1469 fn emit_human_diagnostics(diag: Diag) -> String {
1471 let sm = source_map::SourceMap::empty();
1472 sm.new_source_file(source_map::FileName::custom("test.sol"), CONTRACT.to_string()).unwrap();
1473
1474 let dcx = DiagCtxt::with_buffer_emitter(Some(std::sync::Arc::new(sm)), ColorChoice::Never);
1475 let _ = dcx.emit_diagnostic(diag);
1476
1477 dcx.emitted_diagnostics().unwrap().0
1478 }
1479
1480 #[cfg(feature = "json")]
1481 use std::sync::{Arc, Mutex};
1482
1483 #[cfg(feature = "json")]
1484 #[derive(Clone)]
1485 struct SharedWriter(Arc<Mutex<Vec<u8>>>);
1486
1487 #[cfg(feature = "json")]
1488 impl std::io::Write for SharedWriter {
1489 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1490 self.0.lock().unwrap().write(buf)
1491 }
1492 fn flush(&mut self) -> std::io::Result<()> {
1493 self.0.lock().unwrap().flush()
1494 }
1495 }
1496
1497 #[cfg(feature = "json")]
1498 fn emit_json_diagnostics(diag: Diag) -> String {
1499 let sm = Arc::new(source_map::SourceMap::empty());
1500 sm.new_source_file(source_map::FileName::custom("test.sol"), CONTRACT.to_string()).unwrap();
1501
1502 let writer = Arc::new(Mutex::new(Vec::new()));
1503 let emitter = JsonEmitter::new(Box::new(SharedWriter(writer.clone())), Arc::clone(&sm))
1504 .rustc_like(true);
1505 let dcx = DiagCtxt::new(Box::new(emitter));
1506 let _ = dcx.emit_diagnostic(diag);
1507
1508 let buffer = writer.lock().unwrap();
1509 String::from_utf8(buffer.clone()).expect("JSON output was not valid UTF-8")
1510 }
1511}