use crate::Span;
use anstyle::{AnsiColor, Color};
use std::{
borrow::Cow,
fmt::{self, Write},
hash::{Hash, Hasher},
ops::Deref,
panic::Location,
};
mod builder;
pub use builder::{DiagBuilder, EmissionGuarantee};
mod context;
pub use context::{DiagCtxt, DiagCtxtFlags};
mod emitter;
#[cfg(feature = "json")]
pub use emitter::JsonEmitter;
pub use emitter::{
DynEmitter, Emitter, HumanBufferEmitter, HumanEmitter, InMemoryEmitter, LocalEmitter,
SilentEmitter,
};
mod message;
pub use message::{DiagMsg, MultiSpan, SpanLabel};
pub struct EmittedDiagnostics(pub(crate) String);
impl fmt::Debug for EmittedDiagnostics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl fmt::Display for EmittedDiagnostics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for EmittedDiagnostics {}
impl EmittedDiagnostics {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ErrorGuaranteed(());
impl fmt::Debug for ErrorGuaranteed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ErrorGuaranteed")
}
}
impl ErrorGuaranteed {
#[inline]
pub const fn new_unchecked() -> Self {
Self(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BugAbort;
pub struct ExplicitBug;
pub struct FatalAbort;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DiagId {
s: Cow<'static, str>,
}
impl DiagId {
pub fn new_str(s: impl Into<Cow<'static, str>>) -> Self {
Self { s: s.into() }
}
#[doc(hidden)]
#[cfg_attr(debug_assertions, track_caller)]
pub fn new_from_macro(id: u32) -> Self {
debug_assert!((1..=9999).contains(&id), "error code must be in range 0001-9999");
Self { s: Cow::Owned(format!("{id:04}")) }
}
pub fn as_string(&self) -> String {
self.s.to_string()
}
}
#[macro_export]
macro_rules! error_code {
($id:literal) => {
$crate::diagnostics::DiagId::new_from_macro($id)
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Level {
Bug,
Fatal,
Error,
Warning,
Note,
OnceNote,
Help,
OnceHelp,
FailureNote,
Allow,
}
impl Level {
pub fn to_str(self) -> &'static str {
match self {
Self::Bug => "error: internal compiler error",
Self::Fatal | Self::Error => "error",
Self::Warning => "warning",
Self::Note | Self::OnceNote => "note",
Self::Help | Self::OnceHelp => "help",
Self::FailureNote => "failure-note",
Self::Allow
=> unreachable!(),
}
}
#[inline]
pub fn is_error(self) -> bool {
match self {
Self::Bug | Self::Fatal | Self::Error | Self::FailureNote => true,
Self::Warning
| Self::Note
| Self::OnceNote
| Self::Help
| Self::OnceHelp
| Self::Allow => false,
}
}
#[inline]
pub fn is_note(self) -> bool {
match self {
Self::Note | Self::OnceNote => true,
Self::Bug
| Self::Fatal
| Self::Error
| Self::FailureNote
| Self::Warning
| Self::Help
| Self::OnceHelp
| Self::Allow => false,
}
}
#[inline]
pub const fn style(self) -> anstyle::Style {
anstyle::Style::new().fg_color(self.color()).bold()
}
#[inline]
pub const fn color(self) -> Option<Color> {
match self.ansi_color() {
Some(c) => Some(Color::Ansi(c)),
None => None,
}
}
#[inline]
pub const fn ansi_color(self) -> Option<AnsiColor> {
match self {
Self::Bug | Self::Fatal | Self::Error => Some(AnsiColor::BrightRed),
Self::Warning => {
Some(if cfg!(windows) { AnsiColor::BrightYellow } else { AnsiColor::Yellow })
}
Self::Note | Self::OnceNote => Some(AnsiColor::BrightGreen),
Self::Help | Self::OnceHelp => Some(AnsiColor::BrightCyan),
Self::FailureNote | Self::Allow => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Style {
MainHeaderMsg,
HeaderMsg,
LineAndColumn,
LineNumber,
Quotation,
UnderlinePrimary,
UnderlineSecondary,
LabelPrimary,
LabelSecondary,
NoStyle,
Level(Level),
Highlight,
Addition,
Removal,
}
impl Style {
pub const fn to_color_spec(self, level: Level) -> anstyle::Style {
use AnsiColor::*;
const BRIGHT_BLUE: Color = Color::Ansi(if cfg!(windows) { BrightCyan } else { BrightBlue });
const GREEN: Color = Color::Ansi(BrightGreen);
const MAGENTA: Color = Color::Ansi(BrightMagenta);
const RED: Color = Color::Ansi(BrightRed);
const WHITE: Color = Color::Ansi(BrightWhite);
let s = anstyle::Style::new();
match self {
Self::Addition => s.fg_color(Some(GREEN)),
Self::Removal => s.fg_color(Some(RED)),
Self::LineAndColumn => s,
Self::LineNumber => s.fg_color(Some(BRIGHT_BLUE)).bold(),
Self::Quotation => s,
Self::MainHeaderMsg => if cfg!(windows) { s.fg_color(Some(WHITE)) } else { s }.bold(),
Self::UnderlinePrimary | Self::LabelPrimary => s.fg_color(level.color()).bold(),
Self::UnderlineSecondary | Self::LabelSecondary => s.fg_color(Some(BRIGHT_BLUE)).bold(),
Self::HeaderMsg | Self::NoStyle => s,
Self::Level(level2) => s.fg_color(level2.color()).bold(),
Self::Highlight => s.fg_color(Some(MAGENTA)).bold(),
}
}
}
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "json", serde(rename_all = "kebab-case"))]
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum Applicability {
MachineApplicable,
MaybeIncorrect,
HasPlaceholders,
#[default]
Unspecified,
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]
pub enum SuggestionStyle {
HideCodeInline,
HideCodeAlways,
CompletelyHidden,
#[default]
ShowCode,
ShowAlways,
}
impl SuggestionStyle {
fn hide_inline(&self) -> bool {
!matches!(*self, Self::ShowCode)
}
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub enum Suggestions {
Enabled(Vec<CodeSuggestion>),
Sealed(Box<[CodeSuggestion]>),
Disabled,
}
impl Suggestions {
pub fn unwrap_tag(&self) -> &[CodeSuggestion] {
match self {
Self::Enabled(suggestions) => suggestions,
Self::Sealed(suggestions) => suggestions,
Self::Disabled => &[],
}
}
}
impl Default for Suggestions {
fn default() -> Self {
Self::Enabled(vec![])
}
}
impl Deref for Suggestions {
type Target = [CodeSuggestion];
fn deref(&self) -> &Self::Target {
self.unwrap_tag()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CodeSuggestion {
pub substitutions: Vec<Substitution>,
pub msg: DiagMsg,
pub style: SuggestionStyle,
pub applicability: Applicability,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SubstitutionPart {
pub span: Span,
pub snippet: DiagMsg,
}
impl SubstitutionPart {
pub fn is_addition(&self) -> bool {
self.span.lo() == self.span.hi() && !self.snippet.is_empty()
}
pub fn is_deletion(&self) -> bool {
self.span.lo() != self.span.hi() && self.snippet.is_empty()
}
pub fn is_replacement(&self) -> bool {
self.span.lo() != self.span.hi() && !self.snippet.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Substitution {
pub parts: Vec<SubstitutionPart>,
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub struct SubDiagnostic {
pub level: Level,
pub messages: Vec<(DiagMsg, Style)>,
pub span: MultiSpan,
}
impl SubDiagnostic {
pub fn label(&self) -> Cow<'_, str> {
self.label_with_style(false)
}
pub fn label_with_style(&self, supports_color: bool) -> Cow<'_, str> {
flatten_messages(&self.messages, supports_color, self.level)
}
}
#[must_use]
#[derive(Clone, Debug)]
pub struct Diag {
pub(crate) level: Level,
pub messages: Vec<(DiagMsg, Style)>,
pub span: MultiSpan,
pub children: Vec<SubDiagnostic>,
pub code: Option<DiagId>,
pub suggestions: Suggestions,
pub created_at: &'static Location<'static>,
}
impl PartialEq for Diag {
fn eq(&self, other: &Self) -> bool {
self.keys() == other.keys()
}
}
impl Hash for Diag {
fn hash<H: Hasher>(&self, state: &mut H) {
self.keys().hash(state);
}
}
impl Diag {
#[track_caller]
pub fn new<M: Into<DiagMsg>>(level: Level, msg: M) -> Self {
Self::new_with_messages(level, vec![(msg.into(), Style::NoStyle)])
}
#[track_caller]
pub fn new_with_messages(level: Level, messages: Vec<(DiagMsg, Style)>) -> Self {
Self {
level,
messages,
code: None,
span: MultiSpan::new(),
children: vec![],
suggestions: Suggestions::default(),
created_at: Location::caller(),
}
}
#[inline]
pub fn is_error(&self) -> bool {
self.level.is_error()
}
#[inline]
pub fn is_note(&self) -> bool {
self.level.is_note()
}
pub fn label(&self) -> Cow<'_, str> {
flatten_messages(&self.messages, false, self.level)
}
pub fn label_with_style(&self, supports_color: bool) -> Cow<'_, str> {
flatten_messages(&self.messages, supports_color, self.level)
}
pub fn messages(&self) -> &[(DiagMsg, Style)] {
&self.messages
}
pub fn level(&self) -> Level {
self.level
}
pub fn id(&self) -> Option<String> {
self.code.as_ref().map(|code| code.as_string())
}
fn keys(&self) -> impl PartialEq + std::hash::Hash {
(
&self.level,
&self.messages,
&self.code,
&self.span,
&self.children,
&self.suggestions,
)
}
}
impl Diag {
pub fn span(&mut self, span: impl Into<MultiSpan>) -> &mut Self {
self.span = span.into();
self
}
pub fn code(&mut self, code: impl Into<DiagId>) -> &mut Self {
self.code = Some(code.into());
self
}
pub fn span_label(&mut self, span: Span, label: impl Into<DiagMsg>) -> &mut Self {
self.span.push_span_label(span, label);
self
}
pub fn span_labels(
&mut self,
spans: impl IntoIterator<Item = Span>,
label: impl Into<DiagMsg>,
) -> &mut Self {
let label = label.into();
for span in spans {
self.span_label(span, label.clone());
}
self
}
pub(crate) fn locations_note(&mut self, emitted_at: &Location<'_>) -> &mut Self {
let msg = format!(
"created at {},\n\
emitted at {}",
self.created_at, emitted_at
);
self.note(msg)
}
}
impl Diag {
pub fn warn(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
self.sub(Level::Warning, msg, MultiSpan::new())
}
pub fn span_warn(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
self.sub(Level::Warning, msg, span)
}
pub fn note(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
self.sub(Level::Note, msg, MultiSpan::new())
}
pub fn span_note(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
self.sub(Level::Note, msg, span)
}
pub fn highlighted_note(&mut self, messages: Vec<(impl Into<DiagMsg>, Style)>) -> &mut Self {
self.sub_with_highlights(Level::Note, messages, MultiSpan::new())
}
pub fn note_once(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
self.sub(Level::OnceNote, msg, MultiSpan::new())
}
pub fn span_note_once(
&mut self,
span: impl Into<MultiSpan>,
msg: impl Into<DiagMsg>,
) -> &mut Self {
self.sub(Level::OnceNote, msg, span)
}
pub fn help(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
self.sub(Level::Help, msg, MultiSpan::new())
}
pub fn help_once(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
self.sub(Level::OnceHelp, msg, MultiSpan::new())
}
pub fn highlighted_help(&mut self, msgs: Vec<(impl Into<DiagMsg>, Style)>) -> &mut Self {
self.sub_with_highlights(Level::Help, msgs, MultiSpan::new())
}
pub fn span_help(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
self.sub(Level::Help, msg, span)
}
fn sub(
&mut self,
level: Level,
msg: impl Into<DiagMsg>,
span: impl Into<MultiSpan>,
) -> &mut Self {
self.children.push(SubDiagnostic {
level,
messages: vec![(msg.into(), Style::NoStyle)],
span: span.into(),
});
self
}
fn sub_with_highlights(
&mut self,
level: Level,
messages: Vec<(impl Into<DiagMsg>, Style)>,
span: MultiSpan,
) -> &mut Self {
let messages = messages.into_iter().map(|(m, s)| (m.into(), s)).collect();
self.children.push(SubDiagnostic { level, messages, span });
self
}
}
impl Diag {
pub fn disable_suggestions(&mut self) -> &mut Self {
self.suggestions = Suggestions::Disabled;
self
}
pub fn seal_suggestions(&mut self) -> &mut Self {
if let Suggestions::Enabled(suggestions) = &mut self.suggestions {
let suggestions_slice = std::mem::take(suggestions).into_boxed_slice();
self.suggestions = Suggestions::Sealed(suggestions_slice);
}
self
}
fn push_suggestion(&mut self, suggestion: CodeSuggestion) {
if let Suggestions::Enabled(suggestions) = &mut self.suggestions {
suggestions.push(suggestion);
}
}
pub fn span_suggestion(
&mut self,
span: Span,
msg: impl Into<DiagMsg>,
suggestion: impl Into<DiagMsg>,
applicability: Applicability,
) -> &mut Self {
self.span_suggestion_with_style(
span,
msg,
suggestion,
applicability,
SuggestionStyle::ShowCode,
);
self
}
pub fn span_suggestion_with_style(
&mut self,
span: Span,
msg: impl Into<DiagMsg>,
suggestion: impl Into<DiagMsg>,
applicability: Applicability,
style: SuggestionStyle,
) -> &mut Self {
self.push_suggestion(CodeSuggestion {
substitutions: vec![Substitution {
parts: vec![SubstitutionPart { snippet: suggestion.into(), span }],
}],
msg: msg.into(),
style,
applicability,
});
self
}
pub fn multipart_suggestion(
&mut self,
msg: impl Into<DiagMsg>,
substitutions: Vec<(Span, DiagMsg)>,
applicability: Applicability,
) -> &mut Self {
self.multipart_suggestion_with_style(
msg,
substitutions,
applicability,
SuggestionStyle::ShowCode,
);
self
}
pub fn multipart_suggestion_with_style(
&mut self,
msg: impl Into<DiagMsg>,
substitutions: Vec<(Span, DiagMsg)>,
applicability: Applicability,
style: SuggestionStyle,
) -> &mut Self {
self.push_suggestion(CodeSuggestion {
substitutions: vec![Substitution {
parts: substitutions
.into_iter()
.map(|(span, snippet)| SubstitutionPart { span, snippet })
.collect(),
}],
msg: msg.into(),
style,
applicability,
});
self
}
}
fn flatten_messages(messages: &[(DiagMsg, Style)], with_style: bool, level: Level) -> Cow<'_, str> {
if with_style {
match messages {
[] => Cow::Borrowed(""),
[(msg, Style::NoStyle)] => Cow::Borrowed(msg.as_str()),
[(msg, style)] => {
let mut res = String::new();
write_fmt(&mut res, msg, style, level);
Cow::Owned(res)
}
messages => {
let mut res = String::new();
for (msg, style) in messages {
match style {
Style::NoStyle => res.push_str(msg.as_str()),
_ => write_fmt(&mut res, msg, style, level),
}
}
Cow::Owned(res)
}
}
} else {
match messages {
[] => Cow::Borrowed(""),
[(message, _)] => Cow::Borrowed(message.as_str()),
messages => messages.iter().map(|(msg, _)| msg.as_str()).collect(),
}
}
}
fn write_fmt(output: &mut String, msg: &DiagMsg, style: &Style, level: Level) {
let style = style.to_color_spec(level);
let _ = write!(output, "{style}{}{style:#}", msg.as_str());
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{BytePos, ColorChoice, Span, source_map};
#[test]
fn test_styled_messages() {
let mut diag = Diag::new(Level::Note, "test");
diag.highlighted_note(vec![
("plain text ", Style::NoStyle),
("removed", Style::Removal),
(" middle ", Style::NoStyle),
("added", Style::Addition),
]);
let sub = &diag.children[0];
let plain = sub.label();
assert_eq!(plain, "plain text removed middle added");
let styled = sub.label_with_style(true);
assert_eq!(
styled.to_string(),
"plain text \u{1b}[91mremoved\u{1b}[0m middle \u{1b}[92madded\u{1b}[0m".to_string()
);
}
#[test]
fn test_inline_suggestion() {
let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
diag.span(var_span).span_suggestion(
var_span,
"mutable variables should use mixedCase",
var_sugg,
Applicability::MachineApplicable,
);
assert_eq!(diag.suggestions.len(), 1);
assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
let expected = r#"note: mutable variables should use mixedCase
--> <test.sol>:4:17
|
4 | uint256 my_var = 0;
| ^^^^^^ help: mutable variables should use mixedCase: `myVar`
"#;
assert_eq!(emit_human_diagnostics(diag), expected);
}
#[test]
fn test_suggestion() {
let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
diag.span(var_span).span_suggestion_with_style(
var_span,
"mutable variables should use mixedCase",
var_sugg,
Applicability::MachineApplicable,
SuggestionStyle::ShowAlways,
);
assert_eq!(diag.suggestions.len(), 1);
assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowAlways);
let expected = r#"note: mutable variables should use mixedCase
--> <test.sol>:4:17
|
4 | uint256 my_var = 0;
| ^^^^^^
|
help: mutable variables should use mixedCase
|
4 - uint256 my_var = 0;
4 + uint256 myVar = 0;
|
"#;
assert_eq!(emit_human_diagnostics(diag), expected);
}
#[test]
fn test_suggestion_with_footer() {
let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
diag.span(var_span)
.span_suggestion_with_style(
var_span,
"mutable variables should use mixedCase",
var_sugg,
Applicability::MachineApplicable,
SuggestionStyle::ShowAlways,
)
.help("some footer help msg that should be displayed at the very bottom");
assert_eq!(diag.suggestions.len(), 1);
assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowAlways);
let expected = r#"note: mutable variables should use mixedCase
--> <test.sol>:4:17
|
4 | uint256 my_var = 0;
| ^^^^^^
|
help: mutable variables should use mixedCase
|
4 - uint256 my_var = 0;
4 + uint256 myVar = 0;
|
= help: some footer help msg that should be displayed at the very bottom
"#;
assert_eq!(emit_human_diagnostics(diag), expected);
}
#[test]
fn test_multispan_suggestion() {
let (pub_span, pub_sugg) = (Span::new(BytePos(36), BytePos(42)), "external".into());
let (view_span, view_sugg) = (Span::new(BytePos(43), BytePos(47)), "pure".into());
let mut diag = Diag::new(Level::Warning, "inefficient visibility and mutability");
diag.span(vec![pub_span, view_span]).multipart_suggestion(
"consider changing visibility and mutability",
vec![(pub_span, pub_sugg), (view_span, view_sugg)],
Applicability::MaybeIncorrect,
);
assert_eq!(diag.suggestions[0].substitutions.len(), 1);
assert_eq!(diag.suggestions[0].substitutions[0].parts.len(), 2);
assert_eq!(diag.suggestions[0].applicability, Applicability::MaybeIncorrect);
assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
let expected = r#"warning: inefficient visibility and mutability
--> <test.sol>:3:20
|
3 | function foo() public view {
| ^^^^^^ ^^^^
|
help: consider changing visibility and mutability
|
3 - function foo() public view {
3 + function foo() external pure {
|
"#;
assert_eq!(emit_human_diagnostics(diag), expected);
}
#[test]
#[cfg(feature = "json")]
fn test_json_suggestion() {
let (var_span, var_sugg) = (Span::new(BytePos(66), BytePos(72)), "myVar");
let mut diag = Diag::new(Level::Note, "mutable variables should use mixedCase");
diag.span(var_span).span_suggestion(
var_span,
"mutable variables should use mixedCase",
var_sugg,
Applicability::MachineApplicable,
);
assert_eq!(diag.suggestions.len(), 1);
assert_eq!(diag.suggestions[0].applicability, Applicability::MachineApplicable);
assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
let expected = json!({
"$message_type": "diagnostic",
"message": "mutable variables should use mixedCase",
"code": null,
"level": "note",
"spans": [{
"file_name": "<test.sol>",
"byte_start": 66,
"byte_end": 72,
"line_start": 4,
"line_end": 4,
"column_start": 17,
"column_end": 23,
"is_primary": true,
"text": [{
"text": " uint256 my_var = 0;",
"highlight_start": 17,
"highlight_end": 23
}],
"label": null,
"suggested_replacement": null
}],
"children": [{
"message": "mutable variables should use mixedCase",
"code": null,
"level": "help",
"spans": [{
"file_name": "<test.sol>",
"byte_start": 66,
"byte_end": 72,
"line_start": 4,
"line_end": 4,
"column_start": 17,
"column_end": 23,
"is_primary": true,
"text": [{
"text": " uint256 my_var = 0;",
"highlight_start": 17,
"highlight_end": 23
}],
"label": null,
"suggested_replacement": "myVar"
}],
"children": [],
"rendered": null
}],
"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"
});
assert_eq!(emit_json_diagnostics(diag), expected);
}
#[test]
#[cfg(feature = "json")]
fn test_multispan_json_suggestion() {
let (pub_span, pub_sugg) = (Span::new(BytePos(36), BytePos(42)), "external".into());
let (view_span, view_sugg) = (Span::new(BytePos(43), BytePos(47)), "pure".into());
let mut diag = Diag::new(Level::Warning, "inefficient visibility and mutability");
diag.span(vec![pub_span, view_span]).multipart_suggestion(
"consider changing visibility and mutability",
vec![(pub_span, pub_sugg), (view_span, view_sugg)],
Applicability::MaybeIncorrect,
);
assert_eq!(diag.suggestions[0].substitutions.len(), 1);
assert_eq!(diag.suggestions[0].substitutions[0].parts.len(), 2);
assert_eq!(diag.suggestions[0].applicability, Applicability::MaybeIncorrect);
assert_eq!(diag.suggestions[0].style, SuggestionStyle::ShowCode);
let expected = json!({
"$message_type": "diagnostic",
"message": "inefficient visibility and mutability",
"code": null,
"level": "warning",
"spans": [
{
"file_name": "<test.sol>",
"byte_start": 36,
"byte_end": 42,
"line_start": 3,
"line_end": 3,
"column_start": 20,
"column_end": 26,
"is_primary": true,
"text": [{
"text": " function foo() public view {",
"highlight_start": 20,
"highlight_end": 26
}],
"label": null,
"suggested_replacement": null
},
{
"file_name": "<test.sol>",
"byte_start": 43,
"byte_end": 47,
"line_start": 3,
"line_end": 3,
"column_start": 27,
"column_end": 31,
"is_primary": true,
"text": [{
"text": " function foo() public view {",
"highlight_start": 27,
"highlight_end": 31
}],
"label": null,
"suggested_replacement": null
}
],
"children": [{
"message": "consider changing visibility and mutability",
"code": null,
"level": "help",
"spans": [
{
"file_name": "<test.sol>",
"byte_start": 36,
"byte_end": 42,
"line_start": 3,
"line_end": 3,
"column_start": 20,
"column_end": 26,
"is_primary": true,
"text": [{
"text": " function foo() public view {",
"highlight_start": 20,
"highlight_end": 26
}],
"label": null,
"suggested_replacement": "external"
},
{
"file_name": "<test.sol>",
"byte_start": 43,
"byte_end": 47,
"line_start": 3,
"line_end": 3,
"column_start": 27,
"column_end": 31,
"is_primary": true,
"text": [{
"text": " function foo() public view {",
"highlight_start": 27,
"highlight_end": 31
}],
"label": null,
"suggested_replacement": "pure"
}
],
"children": [],
"rendered": null
}],
"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"
});
assert_eq!(emit_json_diagnostics(diag), expected);
}
const CONTRACT: &str = r#"
contract Test {
function foo() public view {
uint256 my_var = 0;
}
}"#;
fn emit_human_diagnostics(diag: Diag) -> String {
let sm = source_map::SourceMap::empty();
sm.new_source_file(source_map::FileName::custom("test.sol"), CONTRACT.to_string()).unwrap();
let dcx = DiagCtxt::with_buffer_emitter(Some(std::sync::Arc::new(sm)), ColorChoice::Never);
let _ = dcx.emit_diagnostic(diag);
dcx.emitted_diagnostics().unwrap().0
}
#[cfg(feature = "json")]
use {
serde_json::{Value, json},
std::sync::{Arc, Mutex},
};
#[cfg(feature = "json")]
#[derive(Clone)]
struct SharedWriter(Arc<Mutex<Vec<u8>>>);
#[cfg(feature = "json")]
impl std::io::Write for SharedWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.lock().unwrap().write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.lock().unwrap().flush()
}
}
#[cfg(feature = "json")]
fn emit_json_diagnostics(diag: Diag) -> Value {
let sm = Arc::new(source_map::SourceMap::empty());
sm.new_source_file(source_map::FileName::custom("test.sol"), CONTRACT.to_string()).unwrap();
let writer = Arc::new(Mutex::new(Vec::new()));
let emitter = JsonEmitter::new(Box::new(SharedWriter(writer.clone())), Arc::clone(&sm))
.rustc_like(true);
let dcx = DiagCtxt::new(Box::new(emitter));
let _ = dcx.emit_diagnostic(diag);
let buffer = writer.lock().unwrap();
serde_json::from_str(
&String::from_utf8(buffer.clone()).expect("JSON output was not valid UTF-8"),
)
.expect("failed to deserialize JSON")
}
}