replace codespan-reporting with miette 3.0

This commit is contained in:
Kat Marchán 2021-09-20 14:37:26 -07:00
parent cbe85cbeaf
commit a1d6cefdf8
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
14 changed files with 514 additions and 578 deletions

219
Cargo.lock generated
View File

@ -34,6 +34,17 @@ dependencies = [
"wait-timeout",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
@ -77,13 +88,12 @@ dependencies = [
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
name = "ci_info"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
checksum = "1f0e2864372242f01b92c1b882a904f6fb8b57f16e81e148a35b6368b1ea7323"
dependencies = [
"termcolor",
"unicode-width",
"envmnt",
]
[[package]]
@ -140,6 +150,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "dunce"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
[[package]]
name = "either"
version = "1.6.1"
@ -151,7 +167,7 @@ name = "engine-q"
version = "0.1.0"
dependencies = [
"assert_cmd",
"codespan-reporting",
"miette",
"nu-cli",
"nu-command",
"nu-engine",
@ -163,6 +179,25 @@ dependencies = [
"tempfile",
]
[[package]]
name = "envmnt"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f96dd862f12fac698dec3932dff0e6fb34bffeb5515ae5932d620cfe076571e"
dependencies = [
"fsio",
"indexmap",
]
[[package]]
name = "fsio"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09e87827efaf94c7a44b562ff57de06930712fe21b530c3797cdede26e6377eb"
dependencies = [
"dunce",
]
[[package]]
name = "getrandom"
version = "0.2.3"
@ -180,6 +215,31 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.10"
@ -234,6 +294,38 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "miette"
version = "3.0.0-alpha.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043a986fa0bf30fe00f6720e5c298ed1d67fe30ae77659744cbac206e8d2554c"
dependencies = [
"atty",
"ci_info",
"itertools",
"miette-derive",
"once_cell",
"owo-colors",
"supports-color",
"supports-hyperlinks",
"supports-unicode",
"term_size",
"textwrap",
"thiserror",
"unicode-width",
]
[[package]]
name = "miette-derive"
version = "3.0.0-alpha.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3027fb091be28062da879441b2ab8f43d0013d7cde5fcc3a48fb9da7e22a01"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "mio"
version = "0.7.13"
@ -280,12 +372,13 @@ dependencies = [
name = "nu-cli"
version = "0.1.0"
dependencies = [
"codespan-reporting",
"miette",
"nu-ansi-term",
"nu-engine",
"nu-parser",
"nu-protocol",
"reedline",
"thiserror",
]
[[package]]
@ -310,15 +403,17 @@ dependencies = [
name = "nu-parser"
version = "0.1.0"
dependencies = [
"codespan-reporting",
"miette",
"nu-protocol",
"thiserror",
]
[[package]]
name = "nu-protocol"
version = "0.1.0"
dependencies = [
"codespan-reporting",
"miette",
"thiserror",
]
[[package]]
@ -349,6 +444,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "output_vt100"
version = "0.1.2"
@ -364,6 +465,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owo-colors"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2fe43bf372b08cc9ccee5144715db59c79ab00168bbe4cf0d274dc0d5f64d7f"
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -608,6 +715,42 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "supports-color"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40bc06147993f379a124cc6373ad4022f5d9fd4a80019217c773f81a38e9023c"
dependencies = [
"atty",
"ci_info",
"lazy_static",
"regex",
]
[[package]]
name = "supports-hyperlinks"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406"
dependencies = [
"atty",
]
[[package]]
name = "supports-unicode"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5fa283a620b255940913bd962cda2e6320e3799041f96ac0d7191ff2b4622f"
dependencies = [
"atty",
]
[[package]]
name = "syn"
version = "1.0.76"
@ -634,12 +777,44 @@ dependencies = [
]
[[package]]
name = "termcolor"
version = "1.1.2"
name = "term_size"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
dependencies = [
"winapi-util",
"libc",
"winapi",
]
[[package]]
name = "textwrap"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
@ -659,6 +834,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
[[package]]
name = "unicode-linebreak"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
dependencies = [
"regex",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
@ -708,15 +892,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View File

@ -10,14 +10,13 @@ members = ["crates/nu-cli", "crates/nu-engine", "crates/nu-parser", "crates/nu-c
[dependencies]
reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" }
codespan-reporting = "0.11.1"
nu-cli = { path="./crates/nu-cli" }
nu-command = { path="./crates/nu-command" }
nu-engine = { path="./crates/nu-engine" }
nu-parser = { path="./crates/nu-parser" }
nu-protocol = { path = "./crates/nu-protocol" }
nu-table = { path = "./crates/nu-table" }
miette = { version = "3.0.0-alpha.0" }
# mimalloc = { version = "*", default-features = false }
[dev-dependencies]

View File

@ -7,6 +7,7 @@ edition = "2018"
nu-engine = { path = "../nu-engine" }
nu-parser = { path = "../nu-parser" }
nu-protocol = { path = "../nu-protocol" }
codespan-reporting = "0.11.1"
miette = { version = "3.0.0-alpha.0", features = ["fancy"] }
thiserror = "1.0.29"
nu-ansi-term = "0.36.0"
reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" }

View File

@ -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};
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;
return Ok((file_id, new_start..new_end));
}
}
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)));
}
}
panic!(
"internal error: can't find span in parser state: {:?}",
span
)
}
pub fn report_parsing_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)?;
/// 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>,
);
impl std::fmt::Debug for CliError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
MietteHandler::default().debug(self, f)?;
Ok(())
}
}
pub fn report_shell_error(
impl<'src> miette::Diagnostic for CliError<'src> {
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.0.code()
}
fn severity(&self) -> Option<Severity> {
self.0.severity()
}
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_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));
}

View File

@ -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;

View File

@ -4,5 +4,6 @@ version = "0.1.0"
edition = "2018"
[dependencies]
codespan-reporting = "0.11.1"
miette = { version = "3.0.0-alpha.0" }
thiserror = "1.0.29"
nu-protocol = { path = "../nu-protocol"}

View File

@ -1,34 +1,150 @@
use miette::Diagnostic;
use nu_protocol::{Span, Type};
use thiserror::Error;
#[derive(Debug)]
#[derive(Clone, Debug, Error, Diagnostic)]
pub enum ParseError {
ExtraTokens(Span),
ExtraPositional(Span),
UnexpectedEof(String, Span),
Unclosed(String, Span),
UnknownStatement(Span),
Expected(String, Span),
Mismatch(String, String, Span), // expected, found, span
UnsupportedOperation(Span, Span, Type, Span, Type),
ExpectedKeyword(String, Span),
MultipleRestParams(Span),
VariableNotFound(Span),
UnknownCommand(Span),
NonUtf8(Span),
UnknownFlag(Span),
UnknownType(Span),
MissingFlagParam(Span),
ShortFlagBatchCantTakeArg(Span),
MissingPositional(String, Span),
KeywordMissingArgument(String, Span),
MissingType(Span),
TypeMismatch(Type, Type, Span), // expected, found, span
MissingRequiredFlag(String, Span),
IncompleteMathExpression(Span),
UnknownState(String, Span),
IncompleteParser(Span),
RestNeedsName(Span),
ExtraColumns(usize, Span),
MissingColumns(usize, Span),
AssignmentMismatch(String, String, Span),
/// The parser encountered unexpected tokens, when the code should have
/// finished. You should remove these or finish adding what you intended
/// to add.
#[error("Extra tokens in code.")]
#[diagnostic(
code(nu::parser::extra_tokens),
url(docsrs),
help("Try removing them.")
)]
ExtraTokens(#[label = "extra tokens"] Span),
#[error("Extra positional argument.")]
#[diagnostic(code(nu::parser::extra_positional), url(docsrs))]
ExtraPositional(#[label = "extra positional argument"] Span),
#[error("Unexpected end of code.")]
#[diagnostic(code(nu::parser::unexpected_eof), url(docsrs))]
UnexpectedEof(String, #[label("expected {0}")] Span),
#[error("Unclosed delimiter.")]
#[diagnostic(code(nu::parser::unclosed_delimiter), url(docsrs))]
Unclosed(String, #[label("unclosed {0}")] Span),
#[error("Unknown statement.")]
#[diagnostic(code(nu::parser::unknown_statement), url(docsrs))]
UnknownStatement(#[label("unknown statement")] Span),
#[error("Parse mismatch during operation.")]
#[diagnostic(code(nu::parser::parse_mismatch), url(docsrs))]
Expected(String, #[label("expected {0}")] Span),
#[error("Type mismatch during operation.")]
#[diagnostic(code(nu::parser::type_mismatch), url(docsrs))]
Mismatch(String, String, #[label("expected {0}, found {1}")] Span), // expected, found, span
#[error("Unsupported operation.")]
#[diagnostic(
code(nu::parser::unsupported_operation),
url(docsrs),
help("Change {2} or {4} to be the right types and try again.")
)]
UnsupportedOperation(
#[label = "doesn't support these values."] Span,
#[label("{2}")] Span,
Type,
#[label("{4}")] Span,
Type,
),
#[error("Expected keyword.")]
#[diagnostic(code(nu::parser::expected_keyword), url(docsrs))]
ExpectedKeyword(String, #[label("expected {0}")] Span),
#[error("Multiple rest params.")]
#[diagnostic(code(nu::parser::multiple_rest_params), url(docsrs))]
MultipleRestParams(#[label = "multiple rest params"] Span),
#[error("Variable not found.")]
#[diagnostic(code(nu::parser::variable_not_found), url(docsrs))]
VariableNotFound(#[label = "variable not found"] Span),
#[error("Unknown command.")]
#[diagnostic(
code(nu::parser::unknown_command),
url(docsrs),
// TODO: actual suggestions
// help("Did you mean `foo`?")
)]
UnknownCommand(#[label = "unknown command"] Span),
#[error("Non-UTF8 code.")]
#[diagnostic(code(nu::parser::non_utf8), url(docsrs))]
NonUtf8(#[label = "non-UTF8 code"] Span),
#[error("Unknown flag.")]
#[diagnostic(code(nu::parser::unknown_flag), url(docsrs))]
UnknownFlag(#[label = "unknown flag"] Span),
#[error("Unknown type.")]
#[diagnostic(code(nu::parser::unknown_type), url(docsrs))]
UnknownType(#[label = "unknown type"] Span),
#[error("Missing flag param.")]
#[diagnostic(code(nu::parser::missing_flag_param), url(docsrs))]
MissingFlagParam(#[label = "flag missing param"] Span),
#[error("Batches of short flags can't take arguments.")]
#[diagnostic(code(nu::parser::short_flag_arg_cant_take_arg), url(docsrs))]
ShortFlagBatchCantTakeArg(#[label = "short flag batches can't take args"] Span),
#[error("Missing required positional argument.")]
#[diagnostic(code(nu::parser::missing_positional), url(docsrs))]
MissingPositional(String, #[label("missing {0}")] Span),
#[error("Missing argument to `{0}`.")]
#[diagnostic(code(nu::parser::keyword_missing_arg), url(docsrs))]
KeywordMissingArgument(String, #[label("missing value that follows {0}")] Span),
#[error("Missing type.")]
#[diagnostic(code(nu::parser::missing_type), url(docsrs))]
MissingType(#[label = "expected type"] Span),
#[error("Type mismatch.")]
#[diagnostic(code(nu::parser::type_mismatch), url(docsrs))]
TypeMismatch(Type, Type, #[label("expected {0:?}, found {1:?}")] Span), // expected, found, span
#[error("Missing required flag.")]
#[diagnostic(code(nu::parser::missing_required_flag), url(docsrs))]
MissingRequiredFlag(String, #[label("missing required flag {0}")] Span),
#[error("Incomplete math expression.")]
#[diagnostic(code(nu::parser::incomplete_math_expression), url(docsrs))]
IncompleteMathExpression(#[label = "incomplete math expression"] Span),
#[error("Unknown state.")]
#[diagnostic(code(nu::parser::unknown_state), url(docsrs))]
UnknownState(String, #[label("{0}")] Span),
#[error("Parser incomplete.")]
#[diagnostic(code(nu::parser::parser_incomplete), url(docsrs))]
IncompleteParser(#[label = "parser support missing for this expression"] Span),
#[error("Rest parameter needs a name.")]
#[diagnostic(code(nu::parser::rest_needs_name), url(docsrs))]
RestNeedsName(#[label = "needs a parameter name"] Span),
#[error("Extra columns.")]
#[diagnostic(code(nu::parser::extra_columns), url(docsrs))]
ExtraColumns(
usize,
#[label("expected {0} column{}", if *.0 == 1 { "" } else { "s" })] Span,
),
#[error("Missing columns.")]
#[diagnostic(code(nu::parser::missing_columns), url(docsrs))]
MissingColumns(
usize,
#[label("expected {0} column{}", if *.0 == 1 { "" } else { "s" })] Span,
),
#[error("{0}")]
#[diagnostic(code(nu::parser::assignment_mismatch), url(docsrs))]
AssignmentMismatch(String, String, #[label("{1}")] Span),
}

View File

@ -6,4 +6,5 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
codespan-reporting = "0.11.1"
thiserror = "1.0.29"
miette = { version = "3.0.0-alpha.0" }

View File

@ -2,7 +2,7 @@ use crate::{ast::Call, value::Value, BlockId, Example, ShellError, Signature};
use super::EvaluationContext;
pub trait Command {
pub trait Command: Send + Sync {
fn name(&self) -> &str;
fn signature(&self) -> Signature {

View File

@ -1,7 +1,7 @@
use super::Command;
use crate::{ast::Block, BlockId, DeclId, Span, Type, VarId};
use core::panic;
use std::{collections::HashMap, ops::Range, slice::Iter};
use std::{collections::HashMap, slice::Iter};
pub struct EngineState {
files: Vec<(String, usize, usize)>,
@ -549,95 +549,42 @@ impl<'a> StateWorkingSet<'a> {
}
}
impl<'a> codespan_reporting::files::Files<'a> for StateWorkingSet<'a> {
type FileId = usize;
type Name = String;
type Source = String;
fn name(&'a self, id: Self::FileId) -> Result<Self::Name, codespan_reporting::files::Error> {
Ok(self.get_filename(id))
}
fn source(
&'a self,
id: Self::FileId,
) -> Result<Self::Source, codespan_reporting::files::Error> {
Ok(self.get_file_source(id))
}
fn line_index(
&'a self,
id: Self::FileId,
byte_index: usize,
) -> Result<usize, codespan_reporting::files::Error> {
let source = self.get_file_source(id);
let mut count = 0;
for byte in source.bytes().enumerate() {
if byte.0 == byte_index {
// println!("count: {} for file: {} index: {}", count, id, byte_index);
return Ok(count);
}
if byte.1 == b'\n' {
count += 1;
impl<'a> miette::SourceCode for &StateWorkingSet<'a> {
fn read_span<'b>(
&'b self,
span: &miette::SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn miette::SpanContents + 'b>, miette::MietteError> {
for (filename, start, end) in self.files() {
if span.offset() >= *start && span.offset() + span.len() <= *end {
let our_span = Span {
start: *start,
end: *end,
};
// We need to move to a local span because we're only reading
// the specific file contents via self.get_span_contents.
let local_span = (span.offset() - *start, span.len()).into();
let span_contents = self.get_span_contents(our_span);
let span_contents = span_contents.read_span(
&local_span,
context_lines_before,
context_lines_after,
)?;
let content_span = span_contents.span();
// Back to "global" indexing
let retranslated = (content_span.offset() + start, content_span.len()).into();
return Ok(Box::new(miette::MietteSpanContents::new_named(
filename.clone(),
span_contents.data(),
retranslated,
span_contents.line(),
span_contents.column(),
span_contents.line_count(),
)));
}
}
// println!("count: {} for file: {} index: {}", count, id, byte_index);
Ok(count)
}
fn line_range(
&'a self,
id: Self::FileId,
line_index: usize,
) -> Result<Range<usize>, codespan_reporting::files::Error> {
let source = self.get_file_source(id);
let mut count = 0;
let mut start = Some(0);
let mut end = None;
for byte in source.bytes().enumerate() {
#[allow(clippy::comparison_chain)]
if count > line_index {
let start = start.expect("internal error: couldn't find line");
let end = end.expect("internal error: couldn't find line");
// println!(
// "Span: {}..{} for fileid: {} index: {}",
// start, end, id, line_index
// );
return Ok(start..end);
} else if count == line_index {
end = Some(byte.0 + 1);
}
#[allow(clippy::comparison_chain)]
if byte.1 == b'\n' {
count += 1;
if count > line_index {
break;
} else if count == line_index {
start = Some(byte.0 + 1);
}
}
}
match (start, end) {
(Some(start), Some(end)) => {
// println!(
// "Span: {}..{} for fileid: {} index: {}",
// start, end, id, line_index
// );
Ok(start..end)
}
_ => Err(codespan_reporting::files::Error::FileMissing),
}
Err(miette::MietteError::OutOfBounds)
}
}

View File

@ -1,25 +1,72 @@
use miette::Diagnostic;
use thiserror::Error;
use crate::{ast::Operator, Span, Type};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Error, Diagnostic)]
pub enum ShellError {
#[error("Type mismatch during operation.")]
#[diagnostic(code(nu::shell::type_mismatch), url(docsrs))]
OperatorMismatch {
#[label = "type mismatch for operator"]
op_span: Span,
lhs_ty: Type,
#[label("{lhs_ty}")]
lhs_span: Span,
rhs_ty: Type,
#[label("{rhs_ty}")]
rhs_span: Span,
},
UnsupportedOperator(Operator, Span),
UnknownOperator(String, Span),
ExternalNotSupported(Span),
#[error("Unsupported operator: {0}.")]
#[diagnostic(code(nu::shell::unsupported_operator), url(docsrs))]
UnsupportedOperator(Operator, #[label = "unsupported operator"] Span),
#[error("Unsupported operator: {0}.")]
#[diagnostic(code(nu::shell::unknown_operator), url(docsrs))]
UnknownOperator(String, #[label = "unsupported operator"] Span),
#[error("External commands not yet supported")]
#[diagnostic(code(nu::shell::external_commands), url(docsrs))]
ExternalNotSupported(#[label = "external not supported"] Span),
#[error("Internal error: {0}.")]
#[diagnostic(code(nu::shell::internal_error), url(docsrs))]
InternalError(String),
VariableNotFoundAtRuntime(Span),
CantConvert(String, Span),
DivisionByZero(Span),
CannotCreateRange(Span),
AccessBeyondEnd(usize, Span),
AccessBeyondEndOfStream(Span),
IncompatiblePathAccess(String, Span),
CantFindColumn(Span),
ExternalCommand(String, Span),
#[error("Variable not found")]
#[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
VariableNotFoundAtRuntime(#[label = "variable not found"] Span),
#[error("Can't convert to {0}.")]
#[diagnostic(code(nu::shell::cant_convert), url(docsrs))]
CantConvert(String, #[label("can't convert to {0}")] Span),
#[error("Division by zero.")]
#[diagnostic(code(nu::shell::division_by_zero), url(docsrs))]
DivisionByZero(#[label("division by zero")] Span),
#[error("Can't convert range to countable values")]
#[diagnostic(code(nu::shell::range_to_countable), url(docsrs))]
CannotCreateRange(#[label = "can't convert to countable values"] Span),
#[error("Row number too large (max: {0}).")]
#[diagnostic(code(nu::shell::access_beyond_end), url(docsrs))]
AccessBeyondEnd(usize, #[label = "too large"] Span),
#[error("Row number too large.")]
#[diagnostic(code(nu::shell::access_beyond_end_of_stream), url(docsrs))]
AccessBeyondEndOfStream(#[label = "too large"] Span),
#[error("Data cannot be accessed with a cell path")]
#[diagnostic(code(nu::shell::incompatible_path_access), url(docsrs))]
IncompatiblePathAccess(String, #[label("{0} doesn't support cell paths")] Span),
#[error("Cannot find column")]
#[diagnostic(code(nu::shell::column_not_found), url(docsrs))]
CantFindColumn(#[label = "cannot find column"] Span),
#[error("External command")]
#[diagnostic(code(nu::shell::external_command), url(docsrs))]
ExternalCommand(String, #[label("{0}")] Span),
}

View File

@ -1,9 +1,17 @@
use miette::SourceSpan;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl From<Span> for SourceSpan {
fn from(s: Span) -> Self {
Self::new(s.start.into(), (s.end - s.start).into())
}
}
impl Span {
pub fn new(start: usize, end: usize) -> Span {
Span { start, end }

View File

@ -1,4 +1,5 @@
use nu_cli::{report_parsing_error, report_shell_error, NuCompleter, NuHighlighter};
use miette::{IntoDiagnostic, Result};
use nu_cli::{report_error, NuCompleter, NuHighlighter};
use nu_command::create_default_context;
use nu_engine::eval_block;
use nu_parser::parse;
@ -11,18 +12,18 @@ use reedline::DefaultCompletionActionHandler;
#[cfg(test)]
mod tests;
fn main() -> std::io::Result<()> {
fn main() -> Result<()> {
let engine_state = create_default_context();
if let Some(path) = std::env::args().nth(1) {
let file = std::fs::read(&path)?;
let file = std::fs::read(&path).into_diagnostic()?;
let (block, delta) = {
let engine_state = engine_state.borrow();
let mut working_set = StateWorkingSet::new(&*engine_state);
let (output, err) = parse(&mut working_set, Some(&path), &file, false);
if let Some(err) = err {
let _ = report_parsing_error(&working_set, &err);
report_error(&working_set, &err);
std::process::exit(1);
}
@ -44,7 +45,7 @@ fn main() -> std::io::Result<()> {
let engine_state = engine_state.borrow();
let working_set = StateWorkingSet::new(&*engine_state);
let _ = report_shell_error(&working_set, &err);
report_error(&working_set, &err);
std::process::exit(1);
}
@ -56,11 +57,12 @@ fn main() -> std::io::Result<()> {
let completer = NuCompleter::new(engine_state.clone());
let mut line_editor = Reedline::create()?
.with_history(Box::new(FileBackedHistory::with_file(
1000,
"history.txt".into(),
)?))?
let mut line_editor = Reedline::create()
.into_diagnostic()?
.with_history(Box::new(
FileBackedHistory::with_file(1000, "history.txt".into()).into_diagnostic()?,
))
.into_diagnostic()?
.with_highlighter(Box::new(NuHighlighter {
engine_state: engine_state.clone(),
}))
@ -102,7 +104,7 @@ fn main() -> std::io::Result<()> {
false,
);
if let Some(err) = err {
let _ = report_parsing_error(&working_set, &err);
report_error(&working_set, &err);
continue;
}
(output, working_set.render())
@ -123,7 +125,7 @@ fn main() -> std::io::Result<()> {
let engine_state = engine_state.borrow();
let working_set = StateWorkingSet::new(&*engine_state);
let _ = report_shell_error(&working_set, &err);
report_error(&working_set, &err);
}
}
}
@ -134,7 +136,7 @@ fn main() -> std::io::Result<()> {
break;
}
Ok(Signal::CtrlL) => {
line_editor.clear_screen()?;
line_editor.clear_screen().into_diagnostic()?;
}
Err(err) => {
let message = err.to_string();

View File

@ -133,7 +133,7 @@ fn if_elseif4() -> TestResult {
fn no_scope_leak1() -> TestResult {
fail_test(
"if $false { let $x = 10 } else { let $x = 20 }; $x",
"variable not found",
"Variable not found",
)
}