From 27dc35339c16e8bc1fafd081fb3634852ce4b7d9 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Tue, 14 May 2024 15:04:22 +0100 Subject: [PATCH] Introduce Diagnostic trait and remove error rendering in public API --- Cargo.lock | 3 - crates/codegen/runtime/cargo/Cargo.toml | 4 +- .../runtime/cargo/src/runtime/diagnostic.rs | 65 +++++++++++++ .../codegen/runtime/cargo/src/runtime/mod.rs | 1 + .../src/runtime/napi_interface/diagnostic.rs | 51 ++++++++++ .../cargo/src/runtime/napi_interface/mod.rs | 1 + .../src/runtime/napi_interface/parse_error.rs | 9 +- .../runtime/cargo/src/runtime/parse_error.rs | 97 +++++++------------ .../napi-bindings/generated/index.d.ts | 21 +++- .../runtime/napi-bindings/generated/index.js | 4 +- .../outputs/cargo/slang_solidity/Cargo.toml | 6 +- .../src/generated/diagnostic.rs | 67 +++++++++++++ .../cargo/slang_solidity/src/generated/mod.rs | 1 + .../generated/napi_interface/diagnostic.rs | 53 ++++++++++ .../src/generated/napi_interface/mod.rs | 1 + .../generated/napi_interface/parse_error.rs | 9 +- .../src/generated/parse_error.rs | 97 +++++++------------ .../outputs/cargo/slang_solidity/src/lib.rs | 2 +- .../outputs/cargo/slang_solidity/src/main.rs | 3 +- .../slang_solidity_node_addon/Cargo.toml | 1 - .../solidity/outputs/cargo/tests/Cargo.toml | 2 +- .../cargo/tests/src/cst_output/runner.rs | 5 +- .../src/doc_examples/using_the_parser.rs | 8 +- .../napi-bindings/generated/index.d.ts | 21 +++- .../napi-bindings/generated/index.js | 4 +- .../src/doc-examples/using-the-parser.ts | 3 +- crates/solidity/testing/sanctuary/Cargo.toml | 2 +- .../solidity/testing/sanctuary/src/tests.rs | 2 +- .../outputs/cargo/slang_testlang/Cargo.toml | 1 - .../src/generated/diagnostic.rs | 67 +++++++++++++ .../cargo/slang_testlang/src/generated/mod.rs | 1 + .../generated/napi_interface/diagnostic.rs | 53 ++++++++++ .../src/generated/napi_interface/mod.rs | 1 + .../generated/napi_interface/parse_error.rs | 9 +- .../src/generated/parse_error.rs | 97 +++++++------------ .../slang_testlang_node_addon/Cargo.toml | 1 - .../napi-bindings/generated/index.d.ts | 21 +++- .../napi-bindings/generated/index.js | 4 +- .../outputs/npm/tests/src/tests/errors.ts | 19 ++-- 39 files changed, 590 insertions(+), 227 deletions(-) create mode 100644 crates/codegen/runtime/cargo/src/runtime/diagnostic.rs create mode 100644 crates/codegen/runtime/cargo/src/runtime/napi_interface/diagnostic.rs create mode 100644 crates/solidity/outputs/cargo/slang_solidity/src/generated/diagnostic.rs create mode 100644 crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/diagnostic.rs create mode 100644 crates/testlang/outputs/cargo/slang_testlang/src/generated/diagnostic.rs create mode 100644 crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/diagnostic.rs diff --git a/Cargo.lock b/Cargo.lock index 6c3b86fa32..d8956e98e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1807,7 +1807,6 @@ dependencies = [ name = "slang_solidity_node_addon" version = "0.14.2" dependencies = [ - "ariadne", "napi", "napi-build", "napi-derive", @@ -1825,7 +1824,6 @@ name = "slang_testlang" version = "0.14.2" dependencies = [ "anyhow", - "ariadne", "codegen_runtime_generator", "infra_utils", "nom", @@ -1841,7 +1839,6 @@ dependencies = [ name = "slang_testlang_node_addon" version = "0.14.2" dependencies = [ - "ariadne", "napi", "napi-build", "napi-derive", diff --git a/crates/codegen/runtime/cargo/Cargo.toml b/crates/codegen/runtime/cargo/Cargo.toml index a22296ac93..f70b5a3c8b 100644 --- a/crates/codegen/runtime/cargo/Cargo.toml +++ b/crates/codegen/runtime/cargo/Cargo.toml @@ -12,7 +12,7 @@ anyhow = { workspace = true } codegen_runtime_generator = { workspace = true } [dependencies] -ariadne = { workspace = true } +ariadne = { workspace = true, optional = true } napi = { workspace = true, optional = true } napi-derive = { workspace = true, optional = true } nom = { workspace = true } @@ -28,6 +28,8 @@ thiserror = { workspace = true } [features] default = ["slang_napi_interfaces"] slang_napi_interfaces = ["dep:napi", "dep:napi-derive", "dep:serde_json"] +# Only used by the `slang_solidity` CLI +__private_ariadne = ["dep:ariadne"] [lints] workspace = true diff --git a/crates/codegen/runtime/cargo/src/runtime/diagnostic.rs b/crates/codegen/runtime/cargo/src/runtime/diagnostic.rs new file mode 100644 index 0000000000..049379da7f --- /dev/null +++ b/crates/codegen/runtime/cargo/src/runtime/diagnostic.rs @@ -0,0 +1,65 @@ +use std::borrow::Cow; +use std::error::Error; + +use crate::text_index::TextRange; + +#[repr(u8)] +pub enum Severity { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, +} + +pub trait Diagnostic: Error { + fn range(&self) -> TextRange; + fn code(&self) -> Option> { + None + } + fn severity(&self) -> Severity; + fn message(&self) -> String; +} + +#[cfg(feature = "__private_ariadne")] +pub fn render(error: &D, source_id: &str, source: &str, with_color: bool) -> String { + use ariadne::{Color, Config, Label, Report, ReportKind, Source}; + + use crate::text_index::TextRangeExtensions as _; + + let kind = match error.severity() { + Severity::Error => ReportKind::Error, + Severity::Warning => ReportKind::Warning, + Severity::Information => ReportKind::Advice, + Severity::Hint => ReportKind::Advice, + }; + + let color = if with_color { Color::Red } else { Color::Unset }; + + let message = error.message(); + + if source.is_empty() { + return format!("{kind}: {message}\n ─[{source_id}:0:0]"); + } + + let range = error.range().char(); + + let report = Report::build(kind, source_id, range.start) + .with_config(Config::default().with_color(with_color)) + .with_message(message) + .with_label( + Label::new((source_id, range)) + .with_color(color) + .with_message("Error occurred here."), + ) + .finish(); + + let mut result = vec![]; + report + .write((source_id, Source::from(&source)), &mut result) + .expect("Failed to write report"); + + return String::from_utf8(result) + .expect("Failed to convert report to utf8") + .trim() + .to_string(); +} diff --git a/crates/codegen/runtime/cargo/src/runtime/mod.rs b/crates/codegen/runtime/cargo/src/runtime/mod.rs index eab63a3f88..0923a25382 100644 --- a/crates/codegen/runtime/cargo/src/runtime/mod.rs +++ b/crates/codegen/runtime/cargo/src/runtime/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod lexer; pub mod cst; pub mod cursor; +pub mod diagnostic; pub mod parse_error; pub mod parse_output; pub mod query; diff --git a/crates/codegen/runtime/cargo/src/runtime/napi_interface/diagnostic.rs b/crates/codegen/runtime/cargo/src/runtime/napi_interface/diagnostic.rs new file mode 100644 index 0000000000..21ba249115 --- /dev/null +++ b/crates/codegen/runtime/cargo/src/runtime/napi_interface/diagnostic.rs @@ -0,0 +1,51 @@ +use napi_derive::napi; + +use crate::napi_interface::text_index::TextRange; + +/// Severity of the compiler diagnostic. +/// +/// Explicitly compatible with the LSP protocol. +#[napi(namespace = "diagnostic")] +pub enum Severity { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, +} + +impl From for Severity { + fn from(value: crate::diagnostic::Severity) -> Severity { + match value { + crate::diagnostic::Severity::Error => Self::Error, + crate::diagnostic::Severity::Warning => Self::Warning, + crate::diagnostic::Severity::Information => Self::Information, + crate::diagnostic::Severity::Hint => Self::Hint, + } + } +} + +#[napi(namespace = "diagnostic")] +pub struct Diagnostic(pub(crate) Box); + +#[napi(namespace = "diagnostic")] +impl Diagnostic { + #[napi] + pub fn severity(&self) -> Severity { + self.0.severity().into() + } + + #[napi(ts_return_type = "text_index.TextRange")] + pub fn text_range(&self) -> TextRange { + self.0.range().into() + } + + #[napi] + pub fn message(&self) -> String { + self.0.message() + } + + #[napi] + pub fn code(&self) -> String { + self.0.code().unwrap_or_default().into_owned() + } +} diff --git a/crates/codegen/runtime/cargo/src/runtime/napi_interface/mod.rs b/crates/codegen/runtime/cargo/src/runtime/napi_interface/mod.rs index 2c6d569d03..3e82be6ac4 100644 --- a/crates/codegen/runtime/cargo/src/runtime/napi_interface/mod.rs +++ b/crates/codegen/runtime/cargo/src/runtime/napi_interface/mod.rs @@ -1,5 +1,6 @@ pub mod cst; pub mod cursor; +pub mod diagnostic; pub mod parse_error; pub mod parse_output; pub mod query; diff --git a/crates/codegen/runtime/cargo/src/runtime/napi_interface/parse_error.rs b/crates/codegen/runtime/cargo/src/runtime/napi_interface/parse_error.rs index 219a85f70a..1074308c00 100644 --- a/crates/codegen/runtime/cargo/src/runtime/napi_interface/parse_error.rs +++ b/crates/codegen/runtime/cargo/src/runtime/napi_interface/parse_error.rs @@ -4,6 +4,7 @@ use napi_derive::napi; use text_index::TextRange; +use crate::napi_interface::diagnostic::Diagnostic; use crate::napi_interface::{text_index, RustParseError}; #[napi(namespace = "parse_error")] @@ -23,8 +24,10 @@ impl ParseError { self.0.text_range().clone().into() } - #[napi(catch_unwind)] - pub fn to_error_report(&self, source_id: String, source: String, with_color: bool) -> String { - self.0.to_error_report(&source_id, &source, with_color) + #[napi(ts_return_type = "diagnostic.Diagnostic", catch_unwind)] + pub fn to_diagnostic(&self) -> Diagnostic { + // TODO: Figure out if we can auto-gen Diagnostics methods + // in TS for this implementor rather than having to clone here + Diagnostic(Box::new(self.0.clone())) } } diff --git a/crates/codegen/runtime/cargo/src/runtime/parse_error.rs b/crates/codegen/runtime/cargo/src/runtime/parse_error.rs index 950656c97f..c26f9a0e89 100644 --- a/crates/codegen/runtime/cargo/src/runtime/parse_error.rs +++ b/crates/codegen/runtime/cargo/src/runtime/parse_error.rs @@ -1,7 +1,10 @@ use std::collections::BTreeSet; +use std::error::Error; +use std::fmt; +use crate::diagnostic::{self, Diagnostic}; use crate::kinds::TokenKind; -use crate::text_index::{TextRange, TextRangeExtensions}; +use crate::text_index::TextRange; #[derive(Debug, PartialEq, Eq, Clone)] pub struct ParseError { @@ -13,22 +16,6 @@ impl ParseError { pub fn text_range(&self) -> &TextRange { &self.text_range } - - pub fn tokens_that_would_have_allowed_more_progress(&self) -> Vec { - let tokens_that_would_have_allowed_more_progress = self - .tokens_that_would_have_allowed_more_progress - .iter() - .collect::>(); - - tokens_that_would_have_allowed_more_progress - .into_iter() - .map(TokenKind::to_string) - .collect() - } - - pub fn to_error_report(&self, source_id: &str, source: &str, with_color: bool) -> String { - render_error_report(self, source_id, source, with_color) - } } impl ParseError { @@ -43,52 +30,40 @@ impl ParseError { } } -pub(crate) fn render_error_report( - error: &ParseError, - source_id: &str, - source: &str, - with_color: bool, -) -> String { - use ariadne::{Color, Config, Label, Report, ReportKind, Source}; - - let kind = ReportKind::Error; - let color = if with_color { Color::Red } else { Color::Unset }; - - let tokens_that_would_have_allowed_more_progress = - error.tokens_that_would_have_allowed_more_progress(); - let message = if tokens_that_would_have_allowed_more_progress.is_empty() { - "Expected end of file.".to_string() - } else { - format!( - "Expected {expectations}.", - expectations = tokens_that_would_have_allowed_more_progress.join(" or ") - ) - }; - - if source.is_empty() { - return format!("{kind}: {message}\n ─[{source_id}:0:0]"); +impl Error for ParseError {} +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.tokens_that_would_have_allowed_more_progress.is_empty() { + write!(f, "Expected end of file.") + } else { + let deduped = self + .tokens_that_would_have_allowed_more_progress + .iter() + .collect::>(); + + write!(f, "Expected ")?; + + for kind in deduped.iter().take(deduped.len() - 1) { + write!(f, "{kind} or ")?; + } + let last = deduped.last().expect("we just checked that it's not empty"); + write!(f, "{last}.")?; + + Ok(()) + } } +} - let range = error.text_range.char(); - - let mut builder = Report::build(kind, source_id, range.start) - .with_config(Config::default().with_color(with_color)) - .with_message(message); - - builder.add_label( - Label::new((source_id, range)) - .with_color(color) - .with_message("Error occurred here.".to_string()), - ); +impl Diagnostic for ParseError { + fn range(&self) -> TextRange { + self.text_range.clone() + } - let mut result = vec![]; - builder - .finish() - .write((source_id, Source::from(&source)), &mut result) - .expect("Failed to write report"); + fn severity(&self) -> diagnostic::Severity { + diagnostic::Severity::Error + } - return String::from_utf8(result) - .expect("Failed to convert report to utf8") - .trim() - .to_string(); + fn message(&self) -> String { + ToString::to_string(&self) + } } diff --git a/crates/codegen/runtime/npm/src/runtime/napi-bindings/generated/index.d.ts b/crates/codegen/runtime/npm/src/runtime/napi-bindings/generated/index.d.ts index 822c31129f..b3853c8a14 100644 --- a/crates/codegen/runtime/npm/src/runtime/napi-bindings/generated/index.d.ts +++ b/crates/codegen/runtime/npm/src/runtime/napi-bindings/generated/index.d.ts @@ -102,10 +102,29 @@ export namespace cursor { query(queries: Array): query.QueryResultIterator; } } +export namespace diagnostic { + /** + * Severity of the compiler diagnostic. + * + * Explicitly compatible with the LSP protocol. + */ + export enum Severity { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, + } + export class Diagnostic { + severity(): Severity; + textRange(): text_index.TextRange; + message(): string; + code(): string; + } +} export namespace parse_error { export class ParseError { get textRange(): text_index.TextRange; - toErrorReport(sourceId: string, source: string, withColor: boolean): string; + toDiagnostic(): diagnostic.Diagnostic; } } export namespace parse_output { diff --git a/crates/codegen/runtime/npm/src/runtime/napi-bindings/generated/index.js b/crates/codegen/runtime/npm/src/runtime/napi-bindings/generated/index.js index abd6ab450c..e7402be5ac 100644 --- a/crates/codegen/runtime/npm/src/runtime/napi-bindings/generated/index.js +++ b/crates/codegen/runtime/npm/src/runtime/napi-bindings/generated/index.js @@ -291,12 +291,14 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`); } -const { kinds, language, cst, cursor, parse_error, parse_output, query, text_index, ast_internal } = nativeBinding; +const { kinds, language, cst, cursor, diagnostic, parse_error, parse_output, query, text_index, ast_internal } = + nativeBinding; module.exports.kinds = kinds; module.exports.language = language; module.exports.cst = cst; module.exports.cursor = cursor; +module.exports.diagnostic = diagnostic; module.exports.parse_error = parse_error; module.exports.parse_output = parse_output; module.exports.query = query; diff --git a/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml b/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml index b9a76ef719..3bec1965ad 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml +++ b/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml @@ -33,7 +33,9 @@ required-features = ["cli"] [features] default = ["cli"] -cli = ["dep:anyhow", "dep:clap", "dep:serde_json"] +cli = ["dep:anyhow", "dep:clap", "dep:serde_json", "__private_ariadne"] +# This is meant to be used by the CLI or internally only. +__private_ariadne = ["dep:ariadne"] [build-dependencies] # __REMOVE_THIS_LINE_DURING_CARGO_PUBLISH__ anyhow = { workspace = true } # __REMOVE_THIS_LINE_DURING_CARGO_PUBLISH__ @@ -43,7 +45,7 @@ solidity_language = { workspace = true } # __REMOVE_THIS_LINE_DURING_CAR [dependencies] anyhow = { workspace = true, optional = true } -ariadne = { workspace = true } +ariadne = { workspace = true, optional = true } clap = { workspace = true, optional = true } nom = { workspace = true } semver = { workspace = true } diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/diagnostic.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/diagnostic.rs new file mode 100644 index 0000000000..9ccea34247 --- /dev/null +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/diagnostic.rs @@ -0,0 +1,67 @@ +// This file is generated automatically by infrastructure scripts. Please don't edit by hand. + +use std::borrow::Cow; +use std::error::Error; + +use crate::text_index::TextRange; + +#[repr(u8)] +pub enum Severity { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, +} + +pub trait Diagnostic: Error { + fn range(&self) -> TextRange; + fn code(&self) -> Option> { + None + } + fn severity(&self) -> Severity; + fn message(&self) -> String; +} + +#[cfg(feature = "__private_ariadne")] +pub fn render(error: &D, source_id: &str, source: &str, with_color: bool) -> String { + use ariadne::{Color, Config, Label, Report, ReportKind, Source}; + + use crate::text_index::TextRangeExtensions as _; + + let kind = match error.severity() { + Severity::Error => ReportKind::Error, + Severity::Warning => ReportKind::Warning, + Severity::Information => ReportKind::Advice, + Severity::Hint => ReportKind::Advice, + }; + + let color = if with_color { Color::Red } else { Color::Unset }; + + let message = error.message(); + + if source.is_empty() { + return format!("{kind}: {message}\n ─[{source_id}:0:0]"); + } + + let range = error.range().char(); + + let report = Report::build(kind, source_id, range.start) + .with_config(Config::default().with_color(with_color)) + .with_message(message) + .with_label( + Label::new((source_id, range)) + .with_color(color) + .with_message("Error occurred here."), + ) + .finish(); + + let mut result = vec![]; + report + .write((source_id, Source::from(&source)), &mut result) + .expect("Failed to write report"); + + return String::from_utf8(result) + .expect("Failed to convert report to utf8") + .trim() + .to_string(); +} diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/mod.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/mod.rs index 6c56b9c553..5e1f56e389 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/mod.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod lexer; pub mod cst; pub mod cursor; +pub mod diagnostic; pub mod parse_error; pub mod parse_output; pub mod query; diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/diagnostic.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/diagnostic.rs new file mode 100644 index 0000000000..9659273c99 --- /dev/null +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/diagnostic.rs @@ -0,0 +1,53 @@ +// This file is generated automatically by infrastructure scripts. Please don't edit by hand. + +use napi_derive::napi; + +use crate::napi_interface::text_index::TextRange; + +/// Severity of the compiler diagnostic. +/// +/// Explicitly compatible with the LSP protocol. +#[napi(namespace = "diagnostic")] +pub enum Severity { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, +} + +impl From for Severity { + fn from(value: crate::diagnostic::Severity) -> Severity { + match value { + crate::diagnostic::Severity::Error => Self::Error, + crate::diagnostic::Severity::Warning => Self::Warning, + crate::diagnostic::Severity::Information => Self::Information, + crate::diagnostic::Severity::Hint => Self::Hint, + } + } +} + +#[napi(namespace = "diagnostic")] +pub struct Diagnostic(pub(crate) Box); + +#[napi(namespace = "diagnostic")] +impl Diagnostic { + #[napi] + pub fn severity(&self) -> Severity { + self.0.severity().into() + } + + #[napi(ts_return_type = "text_index.TextRange")] + pub fn text_range(&self) -> TextRange { + self.0.range().into() + } + + #[napi] + pub fn message(&self) -> String { + self.0.message() + } + + #[napi] + pub fn code(&self) -> String { + self.0.code().unwrap_or_default().into_owned() + } +} diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/mod.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/mod.rs index 2035b95f64..943d73e66f 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/mod.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/mod.rs @@ -2,6 +2,7 @@ pub mod cst; pub mod cursor; +pub mod diagnostic; pub mod parse_error; pub mod parse_output; pub mod query; diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/parse_error.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/parse_error.rs index 68bd760644..09e1f8caa3 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/parse_error.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/parse_error.rs @@ -6,6 +6,7 @@ use napi_derive::napi; use text_index::TextRange; +use crate::napi_interface::diagnostic::Diagnostic; use crate::napi_interface::{text_index, RustParseError}; #[napi(namespace = "parse_error")] @@ -25,8 +26,10 @@ impl ParseError { self.0.text_range().clone().into() } - #[napi(catch_unwind)] - pub fn to_error_report(&self, source_id: String, source: String, with_color: bool) -> String { - self.0.to_error_report(&source_id, &source, with_color) + #[napi(ts_return_type = "diagnostic.Diagnostic", catch_unwind)] + pub fn to_diagnostic(&self) -> Diagnostic { + // TODO: Figure out if we can auto-gen Diagnostics methods + // in TS for this implementor rather than having to clone here + Diagnostic(Box::new(self.0.clone())) } } diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/parse_error.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/parse_error.rs index f6c3d9b52c..b9a1a74eac 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/parse_error.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/parse_error.rs @@ -1,9 +1,12 @@ // This file is generated automatically by infrastructure scripts. Please don't edit by hand. use std::collections::BTreeSet; +use std::error::Error; +use std::fmt; +use crate::diagnostic::{self, Diagnostic}; use crate::kinds::TokenKind; -use crate::text_index::{TextRange, TextRangeExtensions}; +use crate::text_index::TextRange; #[derive(Debug, PartialEq, Eq, Clone)] pub struct ParseError { @@ -15,22 +18,6 @@ impl ParseError { pub fn text_range(&self) -> &TextRange { &self.text_range } - - pub fn tokens_that_would_have_allowed_more_progress(&self) -> Vec { - let tokens_that_would_have_allowed_more_progress = self - .tokens_that_would_have_allowed_more_progress - .iter() - .collect::>(); - - tokens_that_would_have_allowed_more_progress - .into_iter() - .map(TokenKind::to_string) - .collect() - } - - pub fn to_error_report(&self, source_id: &str, source: &str, with_color: bool) -> String { - render_error_report(self, source_id, source, with_color) - } } impl ParseError { @@ -45,52 +32,40 @@ impl ParseError { } } -pub(crate) fn render_error_report( - error: &ParseError, - source_id: &str, - source: &str, - with_color: bool, -) -> String { - use ariadne::{Color, Config, Label, Report, ReportKind, Source}; - - let kind = ReportKind::Error; - let color = if with_color { Color::Red } else { Color::Unset }; - - let tokens_that_would_have_allowed_more_progress = - error.tokens_that_would_have_allowed_more_progress(); - let message = if tokens_that_would_have_allowed_more_progress.is_empty() { - "Expected end of file.".to_string() - } else { - format!( - "Expected {expectations}.", - expectations = tokens_that_would_have_allowed_more_progress.join(" or ") - ) - }; - - if source.is_empty() { - return format!("{kind}: {message}\n ─[{source_id}:0:0]"); +impl Error for ParseError {} +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.tokens_that_would_have_allowed_more_progress.is_empty() { + write!(f, "Expected end of file.") + } else { + let deduped = self + .tokens_that_would_have_allowed_more_progress + .iter() + .collect::>(); + + write!(f, "Expected ")?; + + for kind in deduped.iter().take(deduped.len() - 1) { + write!(f, "{kind} or ")?; + } + let last = deduped.last().expect("we just checked that it's not empty"); + write!(f, "{last}.")?; + + Ok(()) + } } +} - let range = error.text_range.char(); - - let mut builder = Report::build(kind, source_id, range.start) - .with_config(Config::default().with_color(with_color)) - .with_message(message); - - builder.add_label( - Label::new((source_id, range)) - .with_color(color) - .with_message("Error occurred here.".to_string()), - ); +impl Diagnostic for ParseError { + fn range(&self) -> TextRange { + self.text_range.clone() + } - let mut result = vec![]; - builder - .finish() - .write((source_id, Source::from(&source)), &mut result) - .expect("Failed to write report"); + fn severity(&self) -> diagnostic::Severity { + diagnostic::Severity::Error + } - return String::from_utf8(result) - .expect("Failed to convert report to utf8") - .trim() - .to_string(); + fn message(&self) -> String { + ToString::to_string(&self) + } } diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/lib.rs b/crates/solidity/outputs/cargo/slang_solidity/src/lib.rs index cbf7722d01..8a9148b08a 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/lib.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/lib.rs @@ -9,5 +9,5 @@ pub use generated::*; // https://github.com/rust-lang/cargo/issues/1982 #[cfg(feature = "cli")] mod supress_cli_dependencies { - use {anyhow as _, clap as _, serde_json as _}; + use {anyhow as _, ariadne as _, clap as _, serde_json as _}; } diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/main.rs b/crates/solidity/outputs/cargo/slang_solidity/src/main.rs index 54dfb99241..6d90640471 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/main.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/main.rs @@ -62,7 +62,8 @@ fn execute_parse_command(file_path_string: &str, version: Version, json: bool) - let errors = output.errors(); for error in errors { - let report = error.to_error_report(file_path_string, &input, /* with_color */ true); + const COLOR: bool = true; + let report = slang_solidity::diagnostic::render(error, file_path_string, &input, COLOR); eprintln!("{report}"); } diff --git a/crates/solidity/outputs/cargo/slang_solidity_node_addon/Cargo.toml b/crates/solidity/outputs/cargo/slang_solidity_node_addon/Cargo.toml index b596cfbc36..3bbdd14c80 100644 --- a/crates/solidity/outputs/cargo/slang_solidity_node_addon/Cargo.toml +++ b/crates/solidity/outputs/cargo/slang_solidity_node_addon/Cargo.toml @@ -36,7 +36,6 @@ slang_napi_interfaces = [ napi-build = { workspace = true } [dependencies] -ariadne = { workspace = true } napi = { workspace = true } napi-derive = { workspace = true } nom = { workspace = true } diff --git a/crates/solidity/outputs/cargo/tests/Cargo.toml b/crates/solidity/outputs/cargo/tests/Cargo.toml index ab6ff5a7e8..d411646ff9 100644 --- a/crates/solidity/outputs/cargo/tests/Cargo.toml +++ b/crates/solidity/outputs/cargo/tests/Cargo.toml @@ -19,7 +19,7 @@ infra_utils = { workspace = true } once_cell = { workspace = true } regex = { workspace = true } semver = { workspace = true } -slang_solidity = { workspace = true } +slang_solidity = { workspace = true, features = ["__private_ariadne"] } solidity_language = { workspace = true } strum_macros = { workspace = true } diff --git a/crates/solidity/outputs/cargo/tests/src/cst_output/runner.rs b/crates/solidity/outputs/cargo/tests/src/cst_output/runner.rs index a182aecac5..becba6b4df 100644 --- a/crates/solidity/outputs/cargo/tests/src/cst_output/runner.rs +++ b/crates/solidity/outputs/cargo/tests/src/cst_output/runner.rs @@ -49,7 +49,10 @@ pub fn run(parser_name: &str, test_name: &str) -> Result<()> { let errors = output .errors() .iter() - .map(|error| error.to_error_report(source_id, &source, /* with_color */ false)) + .map(|error| { + const COLOR: bool = false; + slang_solidity::diagnostic::render(error, source_id, &source, COLOR) + }) .collect(); let cursor = output.create_tree_cursor().with_labels(); diff --git a/crates/solidity/outputs/cargo/tests/src/doc_examples/using_the_parser.rs b/crates/solidity/outputs/cargo/tests/src/doc_examples/using_the_parser.rs index 72c0b5faef..fb7fc6fde6 100644 --- a/crates/solidity/outputs/cargo/tests/src/doc_examples/using_the_parser.rs +++ b/crates/solidity/outputs/cargo/tests/src/doc_examples/using_the_parser.rs @@ -27,7 +27,13 @@ fn using_the_parser() -> Result<()> { // --8<-- [start:print-errors] for error in parse_output.errors() { - eprintln!("{}", error.to_error_report(input_path, source, true)); + use slang_solidity::diagnostic::Diagnostic as _; + + eprintln!( + "Error at byte offset {offset}: {message}", + offset = error.range().start.utf8, + message = error.message() + ); } // --8<-- [end:print-errors] diff --git a/crates/solidity/outputs/npm/package/src/generated/napi-bindings/generated/index.d.ts b/crates/solidity/outputs/npm/package/src/generated/napi-bindings/generated/index.d.ts index acc7333fb8..4ddc87c41d 100644 --- a/crates/solidity/outputs/npm/package/src/generated/napi-bindings/generated/index.d.ts +++ b/crates/solidity/outputs/npm/package/src/generated/napi-bindings/generated/index.d.ts @@ -797,10 +797,29 @@ export namespace cursor { query(queries: Array): query.QueryResultIterator; } } +export namespace diagnostic { + /** + * Severity of the compiler diagnostic. + * + * Explicitly compatible with the LSP protocol. + */ + export enum Severity { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, + } + export class Diagnostic { + severity(): Severity; + textRange(): text_index.TextRange; + message(): string; + code(): string; + } +} export namespace parse_error { export class ParseError { get textRange(): text_index.TextRange; - toErrorReport(sourceId: string, source: string, withColor: boolean): string; + toDiagnostic(): diagnostic.Diagnostic; } } export namespace parse_output { diff --git a/crates/solidity/outputs/npm/package/src/generated/napi-bindings/generated/index.js b/crates/solidity/outputs/npm/package/src/generated/napi-bindings/generated/index.js index c117f3d2ba..0888923f1e 100644 --- a/crates/solidity/outputs/npm/package/src/generated/napi-bindings/generated/index.js +++ b/crates/solidity/outputs/npm/package/src/generated/napi-bindings/generated/index.js @@ -291,12 +291,14 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`); } -const { kinds, language, cst, cursor, parse_error, parse_output, query, text_index, ast_internal } = nativeBinding; +const { kinds, language, cst, cursor, diagnostic, parse_error, parse_output, query, text_index, ast_internal } = + nativeBinding; module.exports.kinds = kinds; module.exports.language = language; module.exports.cst = cst; module.exports.cursor = cursor; +module.exports.diagnostic = diagnostic; module.exports.parse_error = parse_error; module.exports.parse_output = parse_output; module.exports.query = query; diff --git a/crates/solidity/outputs/npm/tests/src/doc-examples/using-the-parser.ts b/crates/solidity/outputs/npm/tests/src/doc-examples/using-the-parser.ts index 115511460a..7606fa9395 100644 --- a/crates/solidity/outputs/npm/tests/src/doc-examples/using-the-parser.ts +++ b/crates/solidity/outputs/npm/tests/src/doc-examples/using-the-parser.ts @@ -20,7 +20,8 @@ test("using the parser", async () => { // --8<-- [start:print-errors] for (const error of parseOutput.errors()) { - console.error(error.toErrorReport(inputPath, source, true)); + let diagnostic = error.toDiagnostic(); + console.error(`Encountered an error: ${diagnostic.message()}`); } // --8<-- [end:print-errors] diff --git a/crates/solidity/testing/sanctuary/Cargo.toml b/crates/solidity/testing/sanctuary/Cargo.toml index c1c2328a30..e5e831fd31 100644 --- a/crates/solidity/testing/sanctuary/Cargo.toml +++ b/crates/solidity/testing/sanctuary/Cargo.toml @@ -17,7 +17,7 @@ rayon = { workspace = true } semver = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -slang_solidity = { workspace = true } +slang_solidity = { workspace = true, features = ["__private_ariadne"] } strum_macros = { workspace = true } url = { workspace = true } diff --git a/crates/solidity/testing/sanctuary/src/tests.rs b/crates/solidity/testing/sanctuary/src/tests.rs index 773c847a82..eeac9ed63f 100644 --- a/crates/solidity/testing/sanctuary/src/tests.rs +++ b/crates/solidity/testing/sanctuary/src/tests.rs @@ -112,7 +112,7 @@ pub fn run_test(file: &SourceFile, events: &Events) -> Result<()> { let source_id = file.path.strip_repo_root()?.unwrap_str(); for error in output.errors() { - let report = error.to_error_report(source_id, &source, with_color); + let report = slang_solidity::diagnostic::render(error, source_id, &source, with_color); events.parse_error(format!("[{version}] {report}")); } diff --git a/crates/testlang/outputs/cargo/slang_testlang/Cargo.toml b/crates/testlang/outputs/cargo/slang_testlang/Cargo.toml index c3ccc09864..0fef15ff36 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/Cargo.toml +++ b/crates/testlang/outputs/cargo/slang_testlang/Cargo.toml @@ -13,7 +13,6 @@ infra_utils = { workspace = true } testlang_language = { workspace = true } [dependencies] -ariadne = { workspace = true } nom = { workspace = true } semver = { workspace = true } serde = { workspace = true } diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/diagnostic.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/diagnostic.rs new file mode 100644 index 0000000000..9ccea34247 --- /dev/null +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/diagnostic.rs @@ -0,0 +1,67 @@ +// This file is generated automatically by infrastructure scripts. Please don't edit by hand. + +use std::borrow::Cow; +use std::error::Error; + +use crate::text_index::TextRange; + +#[repr(u8)] +pub enum Severity { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, +} + +pub trait Diagnostic: Error { + fn range(&self) -> TextRange; + fn code(&self) -> Option> { + None + } + fn severity(&self) -> Severity; + fn message(&self) -> String; +} + +#[cfg(feature = "__private_ariadne")] +pub fn render(error: &D, source_id: &str, source: &str, with_color: bool) -> String { + use ariadne::{Color, Config, Label, Report, ReportKind, Source}; + + use crate::text_index::TextRangeExtensions as _; + + let kind = match error.severity() { + Severity::Error => ReportKind::Error, + Severity::Warning => ReportKind::Warning, + Severity::Information => ReportKind::Advice, + Severity::Hint => ReportKind::Advice, + }; + + let color = if with_color { Color::Red } else { Color::Unset }; + + let message = error.message(); + + if source.is_empty() { + return format!("{kind}: {message}\n ─[{source_id}:0:0]"); + } + + let range = error.range().char(); + + let report = Report::build(kind, source_id, range.start) + .with_config(Config::default().with_color(with_color)) + .with_message(message) + .with_label( + Label::new((source_id, range)) + .with_color(color) + .with_message("Error occurred here."), + ) + .finish(); + + let mut result = vec![]; + report + .write((source_id, Source::from(&source)), &mut result) + .expect("Failed to write report"); + + return String::from_utf8(result) + .expect("Failed to convert report to utf8") + .trim() + .to_string(); +} diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/mod.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/mod.rs index 6c56b9c553..5e1f56e389 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/mod.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod lexer; pub mod cst; pub mod cursor; +pub mod diagnostic; pub mod parse_error; pub mod parse_output; pub mod query; diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/diagnostic.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/diagnostic.rs new file mode 100644 index 0000000000..9659273c99 --- /dev/null +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/diagnostic.rs @@ -0,0 +1,53 @@ +// This file is generated automatically by infrastructure scripts. Please don't edit by hand. + +use napi_derive::napi; + +use crate::napi_interface::text_index::TextRange; + +/// Severity of the compiler diagnostic. +/// +/// Explicitly compatible with the LSP protocol. +#[napi(namespace = "diagnostic")] +pub enum Severity { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, +} + +impl From for Severity { + fn from(value: crate::diagnostic::Severity) -> Severity { + match value { + crate::diagnostic::Severity::Error => Self::Error, + crate::diagnostic::Severity::Warning => Self::Warning, + crate::diagnostic::Severity::Information => Self::Information, + crate::diagnostic::Severity::Hint => Self::Hint, + } + } +} + +#[napi(namespace = "diagnostic")] +pub struct Diagnostic(pub(crate) Box); + +#[napi(namespace = "diagnostic")] +impl Diagnostic { + #[napi] + pub fn severity(&self) -> Severity { + self.0.severity().into() + } + + #[napi(ts_return_type = "text_index.TextRange")] + pub fn text_range(&self) -> TextRange { + self.0.range().into() + } + + #[napi] + pub fn message(&self) -> String { + self.0.message() + } + + #[napi] + pub fn code(&self) -> String { + self.0.code().unwrap_or_default().into_owned() + } +} diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/mod.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/mod.rs index 2035b95f64..943d73e66f 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/mod.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/mod.rs @@ -2,6 +2,7 @@ pub mod cst; pub mod cursor; +pub mod diagnostic; pub mod parse_error; pub mod parse_output; pub mod query; diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/parse_error.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/parse_error.rs index 68bd760644..09e1f8caa3 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/parse_error.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/parse_error.rs @@ -6,6 +6,7 @@ use napi_derive::napi; use text_index::TextRange; +use crate::napi_interface::diagnostic::Diagnostic; use crate::napi_interface::{text_index, RustParseError}; #[napi(namespace = "parse_error")] @@ -25,8 +26,10 @@ impl ParseError { self.0.text_range().clone().into() } - #[napi(catch_unwind)] - pub fn to_error_report(&self, source_id: String, source: String, with_color: bool) -> String { - self.0.to_error_report(&source_id, &source, with_color) + #[napi(ts_return_type = "diagnostic.Diagnostic", catch_unwind)] + pub fn to_diagnostic(&self) -> Diagnostic { + // TODO: Figure out if we can auto-gen Diagnostics methods + // in TS for this implementor rather than having to clone here + Diagnostic(Box::new(self.0.clone())) } } diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/parse_error.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/parse_error.rs index f6c3d9b52c..b9a1a74eac 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/parse_error.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/parse_error.rs @@ -1,9 +1,12 @@ // This file is generated automatically by infrastructure scripts. Please don't edit by hand. use std::collections::BTreeSet; +use std::error::Error; +use std::fmt; +use crate::diagnostic::{self, Diagnostic}; use crate::kinds::TokenKind; -use crate::text_index::{TextRange, TextRangeExtensions}; +use crate::text_index::TextRange; #[derive(Debug, PartialEq, Eq, Clone)] pub struct ParseError { @@ -15,22 +18,6 @@ impl ParseError { pub fn text_range(&self) -> &TextRange { &self.text_range } - - pub fn tokens_that_would_have_allowed_more_progress(&self) -> Vec { - let tokens_that_would_have_allowed_more_progress = self - .tokens_that_would_have_allowed_more_progress - .iter() - .collect::>(); - - tokens_that_would_have_allowed_more_progress - .into_iter() - .map(TokenKind::to_string) - .collect() - } - - pub fn to_error_report(&self, source_id: &str, source: &str, with_color: bool) -> String { - render_error_report(self, source_id, source, with_color) - } } impl ParseError { @@ -45,52 +32,40 @@ impl ParseError { } } -pub(crate) fn render_error_report( - error: &ParseError, - source_id: &str, - source: &str, - with_color: bool, -) -> String { - use ariadne::{Color, Config, Label, Report, ReportKind, Source}; - - let kind = ReportKind::Error; - let color = if with_color { Color::Red } else { Color::Unset }; - - let tokens_that_would_have_allowed_more_progress = - error.tokens_that_would_have_allowed_more_progress(); - let message = if tokens_that_would_have_allowed_more_progress.is_empty() { - "Expected end of file.".to_string() - } else { - format!( - "Expected {expectations}.", - expectations = tokens_that_would_have_allowed_more_progress.join(" or ") - ) - }; - - if source.is_empty() { - return format!("{kind}: {message}\n ─[{source_id}:0:0]"); +impl Error for ParseError {} +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.tokens_that_would_have_allowed_more_progress.is_empty() { + write!(f, "Expected end of file.") + } else { + let deduped = self + .tokens_that_would_have_allowed_more_progress + .iter() + .collect::>(); + + write!(f, "Expected ")?; + + for kind in deduped.iter().take(deduped.len() - 1) { + write!(f, "{kind} or ")?; + } + let last = deduped.last().expect("we just checked that it's not empty"); + write!(f, "{last}.")?; + + Ok(()) + } } +} - let range = error.text_range.char(); - - let mut builder = Report::build(kind, source_id, range.start) - .with_config(Config::default().with_color(with_color)) - .with_message(message); - - builder.add_label( - Label::new((source_id, range)) - .with_color(color) - .with_message("Error occurred here.".to_string()), - ); +impl Diagnostic for ParseError { + fn range(&self) -> TextRange { + self.text_range.clone() + } - let mut result = vec![]; - builder - .finish() - .write((source_id, Source::from(&source)), &mut result) - .expect("Failed to write report"); + fn severity(&self) -> diagnostic::Severity { + diagnostic::Severity::Error + } - return String::from_utf8(result) - .expect("Failed to convert report to utf8") - .trim() - .to_string(); + fn message(&self) -> String { + ToString::to_string(&self) + } } diff --git a/crates/testlang/outputs/cargo/slang_testlang_node_addon/Cargo.toml b/crates/testlang/outputs/cargo/slang_testlang_node_addon/Cargo.toml index df3867e1ed..398026ac69 100644 --- a/crates/testlang/outputs/cargo/slang_testlang_node_addon/Cargo.toml +++ b/crates/testlang/outputs/cargo/slang_testlang_node_addon/Cargo.toml @@ -36,7 +36,6 @@ slang_napi_interfaces = [ napi-build = { workspace = true } [dependencies] -ariadne = { workspace = true } napi = { workspace = true } napi-derive = { workspace = true } nom = { workspace = true } diff --git a/crates/testlang/outputs/npm/package/src/generated/napi-bindings/generated/index.d.ts b/crates/testlang/outputs/npm/package/src/generated/napi-bindings/generated/index.d.ts index 347919174b..19184237c2 100644 --- a/crates/testlang/outputs/npm/package/src/generated/napi-bindings/generated/index.d.ts +++ b/crates/testlang/outputs/npm/package/src/generated/napi-bindings/generated/index.d.ts @@ -130,10 +130,29 @@ export namespace cursor { query(queries: Array): query.QueryResultIterator; } } +export namespace diagnostic { + /** + * Severity of the compiler diagnostic. + * + * Explicitly compatible with the LSP protocol. + */ + export enum Severity { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, + } + export class Diagnostic { + severity(): Severity; + textRange(): text_index.TextRange; + message(): string; + code(): string; + } +} export namespace parse_error { export class ParseError { get textRange(): text_index.TextRange; - toErrorReport(sourceId: string, source: string, withColor: boolean): string; + toDiagnostic(): diagnostic.Diagnostic; } } export namespace parse_output { diff --git a/crates/testlang/outputs/npm/package/src/generated/napi-bindings/generated/index.js b/crates/testlang/outputs/npm/package/src/generated/napi-bindings/generated/index.js index e29227b26d..97a51168fd 100644 --- a/crates/testlang/outputs/npm/package/src/generated/napi-bindings/generated/index.js +++ b/crates/testlang/outputs/npm/package/src/generated/napi-bindings/generated/index.js @@ -291,12 +291,14 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`); } -const { kinds, language, cst, cursor, parse_error, parse_output, query, text_index, ast_internal } = nativeBinding; +const { kinds, language, cst, cursor, diagnostic, parse_error, parse_output, query, text_index, ast_internal } = + nativeBinding; module.exports.kinds = kinds; module.exports.language = language; module.exports.cst = cst; module.exports.cursor = cursor; +module.exports.diagnostic = diagnostic; module.exports.parse_error = parse_error; module.exports.parse_output = parse_output; module.exports.query = query; diff --git a/crates/testlang/outputs/npm/tests/src/tests/errors.ts b/crates/testlang/outputs/npm/tests/src/tests/errors.ts index f3a76e13ef..1e0a6590cc 100644 --- a/crates/testlang/outputs/npm/tests/src/tests/errors.ts +++ b/crates/testlang/outputs/npm/tests/src/tests/errors.ts @@ -1,5 +1,7 @@ import { Language } from "@slang-private/slang-testlang/language"; import { RuleKind } from "@slang-private/slang-testlang/kinds"; +import { TextIndex } from "@slang-private/slang-testlang/text_index"; +import { diagnostic } from "@slang-private/slang-testlang/generated"; test("render error reports", () => { const source = "tree [AB;"; @@ -8,18 +10,11 @@ test("render error reports", () => { const errors = language.parse(RuleKind.SourceUnit, source).errors(); expect(errors).toHaveLength(1); - const report = errors[0]!.toErrorReport("test.testlang", source, /* withColor */ false); - expect(report).toEqual( - ` -Error: Expected Identifier or StringLiteral or TreeKeyword. - ╭─[test.testlang:1:6] - │ - 1 │ tree [AB; - │ ──┬─ - │ ╰─── Error occurred here. -───╯ - `.trim(), - ); + const diag = errors[0]!.toDiagnostic(); + expect(diag.severity()).toBe(diagnostic.Severity.Error); + expect(diag.message()).toBe("Expected Identifier or StringLiteral or TreeKeyword."); + expect(diag.textRange().start).toEqual({ utf8: 5, utf16: 5, char: 5 } satisfies TextIndex); + expect(diag.textRange().end).toEqual({ utf8: 9, utf16: 9, char: 9 } satisfies TextIndex); }); test("invalid semantic version", () => {