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_multispan_suggestion() {
1015 let (pub_span, pub_sugg) = (Span::new(BytePos(36), BytePos(42)), "external".into());
1016 let (view_span, view_sugg) = (Span::new(BytePos(43), BytePos(47)), "pure".into());
1017 let mut diag = Diag::new(Level::Warning, "inefficient visibility and mutability");
1018 diag.span(vec![pub_span, view_span]).multipart_suggestion(
1019 "consider changing visibility and mutability",
1020 vec![(pub_span, pub_sugg), (view_span, view_sugg)],
1021 Applicability::MaybeIncorrect,
1022 );
1023
1024 assert_eq!(diag.suggestions[0].substitutions.len(), 1);
1025 assert_eq!(diag.suggestions[0].substitutions[0].parts.len(), 2);
1026 assert_eq!(diag.suggestions[0].applicability, Applicability::MaybeIncorrect);
1027 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1028
1029 let expected = r#"warning: inefficient visibility and mutability
1030 --> <test.sol>:3:20
1031 |
10323 | function foo() public view {
1033 | ^^^^^^ ^^^^
1034 |
1035help: consider changing visibility and mutability
1036 |
10373 - function foo() public view {
10383 + function foo() external pure {
1039 |
1040
1041"#;
1042 assert_eq!(emit_human_diagnostics(diag), expected);
1043 }
1044
1045 #[test]
1046 #[cfg(feature = "json")]
1047 fn test_json_suggestion() {
1048 let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
1049 let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
1050 diag.span(var_span).span_suggestion(
1051 var_span,
1052 "mutable variables should use mixedCase",
1053 var_sugg,
1054 Applicability::MachineApplicable,
1055 );
1056
1057 assert_eq!(diag.suggestions.len(), 1);
1058 assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
1059 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1060
1061 let expected = json!({
1062 "$message_type": "diagnostic",
1063 "message": "mutable variables should use mixedCase",
1064 "code": null,
1065 "level": "note",
1066 "spans": [{
1067 "file_name": "<test.sol>",
1068 "byte_start": 66,
1069 "byte_end": 72,
1070 "line_start": 4,
1071 "line_end": 4,
1072 "column_start": 17,
1073 "column_end": 23,
1074 "is_primary": true,
1075 "text": [{
1076 "text": " uint256 my_var = 0;",
1077 "highlight_start": 17,
1078 "highlight_end": 23
1079 }],
1080 "label": null,
1081 "suggested_replacement": null
1082 }],
1083 "children": [{
1084 "message": "mutable variables should use mixedCase",
1085 "code": null,
1086 "level": "help",
1087 "spans": [{
1088 "file_name": "<test.sol>",
1089 "byte_start": 66,
1090 "byte_end": 72,
1091 "line_start": 4,
1092 "line_end": 4,
1093 "column_start": 17,
1094 "column_end": 23,
1095 "is_primary": true,
1096 "text": [{
1097 "text": " uint256 my_var = 0;",
1098 "highlight_start": 17,
1099 "highlight_end": 23
1100 }],
1101 "label": null,
1102 "suggested_replacement": "myVar"
1103 }],
1104 "children": [],
1105 "rendered": null
1106 }],
1107 "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"
1108 });
1109
1110 assert_eq!(emit_json_diagnostics(diag), expected);
1111 }
1112
1113 #[test]
1114 #[cfg(feature = "json")]
1115 fn test_multispan_json_suggestion() {
1116 let (pub_span, pub_sugg) = (Span::new(BytePos(36), BytePos(42)), "external".into());
1117 let (view_span, view_sugg) = (Span::new(BytePos(43), BytePos(47)), "pure".into());
1118 let mut diag = Diag::new(Level::Warning, "inefficient visibility and mutability");
1119 diag.span(vec![pub_span, view_span]).multipart_suggestion(
1120 "consider changing visibility and mutability",
1121 vec![(pub_span, pub_sugg), (view_span, view_sugg)],
1122 Applicability::MaybeIncorrect,
1123 );
1124
1125 assert_eq!(diag.suggestions[0].substitutions.len(), 1);
1126 assert_eq!(diag.suggestions[0].substitutions[0].parts.len(), 2);
1127 assert_eq!(diag.suggestions[0].applicability, Applicability::MaybeIncorrect);
1128 assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
1129
1130 let expected = json!({
1131 "$message_type": "diagnostic",
1132 "message": "inefficient visibility and mutability",
1133 "code": null,
1134 "level": "warning",
1135 "spans": [
1136 {
1137 "file_name": "<test.sol>",
1138 "byte_start": 36,
1139 "byte_end": 42,
1140 "line_start": 3,
1141 "line_end": 3,
1142 "column_start": 20,
1143 "column_end": 26,
1144 "is_primary": true,
1145 "text": [{
1146 "text": " function foo() public view {",
1147 "highlight_start": 20,
1148 "highlight_end": 26
1149 }],
1150 "label": null,
1151 "suggested_replacement": null
1152 },
1153 {
1154 "file_name": "<test.sol>",
1155 "byte_start": 43,
1156 "byte_end": 47,
1157 "line_start": 3,
1158 "line_end": 3,
1159 "column_start": 27,
1160 "column_end": 31,
1161 "is_primary": true,
1162 "text": [{
1163 "text": " function foo() public view {",
1164 "highlight_start": 27,
1165 "highlight_end": 31
1166 }],
1167 "label": null,
1168 "suggested_replacement": null
1169 }
1170 ],
1171 "children": [{
1172 "message": "consider changing visibility and mutability",
1173 "code": null,
1174 "level": "help",
1175 "spans": [
1176 {
1177 "file_name": "<test.sol>",
1178 "byte_start": 36,
1179 "byte_end": 42,
1180 "line_start": 3,
1181 "line_end": 3,
1182 "column_start": 20,
1183 "column_end": 26,
1184 "is_primary": true,
1185 "text": [{
1186 "text": " function foo() public view {",
1187 "highlight_start": 20,
1188 "highlight_end": 26
1189 }],
1190 "label": null,
1191 "suggested_replacement": "external"
1192 },
1193 {
1194 "file_name": "<test.sol>",
1195 "byte_start": 43,
1196 "byte_end": 47,
1197 "line_start": 3,
1198 "line_end": 3,
1199 "column_start": 27,
1200 "column_end": 31,
1201 "is_primary": true,
1202 "text": [{
1203 "text": " function foo() public view {",
1204 "highlight_start": 27,
1205 "highlight_end": 31
1206 }],
1207 "label": null,
1208 "suggested_replacement": "pure"
1209 }
1210 ],
1211 "children": [],
1212 "rendered": null
1213 }],
1214 "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"
1215 });
1216 assert_eq!(emit_json_diagnostics(diag), expected);
1217 }
1218
1219 const CONTRACT: &str = r#"
1222contract Test {
1223 function foo() public view {
1224 uint256 my_var = 0;
1225 }
1226}"#;
1227
1228 fn emit_human_diagnostics(diag: Diag) -> String {
1230 let sm = source_map::SourceMap::empty();
1231 sm.new_source_file(source_map::FileName::custom("test.sol"), CONTRACT.to_string()).unwrap();
1232
1233 let dcx = DiagCtxt::with_buffer_emitter(Some(std::sync::Arc::new(sm)), ColorChoice::Never);
1234 let _ = dcx.emit_diagnostic(diag);
1235
1236 dcx.emitted_diagnostics().unwrap().0
1237 }
1238
1239 #[cfg(feature = "json")]
1240 use {
1241 serde_json::{Value, json},
1242 std::sync::{Arc, Mutex},
1243 };
1244
1245 #[cfg(feature = "json")]
1247 #[derive(Clone)]
1248 struct SharedWriter(Arc<Mutex<Vec<u8>>>);
1249
1250 #[cfg(feature = "json")]
1251 impl std::io::Write for SharedWriter {
1252 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1253 self.0.lock().unwrap().write(buf)
1254 }
1255 fn flush(&mut self) -> std::io::Result<()> {
1256 self.0.lock().unwrap().flush()
1257 }
1258 }
1259
1260 #[cfg(feature = "json")]
1262 fn emit_json_diagnostics(diag: Diag) -> Value {
1263 let sm = Arc::new(source_map::SourceMap::empty());
1264 sm.new_source_file(source_map::FileName::custom("test.sol"), CONTRACT.to_string()).unwrap();
1265
1266 let writer = Arc::new(Mutex::new(Vec::new()));
1267 let emitter = JsonEmitter::new(Box::new(SharedWriter(writer.clone())), Arc::clone(&sm))
1268 .rustc_like(true);
1269 let dcx = DiagCtxt::new(Box::new(emitter));
1270 let _ = dcx.emit_diagnostic(diag);
1271
1272 let buffer = writer.lock().unwrap();
1273 serde_json::from_str(
1274 &String::from_utf8(buffer.clone()).expect("JSON output was not valid UTF-8"),
1275 )
1276 .expect("failed to deserialize JSON")
1277 }
1278}