Skip to content

Commit 61c21aa

Browse files
WillLillisclason
authored andcommitted
refactor(generate)!: include path when available in IO errors
1 parent 7eb23d9 commit 61c21aa

File tree

2 files changed

+87
-57
lines changed

2 files changed

+87
-57
lines changed

crates/generate/src/generate.rs

Lines changed: 84 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ pub type GenerateResult<T> = Result<T, GenerateError>;
8080
pub enum GenerateError {
8181
#[error("Error with specified path -- {0}")]
8282
GrammarPath(String),
83-
#[error("{0}")]
84-
IO(String),
83+
#[error(transparent)]
84+
IO(IoError),
8585
#[cfg(feature = "load")]
8686
#[error(transparent)]
8787
LoadGrammarFile(#[from] LoadGrammarError),
@@ -100,9 +100,28 @@ pub enum GenerateError {
100100
SuperTypeCycle(#[from] SuperTypeCycleError),
101101
}
102102

103-
impl From<std::io::Error> for GenerateError {
104-
fn from(value: std::io::Error) -> Self {
105-
Self::IO(value.to_string())
103+
#[derive(Debug, Error, Serialize)]
104+
pub struct IoError {
105+
pub error: String,
106+
pub path: Option<String>,
107+
}
108+
109+
impl IoError {
110+
fn new(error: &std::io::Error, path: Option<&Path>) -> Self {
111+
Self {
112+
error: error.to_string(),
113+
path: path.map(|p| p.to_string_lossy().to_string()),
114+
}
115+
}
116+
}
117+
118+
impl std::fmt::Display for IoError {
119+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120+
write!(f, "{}", self.error)?;
121+
if let Some(ref path) = self.path {
122+
write!(f, " ({path})")?;
123+
}
124+
Ok(())
106125
}
107126
}
108127

@@ -117,27 +136,20 @@ pub enum LoadGrammarError {
117136
#[error("Failed to load grammar.js -- {0}")]
118137
LoadJSGrammarFile(#[from] JSError),
119138
#[error("Failed to load grammar.json -- {0}")]
120-
IO(String),
139+
IO(IoError),
121140
#[error("Unknown grammar file extension: {0:?}")]
122141
FileExtension(PathBuf),
123142
}
124143

125-
#[cfg(feature = "load")]
126-
impl From<std::io::Error> for LoadGrammarError {
127-
fn from(value: std::io::Error) -> Self {
128-
Self::IO(value.to_string())
129-
}
130-
}
131-
132144
#[cfg(feature = "load")]
133145
#[derive(Debug, Error, Serialize)]
134146
pub enum ParseVersionError {
135147
#[error("{0}")]
136148
Version(String),
137149
#[error("{0}")]
138150
JSON(String),
139-
#[error("{0}")]
140-
IO(String),
151+
#[error(transparent)]
152+
IO(IoError),
141153
}
142154

143155
#[cfg(feature = "load")]
@@ -152,8 +164,21 @@ pub enum JSError {
152164
JSRuntimeUtf8 { runtime: String, error: String },
153165
#[error("`{runtime}` process exited with status {code}")]
154166
JSRuntimeExit { runtime: String, code: i32 },
155-
#[error("{0}")]
156-
IO(String),
167+
#[error("Failed to open stdin for `{runtime}`")]
168+
JSRuntimeStdin { runtime: String },
169+
#[error("Failed to write {item} to `{runtime}`'s stdin -- {error}")]
170+
JSRuntimeWrite {
171+
runtime: String,
172+
item: String,
173+
error: String,
174+
},
175+
#[error("Failed to read output from `{runtime}` -- {error}")]
176+
JSRuntimeRead { runtime: String, error: String },
177+
#[error(transparent)]
178+
IO(IoError),
179+
#[cfg(feature = "qjs-rt")]
180+
#[error("Failed to get relative path")]
181+
RelativePath,
157182
#[error("Could not parse this package's version as semver -- {0}")]
158183
Semver(String),
159184
#[error("Failed to serialze grammar JSON -- {0}")]
@@ -163,13 +188,6 @@ pub enum JSError {
163188
QuickJS(String),
164189
}
165190

166-
#[cfg(feature = "load")]
167-
impl From<std::io::Error> for JSError {
168-
fn from(value: std::io::Error) -> Self {
169-
Self::IO(value.to_string())
170-
}
171-
}
172-
173191
#[cfg(feature = "load")]
174192
impl From<serde_json::Error> for JSError {
175193
fn from(value: serde_json::Error) -> Self {
@@ -230,7 +248,8 @@ where
230248
.try_exists()
231249
.map_err(|e| GenerateError::GrammarPath(e.to_string()))?
232250
{
233-
fs::create_dir_all(&path_buf)?;
251+
fs::create_dir_all(&path_buf)
252+
.map_err(|e| GenerateError::IO(IoError::new(&e, Some(path_buf.as_path()))))?;
234253
repo_path = path_buf;
235254
repo_path.join("grammar.js")
236255
} else {
@@ -247,15 +266,12 @@ where
247266
let header_path = src_path.join("tree_sitter");
248267

249268
// Ensure that the output directory exists
250-
fs::create_dir_all(&src_path)?;
269+
fs::create_dir_all(&src_path)
270+
.map_err(|e| GenerateError::IO(IoError::new(&e, Some(src_path.as_path()))))?;
251271

252272
if grammar_path.file_name().unwrap() != "grammar.json" {
253-
fs::write(src_path.join("grammar.json"), &grammar_json).map_err(|e| {
254-
GenerateError::IO(format!(
255-
"Failed to write grammar.json to {} -- {e}",
256-
src_path.display()
257-
))
258-
})?;
273+
fs::write(src_path.join("grammar.json"), &grammar_json)
274+
.map_err(|e| GenerateError::IO(IoError::new(&e, Some(src_path.as_path()))))?;
259275
}
260276

261277
// If our job is only to generate `grammar.json` and not `parser.c`, stop here.
@@ -297,7 +313,8 @@ where
297313

298314
write_file(&src_path.join("parser.c"), c_code)?;
299315
write_file(&src_path.join("node-types.json"), node_types_json)?;
300-
fs::create_dir_all(&header_path)?;
316+
fs::create_dir_all(&header_path)
317+
.map_err(|e| GenerateError::IO(IoError::new(&e, Some(header_path.as_path()))))?;
301318
write_file(&header_path.join("alloc.h"), ALLOC_HEADER)?;
302319
write_file(&header_path.join("array.h"), ARRAY_HEADER)?;
303320
write_file(&header_path.join("parser.h"), PARSER_HEADER)?;
@@ -413,9 +430,8 @@ fn read_grammar_version(repo_path: &Path) -> Result<Option<Version>, ParseVersio
413430
let json = path
414431
.exists()
415432
.then(|| {
416-
let contents = fs::read_to_string(path.as_path()).map_err(|e| {
417-
ParseVersionError::IO(format!("Failed to read `{}` -- {e}", path.display()))
418-
})?;
433+
let contents = fs::read_to_string(path.as_path())
434+
.map_err(|e| ParseVersionError::IO(IoError::new(&e, Some(path.as_path()))))?;
419435
serde_json::from_str::<TreeSitterJson>(&contents).map_err(|e| {
420436
ParseVersionError::JSON(format!("Failed to parse `{}` -- {e}", path.display()))
421437
})
@@ -449,14 +465,16 @@ pub fn load_grammar_file(
449465
}
450466
match grammar_path.extension().and_then(|e| e.to_str()) {
451467
Some("js") => Ok(load_js_grammar_file(grammar_path, js_runtime)?),
452-
Some("json") => Ok(fs::read_to_string(grammar_path)?),
468+
Some("json") => Ok(fs::read_to_string(grammar_path)
469+
.map_err(|e| LoadGrammarError::IO(IoError::new(&e, Some(grammar_path))))?),
453470
_ => Err(LoadGrammarError::FileExtension(grammar_path.to_owned()))?,
454471
}
455472
}
456473

457474
#[cfg(feature = "load")]
458475
fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResult<String> {
459-
let grammar_path = dunce::canonicalize(grammar_path)?;
476+
let grammar_path = dunce::canonicalize(grammar_path)
477+
.map_err(|e| JSError::IO(IoError::new(&e, Some(grammar_path))))?;
460478

461479
#[cfg(feature = "qjs-rt")]
462480
if js_runtime == Some("native") {
@@ -497,7 +515,9 @@ fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResu
497515
let mut js_stdin = js_process
498516
.stdin
499517
.take()
500-
.ok_or_else(|| JSError::IO(format!("Failed to open stdin for `{js_runtime}`")))?;
518+
.ok_or_else(|| JSError::JSRuntimeStdin {
519+
runtime: js_runtime.to_string(),
520+
})?;
501521

502522
let cli_version = Version::parse(env!("CARGO_PKG_VERSION"))?;
503523
write!(
@@ -507,21 +527,26 @@ fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResu
507527
globalThis.TREE_SITTER_CLI_VERSION_PATCH = {};",
508528
cli_version.major, cli_version.minor, cli_version.patch,
509529
)
510-
.map_err(|e| {
511-
JSError::IO(format!(
512-
"Failed to write tree-sitter version to `{js_runtime}`'s stdin -- {e}"
513-
))
514-
})?;
515-
js_stdin.write(include_bytes!("./dsl.js")).map_err(|e| {
516-
JSError::IO(format!(
517-
"Failed to write grammar dsl to `{js_runtime}`'s stdin -- {e}"
518-
))
530+
.map_err(|e| JSError::JSRuntimeWrite {
531+
runtime: js_runtime.to_string(),
532+
item: "tree-sitter version".to_string(),
533+
error: e.to_string(),
519534
})?;
535+
js_stdin
536+
.write(include_bytes!("./dsl.js"))
537+
.map_err(|e| JSError::JSRuntimeWrite {
538+
runtime: js_runtime.to_string(),
539+
item: "grammar dsl".to_string(),
540+
error: e.to_string(),
541+
})?;
520542
drop(js_stdin);
521543

522544
let output = js_process
523545
.wait_with_output()
524-
.map_err(|e| JSError::IO(format!("Failed to read output from `{js_runtime}` -- {e}")))?;
546+
.map_err(|e| JSError::JSRuntimeRead {
547+
runtime: js_runtime.to_string(),
548+
error: e.to_string(),
549+
})?;
525550
match output.status.code() {
526551
Some(0) => {
527552
let stdout = String::from_utf8(output.stdout).map_err(|e| JSError::JSRuntimeUtf8 {
@@ -537,9 +562,15 @@ fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResu
537562
grammar_json = &stdout[pos + 1..];
538563

539564
let mut stdout = std::io::stdout().lock();
540-
stdout.write_all(node_output.as_bytes())?;
541-
stdout.write_all(b"\n")?;
542-
stdout.flush()?;
565+
stdout
566+
.write_all(node_output.as_bytes())
567+
.map_err(|e| JSError::IO(IoError::new(&e, None)))?;
568+
stdout
569+
.write_all(b"\n")
570+
.map_err(|e| JSError::IO(IoError::new(&e, None)))?;
571+
stdout
572+
.flush()
573+
.map_err(|e| JSError::IO(IoError::new(&e, None)))?;
543574
}
544575

545576
Ok(serde_json::to_string_pretty(&serde_json::from_str::<
@@ -559,8 +590,7 @@ fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResu
559590

560591
#[cfg(feature = "load")]
561592
pub fn write_file(path: &Path, body: impl AsRef<[u8]>) -> GenerateResult<()> {
562-
fs::write(path, body)
563-
.map_err(|e| GenerateError::IO(format!("Failed to write {:?} -- {e}", path.file_name())))
593+
fs::write(path, body).map_err(|e| GenerateError::IO(IoError::new(&e, Some(path))))
564594
}
565595

566596
#[cfg(test)]

crates/generate/src/quickjs.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rquickjs::{
1010
Context, Ctx, Function, Module, Object, Runtime, Type, Value,
1111
};
1212

13-
use super::{JSError, JSResult};
13+
use super::{IoError, JSError, JSResult};
1414

1515
const DSL: &[u8] = include_bytes!("dsl.js");
1616

@@ -266,10 +266,10 @@ pub fn execute_native_runtime(grammar_path: &Path) -> JSResult<String> {
266266
let loader = ScriptLoader::default().with_extension("mjs");
267267
runtime.set_loader(resolver, loader);
268268

269-
let cwd = std::env::current_dir()?;
269+
let cwd = std::env::current_dir().map_err(|e| JSError::IO(IoError::new(&e, None)))?;
270270
let relative_path = pathdiff::diff_paths(grammar_path, &cwd)
271271
.map(|p| p.to_string_lossy().to_string())
272-
.ok_or_else(|| JSError::IO("Failed to get relative path".to_string()))?;
272+
.ok_or(JSError::RelativePath)?;
273273

274274
context.with(|ctx| -> JSResult<String> {
275275
let globals = ctx.globals();

0 commit comments

Comments
 (0)