1use crate::Span;
6use anstyle::{AnsiColor, Color};
7use std::{
8 borrow::Cow,
9 fmt::{self, Write},
10 hash::{Hash, Hasher},
11 ops::Deref,
12 panic::Location,
13};
14
15mod builder;
16pub use builder::{DiagBuilder, EmissionGuarantee};
17
18mod context;
19pub use context::{DiagCtxt, DiagCtxtFlags};
20
21mod emitter;
22#[cfg(feature = "json")]
23pub use emitter::JsonEmitter;
24pub use emitter::{
25 DynEmitter, Emitter, HumanBufferEmitter, HumanEmitter, InMemoryEmitter, LocalEmitter,
26 SilentEmitter,
27};
28
29mod message;
30pub use message::{DiagMsg, MultiSpan, SpanLabel};
31
32pub struct EmittedDiagnostics(pub(crate) String);
36
37impl fmt::Debug for EmittedDiagnostics {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.write_str(&self.0)
40 }
41}
42
43impl fmt::Display for EmittedDiagnostics {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 f.write_str(&self.0)
46 }
47}
48
49impl std::error::Error for EmittedDiagnostics {}
50
51impl EmittedDiagnostics {
52 pub fn is_empty(&self) -> bool {
54 self.0.is_empty()
55 }
56}
57
58#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub struct ErrorGuaranteed(());
62
63impl fmt::Debug for ErrorGuaranteed {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.write_str("ErrorGuaranteed")
66 }
67}
68
69impl ErrorGuaranteed {
70 #[inline]
74 pub const fn new_unchecked() -> Self {
75 Self(())
76 }
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
82pub struct BugAbort;
83
84pub struct ExplicitBug;
87
88pub struct FatalAbort;
90
91#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
102pub struct DiagId {
103 s: Cow<'static, str>,
104}
105
106impl DiagId {
107 pub fn new_str(s: impl Into<Cow<'static, str>>) -> Self {
112 Self { s: s.into() }
113 }
114
115 #[doc(hidden)]
119 #[cfg_attr(debug_assertions, track_caller)]
120 pub fn new_from_macro(id: u32) -> Self {
121 debug_assert!((1..=9999).contains(&id), "error code must be in range 0001-9999");
122 Self { s: Cow::Owned(format!("{id:04}")) }
123 }
124
125 pub fn as_string(&self) -> String {
127 self.s.to_string()
128 }
129}
130
131#[macro_export]
140macro_rules! error_code {
141 ($id:literal) => {
142 $crate::diagnostics::DiagId::new_from_macro($id)
143 };
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
148pub enum Level {
149 Bug,
153
154 Fatal,
159
160 Error,
165
166 Warning,
170
171 Note,
176
177 OnceNote,
181
182 Help,
187
188 OnceHelp,
192
193 FailureNote,
197
198 Allow,
202}
203
204impl Level {
205 pub fn to_str(self) -> &'static str {
207 match self {
208 Self::Bug => "error: internal compiler error",
209 Self::Fatal | Self::Error => "error",
210 Self::Warning => "warning",
211 Self::Note | Self::OnceNote => "note",
212 Self::Help | Self::OnceHelp => "help",
213 Self::FailureNote => "failure-note",
214 Self::Allow
215 => unreachable!(),
217 }
218 }
219
220 #[inline]
222 pub fn is_error(self) -> bool {
223 match self {
224 Self::Bug | Self::Fatal | Self::Error | Self::FailureNote => true,
225
226 Self::Warning
227 | Self::Note
228 | Self::OnceNote
229 | Self::Help
230 | Self::OnceHelp
231 | Self::Allow => false,
232 }
233 }
234
235 #[inline]
237 pub fn is_note(self) -> bool {
238 match self {
239 Self::Note | Self::OnceNote => true,
240
241 Self::Bug
242 | Self::Fatal
243 | Self::Error
244 | Self::FailureNote
245 | Self::Warning
246 | Self::Help
247 | Self::OnceHelp
248 | Self::Allow => false,
249 }
250 }
251
252 #[inline]
254 pub const fn style(self) -> anstyle::Style {
255 anstyle::Style::new().fg_color(self.color()).bold()
256 }
257
258 #[inline]
260 pub const fn color(self) -> Option<Color> {
261 match self.ansi_color() {
262 Some(c) => Some(Color::Ansi(c)),
263 None => None,
264 }
265 }
266
267 #[inline]
269 pub const fn ansi_color(self) -> Option<AnsiColor> {
270 match self {
272 Self::Bug | Self::Fatal | Self::Error => Some(AnsiColor::BrightRed),
273 Self::Warning => {
274 Some(if cfg!(windows) { AnsiColor::BrightYellow } else { AnsiColor::Yellow })
275 }
276 Self::Note | Self::OnceNote => Some(AnsiColor::BrightGreen),
277 Self::Help | Self::OnceHelp => Some(AnsiColor::BrightCyan),
278 Self::FailureNote | Self::Allow => None,
279 }
280 }
281}
282
283#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
284pub enum Style {
285 MainHeaderMsg,
286 HeaderMsg,
287 LineAndColumn,
288 LineNumber,
289 Quotation,
290 UnderlinePrimary,
291 UnderlineSecondary,
292 LabelPrimary,
293 LabelSecondary,
294 NoStyle,
295 Level(Level),
296 Highlight,
297 Addition,
298 Removal,
299}
300
301impl Style {
302 pub const fn to_color_spec(self, level: Level) -> anstyle::Style {
304 use AnsiColor::*;
305
306 const BRIGHT_BLUE: Color = Color::Ansi(if cfg!(windows) { BrightCyan } else { BrightBlue });
310 const GREEN: Color = Color::Ansi(BrightGreen);
311 const MAGENTA: Color = Color::Ansi(BrightMagenta);
312 const RED: Color = Color::Ansi(BrightRed);
313 const WHITE: Color = Color::Ansi(BrightWhite);
314
315 let s = anstyle::Style::new();
316 match self {
317 Self::Addition => s.fg_color(Some(GREEN)),
318 Self::Removal => s.fg_color(Some(RED)),
319 Self::LineAndColumn => s,
320 Self::LineNumber => s.fg_color(Some(BRIGHT_BLUE)).bold(),
321 Self::Quotation => s,
322 Self::MainHeaderMsg => if cfg!(windows) { s.fg_color(Some(WHITE)) } else { s }.bold(),
323 Self::UnderlinePrimary | Self::LabelPrimary => s.fg_color(level.color()).bold(),
324 Self::UnderlineSecondary | Self::LabelSecondary => s.fg_color(Some(BRIGHT_BLUE)).bold(),
325 Self::HeaderMsg | Self::NoStyle => s,
326 Self::Level(level2) => s.fg_color(level2.color()).bold(),
327 Self::Highlight => s.fg_color(Some(MAGENTA)).bold(),
328 }
329 }
330}
331
332#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
338#[cfg_attr(feature = "json", serde(rename_all = "kebab-case"))]
339#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
340pub enum Applicability {
341 MachineApplicable,
348
349 MaybeIncorrect,
352
353 HasPlaceholders,
357
358 #[default]
360 Unspecified,
361}
362
363#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]
364pub enum SuggestionStyle {
365 HideCodeInline,
367 HideCodeAlways,
369 CompletelyHidden,
371 #[default]
375 ShowCode,
376 ShowAlways,
378}
379
380impl SuggestionStyle {
381 fn hide_inline(&self) -> bool {
382 !matches!(*self, Self::ShowCode)
383 }
384}
385
386#[derive(Clone, Debug, PartialEq, Hash)]
388pub enum Suggestions {
389 Enabled(Vec<CodeSuggestion>),
394 Sealed(Box<[CodeSuggestion]>),
398 Disabled,
402}
403
404impl Suggestions {
405 pub fn unwrap_tag(&self) -> &[CodeSuggestion] {
407 match self {
408 Self::Enabled(suggestions) => suggestions,
409 Self::Sealed(suggestions) => suggestions,
410 Self::Disabled => &[],
411 }
412 }
413}
414
415impl Default for Suggestions {
416 fn default() -> Self {
417 Self::Enabled(vec![])
418 }
419}
420
421impl Deref for Suggestions {
422 type Target = [CodeSuggestion];
423
424 fn deref(&self) -> &Self::Target {
425 self.unwrap_tag()
426 }
427}
428
429#[derive(Clone, Debug, PartialEq, Eq, Hash)]
432pub struct CodeSuggestion {
433 pub substitutions: Vec<Substitution>,
455 pub msg: DiagMsg,
456 pub style: SuggestionStyle,
458 pub applicability: Applicability,
464}
465
466#[derive(Clone, Debug, PartialEq, Eq, Hash)]
468pub struct SubstitutionPart {
469 pub span: Span,
470 pub snippet: DiagMsg,
471}
472
473impl SubstitutionPart {
474 pub fn is_addition(&self) -> bool {
475 self.span.lo() == self.span.hi() && !self.snippet.is_empty()
476 }
477
478 pub fn is_deletion(&self) -> bool {
479 self.span.lo() != self.span.hi() && self.snippet.is_empty()
480 }
481
482 pub fn is_replacement(&self) -> bool {
483 self.span.lo() != self.span.hi() && !self.snippet.is_empty()
484 }
485}
486
487#[derive(Clone, Debug, PartialEq, Eq, Hash)]
489pub struct Substitution {
490 pub parts: Vec<SubstitutionPart>,
491}
492
493#[derive(Clone, Debug, PartialEq, Hash)]
496pub struct SubDiagnostic {
497 pub level: Level,
498 pub messages: Vec<(DiagMsg, Style)>,
499 pub span: MultiSpan,
500}
501
502impl SubDiagnostic {
503 pub fn label(&self) -> Cow<'_, str> {
505 self.label_with_style(false)
506 }
507
508 pub fn label_with_style(&self, supports_color: bool) -> Cow<'_, str> {
510 flatten_messages(&self.messages, supports_color, self.level)
511 }
512}
513
514#[must_use]
516#[derive(Clone, Debug)]
517pub struct Diag {
518 pub(crate) level: Level,
519
520 pub messages: Vec<(DiagMsg, Style)>,
521 pub span: MultiSpan,
522 pub children: Vec<SubDiagnostic>,
523 pub code: Option<DiagId>,
524 pub suggestions: Suggestions,
525
526 pub created_at: &'static Location<'static>,
527}
528
529impl PartialEq for Diag {
530 fn eq(&self, other: &Self) -> bool {
531 self.keys() == other.keys()
532 }
533}
534
535impl Hash for Diag {
536 fn hash<H: Hasher>(&self, state: &mut H) {
537 self.keys().hash(state);
538 }
539}
540
541impl Diag {
542 #[track_caller]
544 pub fn new<M: Into<DiagMsg>>(level: Level, msg: M) -> Self {
545 Self::new_with_messages(level, vec![(msg.into(), Style::NoStyle)])
546 }
547
548 #[track_caller]
550 pub fn new_with_messages(level: Level, messages: Vec<(DiagMsg, Style)>) -> Self {
551 Self {
552 level,
553 messages,
554 code: None,
555 span: MultiSpan::new(),
556 children: vec![],
557 suggestions: Suggestions::default(),
558 created_at: Location::caller(),
562 }
563 }
564
565 #[inline]
567 pub fn is_error(&self) -> bool {
568 self.level.is_error()
569 }
570
571 #[inline]
573 pub fn is_note(&self) -> bool {
574 self.level.is_note()
575 }
576
577 pub fn label(&self) -> Cow<'_, str> {
579 flatten_messages(&self.messages, false, self.level)
580 }
581
582 pub fn label_with_style(&self, supports_color: bool) -> Cow<'_, str> {
584 flatten_messages(&self.messages, supports_color, self.level)
585 }
586
587 pub fn messages(&self) -> &[(DiagMsg, Style)] {
589 &self.messages
590 }
591
592 pub fn level(&self) -> Level {
594 self.level
595 }
596
597 pub fn id(&self) -> Option<String> {
599 self.code.as_ref().map(|code| code.as_string())
600 }
601
602 fn keys(&self) -> impl PartialEq + std::hash::Hash {
604 (
605 &self.level,
606 &self.messages,
607 &self.code,
608 &self.span,
609 &self.children,
610 &self.suggestions,
611 )
616 }
617}
618
619impl Diag {
621 pub fn span(&mut self, span: impl Into<MultiSpan>) -> &mut Self {
623 self.span = span.into();
624 self
625 }
626
627 pub fn code(&mut self, code: impl Into<DiagId>) -> &mut Self {
629 self.code = Some(code.into());
630 self
631 }
632
633 pub fn span_label(&mut self, span: Span, label: impl Into<DiagMsg>) -> &mut Self {
642 self.span.push_span_label(span, label);
643 self
644 }
645
646 pub fn span_labels(
649 &mut self,
650 spans: impl IntoIterator<Item = Span>,
651 label: impl Into<DiagMsg>,
652 ) -> &mut Self {
653 let label = label.into();
654 for span in spans {
655 self.span_label(span, label.clone());
656 }
657 self
658 }
659
660 pub(crate) fn locations_note(&mut self, emitted_at: &Location<'_>) -> &mut Self {
662 let msg = format!(
663 "created at {},\n\
664 emitted at {}",
665 self.created_at, emitted_at
666 );
667 self.note(msg)
668 }
669}
670
671impl Diag {
673 pub fn warn(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
675 self.sub(Level::Warning, msg, MultiSpan::new())
676 }
677
678 pub fn span_warn(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
681 self.sub(Level::Warning, msg, span)
682 }
683
684 pub fn note(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
686 self.sub(Level::Note, msg, MultiSpan::new())
687 }
688
689 pub fn span_note(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
692 self.sub(Level::Note, msg, span)
693 }
694
695 pub fn highlighted_note(&mut self, messages: Vec<(impl Into<DiagMsg>, Style)>) -> &mut Self {
696 self.sub_with_highlights(Level::Note, messages, MultiSpan::new())
697 }
698
699 pub fn note_once(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
702 self.sub(Level::OnceNote, msg, MultiSpan::new())
703 }
704
705 pub fn span_note_once(
708 &mut self,
709 span: impl Into<MultiSpan>,
710 msg: impl Into<DiagMsg>,
711 ) -> &mut Self {
712 self.sub(Level::OnceNote, msg, span)
713 }
714
715 pub fn help(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
717 self.sub(Level::Help, msg, MultiSpan::new())
718 }
719
720 pub fn help_once(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
723 self.sub(Level::OnceHelp, msg, MultiSpan::new())
724 }
725
726 pub fn highlighted_help(&mut self, msgs: Vec<(impl Into<DiagMsg>, Style)>) -> &mut Self {
728 self.sub_with_highlights(Level::Help, msgs, MultiSpan::new())
729 }
730
731 pub fn span_help(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
734 self.sub(Level::Help, msg, span)
735 }
736
737 fn sub(
738 &mut self,
739 level: Level,
740 msg: impl Into<DiagMsg>,
741 span: impl Into<MultiSpan>,
742 ) -> &mut Self {
743 self.children.push(SubDiagnostic {
744 level,
745 messages: vec![(msg.into(), Style::NoStyle)],
746 span: span.into(),
747 });
748 self
749 }
750
751 fn sub_with_highlights(
752 &mut self,
753 level: Level,
754 messages: Vec<(impl Into<DiagMsg>, Style)>,
755 span: MultiSpan,
756 ) -> &mut Self {
757 let messages = messages.into_iter().map(|(m, s)| (m.into(), s)).collect();
758 self.children.push(SubDiagnostic { level, messages, span });
759 self
760 }
761}
762
763impl Diag {
765 pub fn disable_suggestions(&mut self) -> &mut Self {
769 self.suggestions = Suggestions::Disabled;
770 self
771 }
772
773 pub fn seal_suggestions(&mut self) -> &mut Self {
778 if let Suggestions::Enabled(suggestions) = &mut self.suggestions {
779 let suggestions_slice = std::mem::take(suggestions).into_boxed_slice();
780 self.suggestions = Suggestions::Sealed(suggestions_slice);
781 }
782 self
783 }
784
785 fn push_suggestion(&mut self, suggestion: CodeSuggestion) {
790 if let Suggestions::Enabled(suggestions) = &mut self.suggestions {
791 suggestions.push(suggestion);
792 }
793 }
794
795 pub fn span_suggestion(
813 &mut self,
814 span: Span,
815 msg: impl Into<DiagMsg>,
816 suggestion: impl Into<DiagMsg>,
817 applicability: Applicability,
818 ) -> &mut Self {
819 self.span_suggestion_with_style(
820 span,
821 msg,
822 suggestion,
823 applicability,
824 SuggestionStyle::ShowCode,
825 );
826 self
827 }
828
829 pub fn span_suggestion_with_style(
831 &mut self,
832 span: Span,
833 msg: impl Into<DiagMsg>,
834 suggestion: impl Into<DiagMsg>,
835 applicability: Applicability,
836 style: SuggestionStyle,
837 ) -> &mut Self {
838 self.push_suggestion(CodeSuggestion {
839 substitutions: vec![Substitution {
840 parts: vec![SubstitutionPart { snippet: suggestion.into(), span }],
841 }],
842 msg: msg.into(),
843 style,
844 applicability,
845 });
846 self
847 }
848
849 pub fn multipart_suggestion(
852 &mut self,
853 msg: impl Into<DiagMsg>,
854 substitutions: Vec<(Span, DiagMsg)>,
855 applicability: Applicability,
856 ) -> &mut Self {
857 self.multipart_suggestion_with_style(
858 msg,
859 substitutions,
860 applicability,
861 SuggestionStyle::ShowCode,
862 );
863 self
864 }
865
866 pub fn multipart_suggestion_with_style(
868 &mut self,
869 msg: impl Into<DiagMsg>,
870 substitutions: Vec<(Span, DiagMsg)>,
871 applicability: Applicability,
872 style: SuggestionStyle,
873 ) -> &mut Self {
874 self.push_suggestion(CodeSuggestion {
875 substitutions: vec![Substitution {
876 parts: substitutions
877 .into_iter()
878 .map(|(span, snippet)| SubstitutionPart { span, snippet })
879 .collect(),
880 }],
881 msg: msg.into(),
882 style,
883 applicability,
884 });
885 self
886 }
887}
888
889fn flatten_messages(messages: &[(DiagMsg, Style)], with_style: bool, level: Level) -> Cow<'_, str> {
891 if with_style {
892 match messages {
893 [] => Cow::Borrowed(""),
894 [(msg, Style::NoStyle)] => Cow::Borrowed(msg.as_str()),
895 [(msg, style)] => {
896 let mut res = String::new();
897 write_fmt(&mut res, msg, style, level);
898 Cow::Owned(res)
899 }
900 messages => {
901 let mut res = String::new();
902 for (msg, style) in messages {
903 match style {
904 Style::NoStyle => res.push_str(msg.as_str()),
905 _ => write_fmt(&mut res, msg, style, level),
906 }
907 }
908 Cow::Owned(res)
909 }
910 }
911 } else {
912 match messages {
913 [] => Cow::Borrowed(""),
914 [(message, _)] => Cow::Borrowed(message.as_str()),
915 messages => messages.iter().map(|(msg, _)| msg.as_str()).collect(),
916 }
917 }
918}
919
920fn write_fmt(output: &mut String, msg: &DiagMsg, style: &Style, level: Level) {
921 let style = style.to_color_spec(level);
922 let _ = write!(output, "{style}{}{style:#}", msg.as_str());
923}
924
925#[cfg(test)]
926mod tests {
927 use super::*;
928 use crate::{BytePos, ColorChoice, Span, source_map};
929
930 #[test]
931 fn test_styled_messages() {
932 let mut diag = Diag::new(Level::Note, "test");
934
935 diag.highlighted_note(vec![
936 ("plain text ", Style::NoStyle),
937 ("removed", Style::Removal),
938 (" middle ", Style::NoStyle),
939 ("added", Style::Addition),
940 ]);
941
942 let sub = &diag.children[0];
943
944 let plain = sub.label();
946 assert_eq!(plain, "plain text removed middle added");
947
948 let styled = sub.label_with_style(true);
950 assert_eq!(
951 styled.to_string(),
952 "plain text \u{1b}[91mremoved\u{1b}[0m middle \u{1b}[92madded\u{1b}[0m".to_string()
953 );
954 }
955
956 #[test]
957 fn test_inline_suggestion() {
958 let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
959 let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
960 diag.span(var_span).span_suggestion(
961 var_span,
962 "mutable variables should use mixedCase",
963 var_sugg,
964 Applicability::MachineApplicable,
965 );
966
967 assert_eq!(diag.suggestions.len(), 1);
968 assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
969 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
970
971 let expected = r#"note: mutable variables should use mixedCase
972 --> <test.sol>:4:17
973 |
9744 | uint256 my_var = 0;
975 | ^^^^^^ help: mutable variables should use mixedCase: `myVar`
976
977"#;
978 assert_eq!(emit_human_diagnostics(diag), expected);
979 }
980
981 #[test]
982 fn test_suggestion() {
983 let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
984 let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
985 diag.span(var_span).span_suggestion_with_style(
986 var_span,
987 "mutable variables should use mixedCase",
988 var_sugg,
989 Applicability::MachineApplicable,
990 SuggestionStyle::ShowAlways,
991 );
992
993 assert_eq!(diag.suggestions.len(), 1);
994 assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
995 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowAlways);
996
997 let expected = r#"note: mutable variables should use mixedCase
998 --> <test.sol>:4:17
999 |
10004 | uint256 my_var = 0;
1001 | ^^^^^^
1002 |
1003help: mutable variables should use mixedCase
1004 |
10054 - uint256 my_var = 0;
10064 + uint256 myVar = 0;
1007 |
1008
1009"#;
1010 assert_eq!(emit_human_diagnostics(diag), expected);
1011 }
1012
1013 #[test]
1014 fn test_suggestion_with_footer() {
1015 let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
1016 let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
1017 diag.span(var_span)
1018 .span_suggestion_with_style(
1019 var_span,
1020 "mutable variables should use mixedCase",
1021 var_sugg,
1022 Applicability::MachineApplicable,
1023 SuggestionStyle::ShowAlways,
1024 )
1025 .help("some footer help msg that should be displayed at the very bottom");
1026
1027 assert_eq!(diag.suggestions.len(), 1);
1028 assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
1029 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowAlways);
1030
1031 let expected = r#"note: mutable variables should use mixedCase
1032 --> <test.sol>:4:17
1033 |
10344 | uint256 my_var = 0;
1035 | ^^^^^^
1036 |
1037help: mutable variables should use mixedCase
1038 |
10394 - uint256 my_var = 0;
10404 + uint256 myVar = 0;
1041 |
1042 = help: some footer help msg that should be displayed at the very bottom
1043
1044"#;
1045 assert_eq!(emit_human_diagnostics(diag), expected);
1046 }
1047
1048 #[test]
1049 fn test_multispan_suggestion() {
1050 let (pub_span, pub_sugg) = (Span::new(BytePos(36), BytePos(42)), "external".into());
1051 let (view_span, view_sugg) = (Span::new(BytePos(43), BytePos(47)), "pure".into());
1052 let mut diag = Diag::new(Level::Warning, "inefficient visibility and mutability");
1053 diag.span(vec![pub_span, view_span]).multipart_suggestion(
1054 "consider changing visibility and mutability",
1055 vec![(pub_span, pub_sugg), (view_span, view_sugg)],
1056 Applicability::MaybeIncorrect,
1057 );
1058
1059 assert_eq!(diag.suggestions[0].substitutions.len(), 1);
1060 assert_eq!(diag.suggestions[0].substitutions[0].parts.len(), 2);
1061 assert_eq!(diag.suggestions[0].applicability, Applicability::MaybeIncorrect);
1062 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1063
1064 let expected = r#"warning: inefficient visibility and mutability
1065 --> <test.sol>:3:20
1066 |
10673 | function foo() public view {
1068 | ^^^^^^ ^^^^
1069 |
1070help: consider changing visibility and mutability
1071 |
10723 - function foo() public view {
10733 + function foo() external pure {
1074 |
1075
1076"#;
1077 assert_eq!(emit_human_diagnostics(diag), expected);
1078 }
1079
1080 #[test]
1081 #[cfg(feature = "json")]
1082 fn test_json_suggestion() {
1083 let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
1084 let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
1085 diag.span(var_span).span_suggestion(
1086 var_span,
1087 "mutable variables should use mixedCase",
1088 var_sugg,
1089 Applicability::MachineApplicable,
1090 );
1091
1092 assert_eq!(diag.suggestions.len(), 1);
1093 assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
1094 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1095
1096 let expected = json!({
1097 "$message_type": "diagnostic",
1098 "message": "mutable variables should use mixedCase",
1099 "code": null,
1100 "level": "note",
1101 "spans": [{
1102 "file_name": "<test.sol>",
1103 "byte_start": 66,
1104 "byte_end": 72,
1105 "line_start": 4,
1106 "line_end": 4,
1107 "column_start": 17,
1108 "column_end": 23,
1109 "is_primary": true,
1110 "text": [{
1111 "text": " uint256 my_var = 0;",
1112 "highlight_start": 17,
1113 "highlight_end": 23
1114 }],
1115 "label": null,
1116 "suggested_replacement": null
1117 }],
1118 "children": [{
1119 "message": "mutable variables should use mixedCase",
1120 "code": null,
1121 "level": "help",
1122 "spans": [{
1123 "file_name": "<test.sol>",
1124 "byte_start": 66,
1125 "byte_end": 72,
1126 "line_start": 4,
1127 "line_end": 4,
1128 "column_start": 17,
1129 "column_end": 23,
1130 "is_primary": true,
1131 "text": [{
1132 "text": " uint256 my_var = 0;",
1133 "highlight_start": 17,
1134 "highlight_end": 23
1135 }],
1136 "label": null,
1137 "suggested_replacement": "myVar"
1138 }],
1139 "children": [],
1140 "rendered": null
1141 }],
1142 "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"
1143 });
1144
1145 assert_eq!(emit_json_diagnostics(diag), expected);
1146 }
1147
1148 #[test]
1149 #[cfg(feature = "json")]
1150 fn test_multispan_json_suggestion() {
1151 let (pub_span, pub_sugg) = (Span::new(BytePos(36), BytePos(42)), "external".into());
1152 let (view_span, view_sugg) = (Span::new(BytePos(43), BytePos(47)), "pure".into());
1153 let mut diag = Diag::new(Level::Warning, "inefficient visibility and mutability");
1154 diag.span(vec![pub_span, view_span]).multipart_suggestion(
1155 "consider changing visibility and mutability",
1156 vec![(pub_span, pub_sugg), (view_span, view_sugg)],
1157 Applicability::MaybeIncorrect,
1158 );
1159
1160 assert_eq!(diag.suggestions[0].substitutions.len(), 1);
1161 assert_eq!(diag.suggestions[0].substitutions[0].parts.len(), 2);
1162 assert_eq!(diag.suggestions[0].applicability, Applicability::MaybeIncorrect);
1163 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1164
1165 let expected = json!({
1166 "$message_type": "diagnostic",
1167 "message": "inefficient visibility and mutability",
1168 "code": null,
1169 "level": "warning",
1170 "spans": [
1171 {
1172 "file_name": "<test.sol>",
1173 "byte_start": 36,
1174 "byte_end": 42,
1175 "line_start": 3,
1176 "line_end": 3,
1177 "column_start": 20,
1178 "column_end": 26,
1179 "is_primary": true,
1180 "text": [{
1181 "text": " function foo() public view {",
1182 "highlight_start": 20,
1183 "highlight_end": 26
1184 }],
1185 "label": null,
1186 "suggested_replacement": null
1187 },
1188 {
1189 "file_name": "<test.sol>",
1190 "byte_start": 43,
1191 "byte_end": 47,
1192 "line_start": 3,
1193 "line_end": 3,
1194 "column_start": 27,
1195 "column_end": 31,
1196 "is_primary": true,
1197 "text": [{
1198 "text": " function foo() public view {",
1199 "highlight_start": 27,
1200 "highlight_end": 31
1201 }],
1202 "label": null,
1203 "suggested_replacement": null
1204 }
1205 ],
1206 "children": [{
1207 "message": "consider changing visibility and mutability",
1208 "code": null,
1209 "level": "help",
1210 "spans": [
1211 {
1212 "file_name": "<test.sol>",
1213 "byte_start": 36,
1214 "byte_end": 42,
1215 "line_start": 3,
1216 "line_end": 3,
1217 "column_start": 20,
1218 "column_end": 26,
1219 "is_primary": true,
1220 "text": [{
1221 "text": " function foo() public view {",
1222 "highlight_start": 20,
1223 "highlight_end": 26
1224 }],
1225 "label": null,
1226 "suggested_replacement": "external"
1227 },
1228 {
1229 "file_name": "<test.sol>",
1230 "byte_start": 43,
1231 "byte_end": 47,
1232 "line_start": 3,
1233 "line_end": 3,
1234 "column_start": 27,
1235 "column_end": 31,
1236 "is_primary": true,
1237 "text": [{
1238 "text": " function foo() public view {",
1239 "highlight_start": 27,
1240 "highlight_end": 31
1241 }],
1242 "label": null,
1243 "suggested_replacement": "pure"
1244 }
1245 ],
1246 "children": [],
1247 "rendered": null
1248 }],
1249 "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"
1250 });
1251 assert_eq!(emit_json_diagnostics(diag), expected);
1252 }
1253
1254 const CONTRACT: &str = r#"
1257contract Test {
1258 function foo() public view {
1259 uint256 my_var = 0;
1260 }
1261}"#;
1262
1263 fn emit_human_diagnostics(diag: Diag) -> String {
1265 let sm = source_map::SourceMap::empty();
1266 sm.new_source_file(source_map::FileName::custom("test.sol"), CONTRACT.to_string()).unwrap();
1267
1268 let dcx = DiagCtxt::with_buffer_emitter(Some(std::sync::Arc::new(sm)), ColorChoice::Never);
1269 let _ = dcx.emit_diagnostic(diag);
1270
1271 dcx.emitted_diagnostics().unwrap().0
1272 }
1273
1274 #[cfg(feature = "json")]
1275 use {
1276 serde_json::{Value, json},
1277 std::sync::{Arc, Mutex},
1278 };
1279
1280 #[cfg(feature = "json")]
1282 #[derive(Clone)]
1283 struct SharedWriter(Arc<Mutex<Vec<u8>>>);
1284
1285 #[cfg(feature = "json")]
1286 impl std::io::Write for SharedWriter {
1287 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1288 self.0.lock().unwrap().write(buf)
1289 }
1290 fn flush(&mut self) -> std::io::Result<()> {
1291 self.0.lock().unwrap().flush()
1292 }
1293 }
1294
1295 #[cfg(feature = "json")]
1297 fn emit_json_diagnostics(diag: Diag) -> Value {
1298 let sm = Arc::new(source_map::SourceMap::empty());
1299 sm.new_source_file(source_map::FileName::custom("test.sol"), CONTRACT.to_string()).unwrap();
1300
1301 let writer = Arc::new(Mutex::new(Vec::new()));
1302 let emitter = JsonEmitter::new(Box::new(SharedWriter(writer.clone())), Arc::clone(&sm))
1303 .rustc_like(true);
1304 let dcx = DiagCtxt::new(Box::new(emitter));
1305 let _ = dcx.emit_diagnostic(diag);
1306
1307 let buffer = writer.lock().unwrap();
1308 serde_json::from_str(
1309 &String::from_utf8(buffer.clone()).expect("JSON output was not valid UTF-8"),
1310 )
1311 .expect("failed to deserialize JSON")
1312 }
1313}