Skip to content

Implement JSON output for diagnostics #295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion fathom-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use codespan_reporting::term::termcolor::ColorChoice;
use fathom::driver::TermWidth;
use fathom::driver::{diagnostic, TermWidth};
use structopt::StructOpt;

mod commands;
Expand All @@ -17,6 +17,16 @@ pub struct Options {
parse(try_from_str = parse_color_choice),
)]
color: ColorChoice,
/// How diagnostics are rendered
#[structopt(
long = "diagnostic-style",
name = "STYLE",
default_value = "human",
case_insensitive = true,
possible_values = &["human", "json"],
parse(try_from_str = parse_diagnostic_style),
)]
diagnostic_style: diagnostic::Style,
/// The width of terminal to use when wrapping diagnostic output
#[structopt(
long = "term-width",
Expand All @@ -27,6 +37,7 @@ pub struct Options {
parse(try_from_str = parse_term_width),
)]
term_width: TermWidth,

#[structopt(subcommand)]
command: Command,
}
Expand Down Expand Up @@ -59,6 +70,14 @@ fn parse_color_choice(src: &str) -> Result<ColorChoice, &'static str> {
}
}

fn parse_diagnostic_style(src: &str) -> Result<diagnostic::Style, &'static str> {
match () {
() if src.eq_ignore_ascii_case("human") => Ok(diagnostic::Style::Human),
() if src.eq_ignore_ascii_case("json") => Ok(diagnostic::Style::Json),
() => Err("valid values: human, json"),
}
}

fn parse_term_width(src: &str) -> Result<TermWidth, &'static str> {
match () {
() if src.eq_ignore_ascii_case("auto") => Ok(TermWidth::Auto),
Expand Down
2 changes: 2 additions & 0 deletions fathom/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ num-bigint = "0.4"
num-traits = "0.2"
pretty = "0.10"
termsize = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[dev-dependencies]
proptest = "1"
Expand Down
57 changes: 35 additions & 22 deletions fathom/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,22 @@ use crate::lang::{core, surface, FileId};
use crate::pass::{core_to_pretty, surface_to_core, surface_to_doc, surface_to_pretty};
use crate::reporting::Message;

pub mod diagnostic;

lazy_static::lazy_static! {
static ref GLOBALS: core::Globals = core::Globals::default();
}

/// The width of the terminal to use when printing diagnostics.
#[derive(Debug, Copy, Clone)]
pub enum TermWidth {
/// Detect wrapping from the terminal width
Auto,
/// No wrapping
None,
/// Explicit terminal width
Explicit(u16),
}

impl TermWidth {
fn compute(self) -> usize {
match self {
TermWidth::Auto => termsize::get().map_or(usize::MAX, |size| usize::from(size.cols)),
TermWidth::None => usize::MAX,
TermWidth::Explicit(count) => usize::from(count),
}
}
}

/// Fathom compiler driver
pub struct Driver {
validate_core: bool,

emit_core: bool,
emit_width: TermWidth,
emit_writer: Box<dyn WriteColor>,

codespan_config: codespan_reporting::term::Config,
diagnostic_style: diagnostic::Style,
diagnostic_writer: Box<dyn WriteColor>,

files: SimpleFiles<String, String>,
Expand All @@ -57,10 +41,13 @@ impl Driver {
pub fn new() -> Driver {
Driver {
validate_core: false,

emit_core: false,
emit_width: TermWidth::Auto,
emit_writer: Box::new(BufferedStandardStream::stdout(ColorChoice::Auto)),

codespan_config: codespan_reporting::term::Config::default(),
diagnostic_style: diagnostic::Style::Human,
diagnostic_writer: Box::new(BufferedStandardStream::stderr(ColorChoice::Auto)),

files: SimpleFiles::new(),
Expand All @@ -81,7 +68,7 @@ impl Driver {
self.validate_core = validate_core;
}

/// Set the width to use for printing diagnostics.
/// Set the width to use when emitting data and intermediate languages.
pub fn set_emit_width(&mut self, emit_width: TermWidth) {
self.emit_width = emit_width;
}
Expand All @@ -91,6 +78,11 @@ impl Driver {
self.emit_writer = Box::new(stream) as Box<dyn WriteColor>;
}

/// Set the diagnostic style to use when rendering diagnostics.
pub fn set_diagnostic_style(&mut self, diagnostic_style: diagnostic::Style) {
self.diagnostic_style = diagnostic_style;
}

/// Set the writer to use when rendering diagnostics
pub fn set_diagnostic_writer(&mut self, stream: impl 'static + WriteColor) {
self.diagnostic_writer = Box::new(stream) as Box<dyn WriteColor>;
Expand Down Expand Up @@ -249,6 +241,27 @@ impl Driver {
}
}

/// The width of the terminal to use when printing diagnostics.
#[derive(Debug, Copy, Clone)]
pub enum TermWidth {
/// Detect wrapping from the terminal width
Auto,
/// No wrapping
None,
/// Explicit terminal width
Explicit(u16),
}

impl TermWidth {
fn compute(self) -> usize {
match self {
TermWidth::Auto => termsize::get().map_or(usize::MAX, |size| usize::from(size.cols)),
TermWidth::None => usize::MAX,
TermWidth::Explicit(count) => usize::from(count),
}
}
}

/// An error produced while reading binary data.
#[derive(Debug)]
pub enum ReadDataError {
Expand Down
58 changes: 58 additions & 0 deletions fathom/src/driver/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! This stuff would probably be better off in `codespan_reporting`...

use codespan_reporting::diagnostic::{Diagnostic, LabelStyle, Severity};
use codespan_reporting::files::{self, Files};

pub mod api;

#[derive(Debug, Copy, Clone)]
pub enum Style {
Human,
Json,
}

pub fn to_json_diagnostic<'files, F: Files<'files>>(
files: &'files F,
diagnostic: &Diagnostic<F::FileId>,
) -> Result<api::Diagnostic, files::Error> {
let labels = diagnostic
.labels
.iter()
.map(|label| {
let start = files.location(label.file_id, label.range.start)?;
let end = files.location(label.file_id, label.range.end)?;

Ok(api::Label {
file: files.name(label.file_id)?.to_string(),
start: api::Location {
line: start.line_number,
column: start.column_number,
byte: label.range.start,
},
end: api::Location {
line: end.line_number,
column: end.column_number,
byte: label.range.end,
},
style: match label.style {
LabelStyle::Primary => api::LabelStyle::Primary,
LabelStyle::Secondary => api::LabelStyle::Secondary,
},
message: label.message.clone(),
})
})
.collect::<Result<_, files::Error>>()?;

Ok(api::Diagnostic {
message: diagnostic.message.clone(),
severity: match diagnostic.severity {
Severity::Bug => api::Severity::Bug,
Severity::Error => api::Severity::Error,
Severity::Warning => api::Severity::Warning,
Severity::Note => api::Severity::Note,
Severity::Help => api::Severity::Help,
},
labels,
notes: diagnostic.notes.clone(),
})
}
47 changes: 47 additions & 0 deletions fathom/src/driver/diagnostic/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Serialized diagnostic output, for tooling and screenreaders, etc.

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Severity {
Bug,
Error,
Warning,
Note,
Help,
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Diagnostic {
pub message: String,
pub severity: Severity,
pub labels: Vec<Label>,
pub notes: Vec<String>,
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LabelStyle {
Primary,
Secondary,
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Label {
pub file: String,
pub start: Location,
pub end: Location,
pub style: LabelStyle,
pub message: String,
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Location {
pub line: usize,
pub column: usize,
pub byte: usize,
}