Skip to content

Commit

Permalink
feat(error-handling): handle errors thrown by callback argument
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed Dec 1, 2022
1 parent ddd926b commit 9ca1390
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 26 deletions.
2 changes: 1 addition & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface TransformResult {
}
export function localImport(callback: (path: string) => string): Plugin
export class Plugin {
/** Public plugin properties https://rollupjs.org/guide/en/#properties */
/** Name of the Rollup plugin https://rollupjs.org/guide/en/#name */
name: string
/** Build hook: https://rollupjs.org/guide/en/#transform */
transform(sourceCode: string, filename: string): TransformResult
Expand Down
32 changes: 23 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use napi::{Env, Error, JsFunction, JsString, Ref};
use napi::{Env, Error, JsError, JsFunction, JsString, JsUnknown, Ref};
use napi_derive::napi;

mod parser;
Expand Down Expand Up @@ -32,15 +32,18 @@ impl Plugin {
let transform_paths = Box::new(move |path: String| {
let args: [JsString; 1] = [env.create_string(&path).unwrap()];

let modified = callback
.call(None, &args)
.unwrap()
.coerce_to_string()
.unwrap()
.into_utf8()
.unwrap();
match callback.call(None, &args) {
Ok(new_path) => Ok(to_string(new_path)),
Err(e) => {
// Capture the error thrown from JS side
let error_from_callback = to_string(JsError::from(e).into_unknown(env));

return modified.as_str().unwrap().to_string();
return Err(format!(
"Callback threw error {:?} when called with {:?}",
&error_from_callback, &path
));
}
}
});

parser::parse(&source_code, &filename, transform_paths)
Expand Down Expand Up @@ -77,3 +80,14 @@ pub fn local_import(env: Env, callback: JsFunction) -> Result<Plugin, Error> {
callback_reference,
})
}

fn to_string(js_unknown: JsUnknown) -> String {
js_unknown
.coerce_to_string()
.unwrap()
.into_utf8()
.unwrap()
.as_str()
.unwrap()
.to_string()
}
47 changes: 35 additions & 12 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,22 @@ use swc_core::{

use crate::TransformResult;

type Callback = Box<dyn Fn(String) -> String>;
type Callback = Box<dyn Fn(String) -> Result<String, String>>;

struct Visitor {
callback: Callback,
errors: Vec<String>,
}

impl VisitMut for Visitor {
fn visit_mut_export_all(&mut self, node: &mut ExportAll) {
let path = node.src.value.to_string();

if is_local_import(&path) {
let new_path = (&self.callback)(path);
node.src.value = new_path.into()
match (&self.callback)(path) {
Ok(new_path) => node.src.value = new_path.into(),
Err(error) => self.errors.push(error),
}
}
}

Expand All @@ -46,17 +49,21 @@ impl VisitMut for Visitor {
};

if is_local_import(&path) {
let new_path = (&self.callback)(path);
node.src.as_mut().unwrap().value = new_path.into()
match (&self.callback)(path) {
Ok(new_path) => node.src.as_mut().unwrap().value = new_path.into(),
Err(error) => self.errors.push(error),
};
}
}

fn visit_mut_import_decl(&mut self, node: &mut ImportDecl) {
let path = node.src.value.to_string();

if is_local_import(&path) {
let new_path = (&self.callback)(path);
node.src.value = new_path.into()
match (&self.callback)(path) {
Ok(new_path) => node.src.value = new_path.into(),
Err(error) => self.errors.push(error),
};
}
}
}
Expand All @@ -65,8 +72,8 @@ fn is_local_import(path: &str) -> bool {
return path.starts_with("./") || path.starts_with("../");
}

fn visitor(callback: Callback) -> impl Fold {
as_folder(Visitor { callback })
fn as_visitor(visitor: &mut Visitor) -> impl Fold + '_ {
as_folder(visitor)
}

pub fn parse(code: &str, filename: &str, callback: Callback) -> napi::Result<TransformResult> {
Expand All @@ -78,6 +85,11 @@ pub fn parse(code: &str, filename: &str, callback: Callback) -> napi::Result<Tra

let compiler = Compiler::new(source_map);

let mut visitor = Visitor {
callback,
errors: vec![],
};

let transformed = compiler.process_js_with_custom_pass(
source_file,
None,
Expand All @@ -87,10 +99,21 @@ pub fn parse(code: &str, filename: &str, callback: Callback) -> napi::Result<Tra
source_maps: Some(SourceMapsConfig::Bool(true)),
..Default::default()
},
|_, _| visitor(callback),
|_, _| as_visitor(&mut visitor),
|_, _| noop(),
);

if visitor.errors.len() > 0 {
return Err(napi::Error::new(
napi::Status::GenericFailure,
format!(
"Run into {:?} error(s): [{:?}].",
visitor.errors.len(),
visitor.errors.join(",")
),
));
}

handler.abort_if_errors();

let TransformOutput {
Expand Down Expand Up @@ -126,7 +149,7 @@ mod tests {
let mut new_path: String = path.to_string();
new_path.push_str(".js");

new_path
Ok(new_path)
})
}

Expand All @@ -137,7 +160,7 @@ mod tests {
"some-file.js",
Box::new(|path: String| {
assert_eq!(path, "./local-file");
path
Ok(path)
}),
)
.unwrap();
Expand Down
28 changes: 24 additions & 4 deletions test/error-handling.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,33 @@ test("throws useful error message when callback is not function", () => {
);
});

test.todo("throws useful error message when callback throws", () => {
test("throws useful error message when callback throws", () => {
const plugin = localImport(() => {
throw new Error("Error from callback");
throw new Error("Throwing some error from callback");
});
cleanups.push(plugin.buildEnd);

expect(() =>
plugin.transform('import fs from "./some-file";')
).toThrowErrorMatchingInlineSnapshot();
plugin.transform('import A from "./some-file";', "file.js")
).toThrowErrorMatchingInlineSnapshot('"Run into 1 error(s): [\\"Callback threw error \\\\\\"Error: Throwing some error from callback\\\\\\" when called with \\\\\\"./some-file\\\\\\"\\"]."');
});

test("includes all errors thrown by callback in error message", () => {
const plugin = localImport(() => {
throw new Error("Throwing some error from callback");
});
cleanups.push(plugin.buildEnd);

expect(() =>
plugin.transform(
`
export * from "./local-file-1";
export { a } from "./local-file-2";
import b from "./local-file-3";
import { c } from "./local-file-4";
import "./local-file-5";
`,
"file.js"
)
).toThrowErrorMatchingInlineSnapshot('"Run into 5 error(s): [\\"Callback threw error \\\\\\"Error: Throwing some error from callback\\\\\\" when called with \\\\\\"./local-file-1\\\\\\",Callback threw error \\\\\\"Error: Throwing some error from callback\\\\\\" when called with \\\\\\"./local-file-2\\\\\\",Callback threw error \\\\\\"Error: Throwing some error from callback\\\\\\" when called with \\\\\\"./local-file-3\\\\\\",Callback threw error \\\\\\"Error: Throwing some error from callback\\\\\\" when called with \\\\\\"./local-file-4\\\\\\",Callback threw error \\\\\\"Error: Throwing some error from callback\\\\\\" when called with \\\\\\"./local-file-5\\\\\\"\\"]."');
});

0 comments on commit 9ca1390

Please sign in to comment.