Show source names in diagnostics
This commit is contained in:
parent
cc5ab63922
commit
a2a6dee025
|
|
@ -4,19 +4,22 @@ use std::{
|
|||
process::ExitCode,
|
||||
};
|
||||
|
||||
use decodal_core::{Data, Diagnostic, DiagnosticKind, Engine, LoadedSource, SourceLoader, Span};
|
||||
use decodal_core::{
|
||||
Data, Diagnostic, DiagnosticKind, Engine, LoadedSource, SourceId, SourceLoader, Span,
|
||||
format_diagnostic_with,
|
||||
};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(error) => {
|
||||
print_diagnostic(&error);
|
||||
eprintln!("{error}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), Diagnostic> {
|
||||
fn run() -> Result<(), String> {
|
||||
let mut args = env::args().skip(1);
|
||||
let first = args.next().unwrap_or_else(|| String::from("--help"));
|
||||
match first.as_str() {
|
||||
|
|
@ -51,12 +54,19 @@ fn run() -> Result<(), Diagnostic> {
|
|||
}
|
||||
}
|
||||
|
||||
fn materialize_path(path: &str) -> Result<Data, Diagnostic> {
|
||||
let root = read_root_source(path)?;
|
||||
fn materialize_path(path: &str) -> Result<Data, String> {
|
||||
let root = read_root_source(path).map_err(format_raw_diagnostic)?;
|
||||
let root_name = root.name.clone();
|
||||
let mut engine = Engine::new(FsLoader);
|
||||
let module = engine.add_root_source(root.key, root.name, &root.source)?;
|
||||
let value = engine.eval_module(module)?;
|
||||
engine.materialize(&value)
|
||||
let module = engine
|
||||
.add_root_source(root.key, root.name, &root.source)
|
||||
.map_err(|error| format_diagnostic_with_root(&error, &root_name))?;
|
||||
let value = engine
|
||||
.eval_module(module)
|
||||
.map_err(|error| engine.format_diagnostic(&error))?;
|
||||
engine
|
||||
.materialize(&value)
|
||||
.map_err(|error| engine.format_diagnostic(&error))
|
||||
}
|
||||
|
||||
fn read_root_source(path: &str) -> Result<LoadedSource, Diagnostic> {
|
||||
|
|
@ -139,24 +149,12 @@ fn print_help() {
|
|||
println!(" decodal - Read DCDL source from stdin");
|
||||
}
|
||||
|
||||
fn print_diagnostic(error: &Diagnostic) {
|
||||
eprintln!(
|
||||
"error[{kind:?}] {source}:{start}..{end}: {message}",
|
||||
kind = error.kind,
|
||||
source = error.span.source.0,
|
||||
start = error.span.start,
|
||||
end = error.span.end,
|
||||
message = error.message,
|
||||
);
|
||||
for label in &error.labels {
|
||||
eprintln!(
|
||||
" note {source}:{start}..{end}: {message}",
|
||||
source = label.span.source.0,
|
||||
start = label.span.start,
|
||||
end = label.span.end,
|
||||
message = label.message,
|
||||
);
|
||||
}
|
||||
fn format_diagnostic_with_root(error: &Diagnostic, root_name: &str) -> String {
|
||||
format_diagnostic_with(error, |source| (source == SourceId(0)).then_some(root_name))
|
||||
}
|
||||
|
||||
fn format_raw_diagnostic(error: Diagnostic) -> String {
|
||||
format_diagnostic_with(&error, |_| None)
|
||||
}
|
||||
|
||||
fn print_data(data: &Data, indent: usize) {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,17 @@ impl<L: SourceLoader> Engine<L> {
|
|||
self.materialize_with_path(value, &mut Vec::new())
|
||||
}
|
||||
|
||||
pub fn source_name(&self, source: SourceId) -> Option<&str> {
|
||||
self.modules
|
||||
.iter()
|
||||
.find(|module| module.source == source)
|
||||
.map(|module| module.name.as_str())
|
||||
}
|
||||
|
||||
pub fn format_diagnostic(&self, diagnostic: &Diagnostic) -> String {
|
||||
format_diagnostic_with(diagnostic, |source| self.source_name(source))
|
||||
}
|
||||
|
||||
fn materialize_with_path(
|
||||
&mut self,
|
||||
value: &RuntimeValue,
|
||||
|
|
@ -1163,6 +1174,39 @@ fn primitive_type(name: &str) -> Option<PrimitiveType> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn format_diagnostic_with<'a>(
|
||||
diagnostic: &Diagnostic,
|
||||
mut source_name: impl FnMut(SourceId) -> Option<&'a str>,
|
||||
) -> String {
|
||||
let mut out = format!(
|
||||
"{:?} at {}:{}..{}: {}",
|
||||
diagnostic.kind,
|
||||
format_source(diagnostic.span.source, &mut source_name),
|
||||
diagnostic.span.start,
|
||||
diagnostic.span.end,
|
||||
diagnostic.message,
|
||||
);
|
||||
for label in &diagnostic.labels {
|
||||
out.push_str(&format!(
|
||||
"\nnote at {}:{}..{}: {}",
|
||||
format_source(label.span.source, &mut source_name),
|
||||
label.span.start,
|
||||
label.span.end,
|
||||
label.message,
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn format_source<'a>(
|
||||
source: SourceId,
|
||||
source_name: &mut impl FnMut(SourceId) -> Option<&'a str>,
|
||||
) -> String {
|
||||
source_name(source)
|
||||
.map(String::from)
|
||||
.unwrap_or_else(|| format!("source {}", source.0))
|
||||
}
|
||||
|
||||
fn constraint_primary_span(constraints: &[ConstraintEntry]) -> Span {
|
||||
constraints
|
||||
.first()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ pub use ast::{Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Literal, Param};
|
|||
pub use constraints::normalize_constraints;
|
||||
pub use diagnostic::{Diagnostic, DiagnosticKind, Result};
|
||||
pub use embedding::{HostField, HostValue};
|
||||
pub use eval::Engine;
|
||||
pub use eval::{Engine, format_diagnostic_with};
|
||||
pub use lexer::{Lexer, Token, TokenKind};
|
||||
pub use module::{EmptyLoader, LoadedSource, Module, SourceLoader};
|
||||
pub use parser::{ParseOutput, Parser, SourceForm, parse_source, parse_source_with_source_id};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use decodal_core::{
|
||||
Data, Diagnostic, DiagnosticKind, EmptyLoader, Engine, LoadedSource, SourceLoader, Span,
|
||||
Data, Diagnostic, DiagnosticKind, EmptyLoader, Engine, LoadedSource, SourceId, SourceLoader,
|
||||
Span, format_diagnostic_with,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
|
|
@ -24,11 +25,18 @@ fn encode_result(result: Result<String, String>) -> String {
|
|||
|
||||
fn evaluate_inner(source: &str) -> Result<String, String> {
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
let module = engine
|
||||
.add_root_source("playground", "playground", source)
|
||||
.map_err(format_diagnostic)?;
|
||||
let value = engine.eval_module(module).map_err(format_diagnostic)?;
|
||||
let data = engine.materialize(&value).map_err(format_diagnostic)?;
|
||||
let module = match engine.add_root_source("playground", "playground", source) {
|
||||
Ok(module) => module,
|
||||
Err(error) => return Err(format_diagnostic_with_root(&error, "playground")),
|
||||
};
|
||||
let value = match engine.eval_module(module) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(engine.format_diagnostic(&error)),
|
||||
};
|
||||
let data = match engine.materialize(&value) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return Err(engine.format_diagnostic(&error)),
|
||||
};
|
||||
Ok(format_data(&data, 0))
|
||||
}
|
||||
|
||||
|
|
@ -48,11 +56,18 @@ fn evaluate_project_inner(entry: &str, files_json: &str) -> Result<String, Strin
|
|||
.ok_or_else(|| format!("entry file `{entry}` was not found"))?;
|
||||
|
||||
let mut engine = Engine::new(VirtualLoader { files });
|
||||
let module = engine
|
||||
.add_root_source(entry.clone(), entry.clone(), &source)
|
||||
.map_err(format_diagnostic)?;
|
||||
let value = engine.eval_module(module).map_err(format_diagnostic)?;
|
||||
let data = engine.materialize(&value).map_err(format_diagnostic)?;
|
||||
let module = match engine.add_root_source(entry.clone(), entry.clone(), &source) {
|
||||
Ok(module) => module,
|
||||
Err(error) => return Err(format_diagnostic_with_root(&error, &entry)),
|
||||
};
|
||||
let value = match engine.eval_module(module) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(engine.format_diagnostic(&error)),
|
||||
};
|
||||
let data = match engine.materialize(&value) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return Err(engine.format_diagnostic(&error)),
|
||||
};
|
||||
Ok(format_data(&data, 0))
|
||||
}
|
||||
|
||||
|
|
@ -124,22 +139,10 @@ fn normalize_path(path: &str) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
fn format_diagnostic(diagnostic: decodal_core::Diagnostic) -> String {
|
||||
let mut out = format!(
|
||||
"{:?} at {}:{}..{}: {}",
|
||||
diagnostic.kind,
|
||||
diagnostic.span.source.0,
|
||||
diagnostic.span.start,
|
||||
diagnostic.span.end,
|
||||
diagnostic.message,
|
||||
);
|
||||
for label in diagnostic.labels {
|
||||
out.push_str(&format!(
|
||||
"\nnote at {}:{}..{}: {}",
|
||||
label.span.source.0, label.span.start, label.span.end, label.message,
|
||||
));
|
||||
}
|
||||
out
|
||||
fn format_diagnostic_with_root(diagnostic: &decodal_core::Diagnostic, root_name: &str) -> String {
|
||||
format_diagnostic_with(diagnostic, |source| {
|
||||
(source == SourceId(0)).then_some(root_name)
|
||||
})
|
||||
}
|
||||
|
||||
fn format_data(data: &Data, indent: usize) -> String {
|
||||
|
|
@ -237,4 +240,17 @@ mod tests {
|
|||
let output = evaluate_project_inner("main.dcdl", files).unwrap();
|
||||
assert!(output.contains("\"port\": 9443"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_diagnostics_use_virtual_file_names() {
|
||||
let files = r#"{
|
||||
"main.dcdl":"let dep = import \"./schemas/service.dcdl\"; in dep.Service & { port = 80; }",
|
||||
"schemas/service.dcdl":"Service = { port = Int & > 443 default 8443; }"
|
||||
}"#;
|
||||
let error = evaluate_project_inner("main.dcdl", files).unwrap_err();
|
||||
assert!(error.contains("main.dcdl:"));
|
||||
assert!(error.contains("schemas/service.dcdl:"));
|
||||
assert!(!error.contains("source 0:"));
|
||||
assert!(!error.contains("source 1:"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ DiagnosticLabel {
|
|||
```
|
||||
|
||||
`span` は primary location を示す。
|
||||
表示時には `Span.source` を source id のまま出すのではなく、可能な限り file path や virtual file name に解決する。
|
||||
`labels` は同じ error に関係する追加 location を示す。
|
||||
合成や materialize の失敗では、衝突した constraint、value、default、または処理中 field path を label に含める。
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue
Block a user