forked from extern/nushell
replace codespan-reporting with miette 3.0
This commit is contained in:
@ -1,414 +1,53 @@
|
||||
use core::ops::Range;
|
||||
use miette::{LabeledSpan, MietteHandler, ReportHandler, Severity, SourceCode};
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use thiserror::Error;
|
||||
|
||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
|
||||
use nu_parser::ParseError;
|
||||
use nu_protocol::{engine::StateWorkingSet, ShellError, Span};
|
||||
/// This error exists so that we can defer SourceCode handling. It simply
|
||||
/// forwards most methods, except for `.source_code()`, which we provide.
|
||||
#[derive(Error)]
|
||||
#[error("{0}")]
|
||||
struct CliError<'src>(
|
||||
&'src (dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
&'src StateWorkingSet<'src>,
|
||||
);
|
||||
|
||||
fn convert_span_to_diag(
|
||||
working_set: &StateWorkingSet,
|
||||
span: &Span,
|
||||
) -> Result<(usize, Range<usize>), Box<dyn std::error::Error>> {
|
||||
for (file_id, (_, start, end)) in working_set.files().enumerate() {
|
||||
if span.start >= *start && span.end <= *end {
|
||||
let new_start = span.start - start;
|
||||
let new_end = span.end - start;
|
||||
impl std::fmt::Debug for CliError<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
MietteHandler::default().debug(self, f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
return Ok((file_id, new_start..new_end));
|
||||
}
|
||||
impl<'src> miette::Diagnostic for CliError<'src> {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
self.0.code()
|
||||
}
|
||||
|
||||
if span.start == working_set.next_span_start() {
|
||||
// We're trying to highlight the space after the end
|
||||
if let Some((file_id, (_, _, end))) = working_set.files().enumerate().last() {
|
||||
return Ok((file_id, *end..(*end + 1)));
|
||||
}
|
||||
fn severity(&self) -> Option<Severity> {
|
||||
self.0.severity()
|
||||
}
|
||||
|
||||
panic!(
|
||||
"internal error: can't find span in parser state: {:?}",
|
||||
span
|
||||
)
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
self.0.help()
|
||||
}
|
||||
|
||||
fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
self.0.url()
|
||||
}
|
||||
|
||||
fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
|
||||
self.0.labels()
|
||||
}
|
||||
|
||||
// Finally, we redirect the source_code method to our own source.
|
||||
fn source_code(&self) -> Option<&dyn SourceCode> {
|
||||
Some(&self.1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_parsing_error(
|
||||
pub fn report_error(
|
||||
working_set: &StateWorkingSet,
|
||||
error: &ParseError,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let writer = StandardStream::stderr(ColorChoice::Always);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
|
||||
let diagnostic =
|
||||
match error {
|
||||
ParseError::Mismatch(expected, found, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Type mismatch during operation")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("expected {}, found {}", expected, found))])
|
||||
}
|
||||
ParseError::ExtraTokens(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Extra tokens in code")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("extra tokens")
|
||||
])
|
||||
}
|
||||
ParseError::ExtraPositional(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Extra positional argument")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("extra positional argument")])
|
||||
}
|
||||
ParseError::UnexpectedEof(s, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unexpected end of code")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("expected {}", s))])
|
||||
}
|
||||
ParseError::Unclosed(delim, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unclosed delimiter")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("unclosed {}", delim))])
|
||||
}
|
||||
ParseError::UnknownStatement(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unknown statement")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("unknown statement")
|
||||
])
|
||||
}
|
||||
ParseError::MultipleRestParams(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Multiple rest params")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("multiple rest params")])
|
||||
}
|
||||
ParseError::VariableNotFound(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Variable not found")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("variable not found")
|
||||
])
|
||||
}
|
||||
ParseError::UnknownCommand(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unknown command")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("unknown command")
|
||||
])
|
||||
}
|
||||
ParseError::UnknownFlag(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unknown flag")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("unknown flag")
|
||||
])
|
||||
}
|
||||
ParseError::UnknownType(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unknown type")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("unknown type")
|
||||
])
|
||||
}
|
||||
ParseError::MissingFlagParam(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Missing flag param")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("flag missing parameter")])
|
||||
}
|
||||
ParseError::ShortFlagBatchCantTakeArg(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Batches of short flags can't take arguments")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("short flag batches can't take args")])
|
||||
}
|
||||
ParseError::KeywordMissingArgument(name, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message(format!("Missing argument to {}", name))
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("missing value that follows {}", name))])
|
||||
}
|
||||
ParseError::MissingPositional(name, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Missing required positional arg")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("missing {}", name))])
|
||||
}
|
||||
ParseError::MissingType(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Missing type")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("expected type")
|
||||
])
|
||||
}
|
||||
ParseError::MissingColumns(count, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Missing columns")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range).with_message(
|
||||
format!(
|
||||
"expected {} column{}",
|
||||
count,
|
||||
if *count == 1 { "" } else { "s" }
|
||||
),
|
||||
)])
|
||||
}
|
||||
ParseError::ExtraColumns(count, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Extra columns")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range).with_message(
|
||||
format!(
|
||||
"expected {} column{}",
|
||||
count,
|
||||
if *count == 1 { "" } else { "s" }
|
||||
),
|
||||
)])
|
||||
}
|
||||
ParseError::TypeMismatch(expected, found, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Type mismatch")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("expected {:?}, found {:?}", expected, found))])
|
||||
}
|
||||
ParseError::MissingRequiredFlag(name, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Missing required flag")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("missing required flag {}", name))])
|
||||
}
|
||||
ParseError::IncompleteMathExpression(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Incomplete math expresssion")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("incomplete math expression")])
|
||||
}
|
||||
ParseError::UnknownState(name, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unknown state")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message(name.to_string())
|
||||
])
|
||||
}
|
||||
ParseError::NonUtf8(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Non-UTF8 code")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("non-UTF8 code")
|
||||
])
|
||||
}
|
||||
ParseError::Expected(expected, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Parse mismatch during operation")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("expected {}", expected))])
|
||||
}
|
||||
ParseError::UnsupportedOperation(op_span, lhs_span, lhs_ty, rhs_span, rhs_ty) => {
|
||||
let (lhs_file_id, lhs_range) = convert_span_to_diag(working_set, lhs_span)?;
|
||||
let (rhs_file_id, rhs_range) = convert_span_to_diag(working_set, rhs_span)?;
|
||||
let (op_file_id, op_range) = convert_span_to_diag(working_set, op_span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unsupported operation")
|
||||
.with_labels(vec![
|
||||
Label::primary(op_file_id, op_range)
|
||||
.with_message("doesn't support these values"),
|
||||
Label::secondary(lhs_file_id, lhs_range).with_message(lhs_ty.to_string()),
|
||||
Label::secondary(rhs_file_id, rhs_range).with_message(rhs_ty.to_string()),
|
||||
])
|
||||
}
|
||||
ParseError::ExpectedKeyword(expected, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Expected keyword")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("expected {}", expected))])
|
||||
}
|
||||
ParseError::IncompleteParser(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Parser incomplete")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("parser support missing for this expression")])
|
||||
}
|
||||
ParseError::RestNeedsName(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Rest parameter needs a name")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("needs a parameter name")])
|
||||
}
|
||||
ParseError::AssignmentMismatch(msg, label, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message(msg)
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message(label)
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
// println!("DIAG");
|
||||
// println!("{:?}", diagnostic);
|
||||
codespan_reporting::term::emit(&mut writer.lock(), &config, working_set, &diagnostic)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn report_shell_error(
|
||||
working_set: &StateWorkingSet,
|
||||
error: &ShellError,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let writer = StandardStream::stderr(ColorChoice::Always);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
|
||||
let diagnostic =
|
||||
match error {
|
||||
ShellError::OperatorMismatch {
|
||||
op_span,
|
||||
lhs_ty,
|
||||
lhs_span,
|
||||
rhs_ty,
|
||||
rhs_span,
|
||||
} => {
|
||||
let (lhs_file_id, lhs_range) = convert_span_to_diag(working_set, lhs_span)?;
|
||||
let (rhs_file_id, rhs_range) = convert_span_to_diag(working_set, rhs_span)?;
|
||||
let (op_file_id, op_range) = convert_span_to_diag(working_set, op_span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Type mismatch during operation")
|
||||
.with_labels(vec![
|
||||
Label::primary(op_file_id, op_range)
|
||||
.with_message("type mismatch for operator"),
|
||||
Label::secondary(lhs_file_id, lhs_range).with_message(lhs_ty.to_string()),
|
||||
Label::secondary(rhs_file_id, rhs_range).with_message(rhs_ty.to_string()),
|
||||
])
|
||||
}
|
||||
ShellError::UnsupportedOperator(op, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message(format!("Unsupported operator: {}", op))
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("unsupported operator")])
|
||||
}
|
||||
ShellError::UnknownOperator(op, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message(format!("Unsupported operator: {}", op))
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("unsupported operator")])
|
||||
}
|
||||
ShellError::ExternalNotSupported(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("External commands not yet supported")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("external not supported")])
|
||||
}
|
||||
ShellError::InternalError(s) => {
|
||||
Diagnostic::error().with_message(format!("Internal error: {}", s))
|
||||
}
|
||||
ShellError::VariableNotFoundAtRuntime(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Variable not found")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("variable not found")
|
||||
])
|
||||
}
|
||||
ShellError::CantConvert(s, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message(format!("Can't convert to {}", s))
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("can't convert to {}", s))])
|
||||
}
|
||||
ShellError::CannotCreateRange(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Can't convert range to countable values")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("can't convert to countable values")])
|
||||
}
|
||||
ShellError::DivisionByZero(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
|
||||
Diagnostic::error()
|
||||
.with_message("Division by zero")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("division by zero")
|
||||
])
|
||||
}
|
||||
ShellError::AccessBeyondEnd(len, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
|
||||
Diagnostic::error()
|
||||
.with_message("Row number too large")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("row number too large (max: {})", *len))])
|
||||
}
|
||||
ShellError::AccessBeyondEndOfStream(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
|
||||
Diagnostic::error()
|
||||
.with_message("Row number too large")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("row number too large")])
|
||||
}
|
||||
ShellError::IncompatiblePathAccess(name, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
|
||||
Diagnostic::error()
|
||||
.with_message("Data cannot be accessed with a cell path")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("{} doesn't support cell paths", name))])
|
||||
}
|
||||
ShellError::CantFindColumn(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
|
||||
//FIXME: add "did you mean"
|
||||
Diagnostic::error()
|
||||
.with_message("Cannot find column")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("cannot find column")
|
||||
])
|
||||
}
|
||||
ShellError::ExternalCommand(error, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
|
||||
Diagnostic::error()
|
||||
.with_message("External command")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message(error.to_string())
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
// println!("DIAG");
|
||||
// println!("{:?}", diagnostic);
|
||||
codespan_reporting::term::emit(&mut writer.lock(), &config, working_set, &diagnostic)?;
|
||||
|
||||
Ok(())
|
||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
) {
|
||||
eprintln!("Error: {:?}", CliError(error, working_set));
|
||||
}
|
||||
|
@ -3,5 +3,5 @@ mod errors;
|
||||
mod syntax_highlight;
|
||||
|
||||
pub use completions::NuCompleter;
|
||||
pub use errors::{report_parsing_error, report_shell_error};
|
||||
pub use errors::report_error;
|
||||
pub use syntax_highlight::NuHighlighter;
|
||||
|
Reference in New Issue
Block a user