Skip to content

Commit

Permalink
Add --error-format flag to serialize err diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
yannham committed Dec 18, 2023
1 parent e1113af commit 43d2dad
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 99 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ assert_cmd = "2.0.11"
assert_matches = "1.5.0"
clap = "4.3"
clap_complete = "4.3.2"
codespan = "0.11"
codespan = { version = "0.11", features = ["serialization"] }
codespan-lsp = "0.11"
codespan-reporting = "0.11"
codespan-reporting = { version = "0.11", features = ["serialization"] }
comrak = "0.17.0"
criterion = "0.4"
csv = "1"
Expand Down
6 changes: 6 additions & 0 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::{
pprint_ast::PprintAstCommand, query::QueryCommand, typecheck::TypecheckCommand,
};

use nickel_lang_core::error::report::ErrorFormat;

#[cfg(feature = "repl")]
use crate::repl::ReplCommand;

Expand Down Expand Up @@ -47,6 +49,10 @@ pub struct GlobalOptions {
#[arg(long, global = true, value_enum, default_value_t)]
pub color: clap::ColorChoice,

/// Output error messages in a specific format.
#[arg(long, global = true, value_enum, default_value_t)]
pub error_format: ErrorFormat,

#[cfg(feature = "metrics")]
/// Print all recorded metrics at the very end of the program
#[arg(long, global = true, default_value_t = false)]
Expand Down
9 changes: 5 additions & 4 deletions cli/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Error handling for the CLI.

use nickel_lang_core::{
error::{Diagnostic, FileId, Files, IntoDiagnostics, ParseError},
error::{report::ErrorFormat, Diagnostic, FileId, Files, IntoDiagnostics, ParseError},
eval::cache::lazy::CBNCache,
program::{FieldOverride, FieldPath, Program},
};
Expand Down Expand Up @@ -215,9 +215,10 @@ impl<T> ResultErrorExt<T> for Result<T, nickel_lang_core::error::Error> {
}

impl Error {
pub fn report(self) {
/// Report this error on the standard error stream.
pub fn report(self, format: ErrorFormat) {
match self {
Error::Program { mut program, error } => program.report(error),
Error::Program { mut program, error } => program.report(error, format),
Error::Io { error } => {
eprintln!("{error}")
}
Expand All @@ -233,7 +234,7 @@ impl Error {
}
#[cfg(feature = "format")]
Error::Format { error } => eprintln!("{error}"),
Error::CliUsage { error, mut program } => program.report(error),
Error::CliUsage { error, mut program } => program.report(error, format),
Error::CustomizeInfoPrinted => {
// Nothing to do, the caller should simply exit.
}
Expand Down
3 changes: 2 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fn main() -> ExitCode {

let opts = <Options as clap::Parser>::parse();

let error_format = opts.global.error_format;
#[cfg(feature = "metrics")]
let report_metrics = opts.global.metrics;

Expand Down Expand Up @@ -61,7 +62,7 @@ fn main() -> ExitCode {
// user's point of view.
Ok(()) | Err(error::Error::CustomizeInfoPrinted) => ExitCode::SUCCESS,
Err(error) => {
error.report();
error.report(error_format);
ExitCode::FAILURE
}
}
Expand Down
2 changes: 1 addition & 1 deletion cli/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl QueryCommand {
let mut program = self.inputs.prepare(&global)?;

if self.inputs.customize_mode.field().is_none() {
program.report(Warning::EmptyQueryPath)
program.report(Warning::EmptyQueryPath, global.error_format);
}

let found = program
Expand Down
70 changes: 1 addition & 69 deletions core/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use crate::{
typ::{Type, TypeF, VarKindDiscriminant},
};

pub mod report;
pub mod suggest;

/// A general error occurring during either parsing or evaluation.
Expand Down Expand Up @@ -2282,72 +2283,3 @@ impl IntoDiagnostics<FileId> for ReplError {
}
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ColorOpt(pub(crate) clap::ColorChoice);

impl From<clap::ColorChoice> for ColorOpt {
fn from(color_choice: clap::ColorChoice) -> Self {
Self(color_choice)
}
}

impl From<ColorOpt> for ColorChoice {
fn from(c: ColorOpt) -> Self {
use std::io::{stdout, IsTerminal};

match c.0 {
clap::ColorChoice::Auto => {
if stdout().is_terminal() {
ColorChoice::Auto
} else {
ColorChoice::Never
}
}
clap::ColorChoice::Always => ColorChoice::Always,
clap::ColorChoice::Never => ColorChoice::Never,
}
}
}

impl Default for ColorOpt {
fn default() -> Self {
Self(clap::ColorChoice::Auto)
}
}

/// Pretty-print an error on stderr.
///
/// # Arguments
///
/// - `cache` is the file cache used during the evaluation, which is required by the reporting
/// infrastructure to point at specific locations and print snippets when needed.
pub fn report<E: IntoDiagnostics<FileId>>(cache: &mut Cache, error: E, color_opt: ColorOpt) {
let stdlib_ids = cache.get_all_stdlib_modules_file_id();
report_with(
&mut StandardStream::stderr(color_opt.into()).lock(),
cache.files_mut(),
stdlib_ids.as_ref(),
error,
)
}

/// Report an error on `stderr`, provided a file database and a list of stdlib file ids.
pub fn report_with<E: IntoDiagnostics<FileId>>(
writer: &mut dyn WriteColor,
files: &mut Files<String>,
stdlib_ids: Option<&Vec<FileId>>,
error: E,
) {
let config = codespan_reporting::term::Config::default();
let diagnostics = error.into_diagnostics(files, stdlib_ids);

let result = diagnostics
.iter()
.try_for_each(|d| codespan_reporting::term::emit(writer, &config, files, d));

match result {
Ok(()) => (),
Err(err) => panic!("error::report_with(): could not print an error on stderr: {err}"),
};
}
114 changes: 114 additions & 0 deletions core/src/error/report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//! Error diagnostics reporting and serialization.
use super::*;

/// Serializable wrapper type to export diagnostics with a top-level attribute.
#[derive(serde::Serialize)]
pub struct DiagnosticsWrapper {
pub diagnostics: Vec<Diagnostic<FileId>>,
}

impl From<Vec<Diagnostic<FileId>>> for DiagnosticsWrapper {
fn from(diagnostics: Vec<Diagnostic<FileId>>) -> Self {
Self { diagnostics }
}
}

/// Available export formats for error diagnostics.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, clap::ValueEnum)]
pub enum ErrorFormat {
#[default]
Text,
Json,
Yaml,
Toml,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ColorOpt(pub(crate) clap::ColorChoice);

impl From<clap::ColorChoice> for ColorOpt {
fn from(color_choice: clap::ColorChoice) -> Self {
Self(color_choice)
}
}

impl From<ColorOpt> for ColorChoice {
fn from(c: ColorOpt) -> Self {
use std::io::{stdout, IsTerminal};

match c.0 {
clap::ColorChoice::Auto => {
if stdout().is_terminal() {
ColorChoice::Auto
} else {
ColorChoice::Never
}
}
clap::ColorChoice::Always => ColorChoice::Always,
clap::ColorChoice::Never => ColorChoice::Never,
}
}
}

impl Default for ColorOpt {
fn default() -> Self {
Self(clap::ColorChoice::Auto)
}
}

/// Pretty-print an error on stderr.
///
/// # Arguments
///
/// - `cache` is the file cache used during the evaluation, which is required by the reporting
/// infrastructure to point at specific locations and print snippets when needed.
pub fn report<E: IntoDiagnostics<FileId>>(
cache: &mut Cache,
error: E,
format: ErrorFormat,
color_opt: ColorOpt,
) {
let stdlib_ids = cache.get_all_stdlib_modules_file_id();
report_with(
&mut StandardStream::stderr(color_opt.into()).lock(),
cache.files_mut(),
stdlib_ids.as_ref(),
error,
format,
)
}

/// Report an error on `stderr`, provided a file database and a list of stdlib file ids.
pub fn report_with<E: IntoDiagnostics<FileId>>(
writer: &mut dyn WriteColor,
files: &mut Files<String>,
stdlib_ids: Option<&Vec<FileId>>,
error: E,
format: ErrorFormat,
) {
let config = codespan_reporting::term::Config::default();
let diagnostics = error.into_diagnostics(files, stdlib_ids);
let stderr = std::io::stderr();

let result = match format {
ErrorFormat::Text => diagnostics.iter().try_for_each(|d| {
codespan_reporting::term::emit(writer, &config, files, d).map_err(|err| err.to_string())
}),
ErrorFormat::Json => serde_json::to_writer(stderr, &DiagnosticsWrapper::from(diagnostics))
.and_then(|_| {
eprintln!();
Ok(())
})
.map_err(|err| err.to_string()),
ErrorFormat::Yaml => serde_yaml::to_writer(stderr, &DiagnosticsWrapper::from(diagnostics))
.map_err(|err| err.to_string()),
ErrorFormat::Toml => toml::to_string(&DiagnosticsWrapper::from(diagnostics))
.map(|repr| eprint!("{}", repr))
.map_err(|err| err.to_string()),
};

match result {
Ok(()) => (),
Err(err) => panic!("error::report_with(): could not print an error on stderr: {err}"),
};
}
9 changes: 6 additions & 3 deletions core/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
//! Each such value is added to the initial environment before the evaluation of the program.
use crate::{
cache::*,
error::{report, ColorOpt, Error, EvalError, IOError, IntoDiagnostics, ParseError},
error::{
report::{report, ColorOpt, ErrorFormat},
Error, EvalError, IOError, IntoDiagnostics, ParseError,
},
eval::{cache::Cache as EvalCache, Closure, VirtualMachine},
identifier::LocIdent,
label::Label,
Expand Down Expand Up @@ -454,11 +457,11 @@ impl<EC: EvalCache> Program<EC> {
}

/// Wrapper for [`report`].
pub fn report<E>(&mut self, error: E)
pub fn report<E>(&mut self, error: E, format: ErrorFormat)
where
E: IntoDiagnostics<FileId>,
{
report(self.vm.import_resolver_mut(), error, self.color_opt)
report(self.vm.import_resolver_mut(), error, format, self.color_opt)
}

/// Build an error report as a string and return it.
Expand Down
9 changes: 8 additions & 1 deletion core/src/repl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
//! jupyter-kernel (which is not exactly user-facing, but still manages input/output and
//! formatting), etc.
use crate::cache::{Cache, Envs, ErrorTolerance, SourcePath};
use crate::error::{Error, EvalError, IOError, ParseError, ParseErrors, ReplError};
use crate::error::{
report::{self, ColorOpt, ErrorFormat},
Error, EvalError, IOError, IntoDiagnostics, ParseError, ParseErrors, ReplError,
};
use crate::eval::cache::Cache as EvalCache;
use crate::eval::{Closure, VirtualMachine};
use crate::identifier::LocIdent;
Expand Down Expand Up @@ -208,6 +211,10 @@ impl<EC: EvalCache> ReplImpl<EC> {
}
}
}

fn report(&mut self, err: impl IntoDiagnostics<FileId>, color_opt: ColorOpt) {
report::report(self.cache_mut(), err, ErrorFormat::Text, color_opt);
}
}

impl<EC: EvalCache> Repl for ReplImpl<EC> {
Expand Down
Loading

0 comments on commit 43d2dad

Please sign in to comment.