Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dx: better error message for 'Cannot resolve module' #3402

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 47 additions & 30 deletions cli/file_fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ impl SourceFileFetcher {
pub fn fetch_source_file_async(
self: &Self,
specifier: &ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>,
) -> Pin<Box<SourceFileFuture>> {
let module_url = specifier.as_url().to_owned();
debug!("fetch_source_file. specifier {} ", &module_url);
Expand All @@ -156,12 +157,16 @@ impl SourceFileFetcher {
.then(move |result| {
let mut out = match result.map_err(|err| {
if err.kind() == ErrorKind::NotFound {
// For NotFound, change the message to something better.
DenoError::new(
ErrorKind::NotFound,
format!("Cannot resolve module \"{}\"", module_url.to_string()),
)
.into()
let msg = if let Some(referrer) = maybe_referrer {
format!(
"Cannot resolve module \"{}\" from \"{}\"",
module_url.to_string(),
referrer
)
} else {
format!("Cannot resolve module \"{}\"", module_url.to_string())
};
DenoError::new(ErrorKind::NotFound, msg).into()
} else {
err
}
Expand Down Expand Up @@ -1011,10 +1016,12 @@ mod tests {
);

// first download
tokio_util::run(fetcher.fetch_source_file_async(&specifier).then(|r| {
assert!(r.is_ok());
futures::future::ok(())
}));
tokio_util::run(fetcher.fetch_source_file_async(&specifier, None).then(
|r| {
assert!(r.is_ok());
futures::future::ok(())
},
));

let result = fs::File::open(&headers_file_name);
assert!(result.is_ok());
Expand All @@ -1026,10 +1033,12 @@ mod tests {
// download file again, it should use already fetched file even though `use_disk_cache` is set to
// false, this can be verified using source header file creation timestamp (should be
// the same as after first download)
tokio_util::run(fetcher.fetch_source_file_async(&specifier).then(|r| {
assert!(r.is_ok());
futures::future::ok(())
}));
tokio_util::run(fetcher.fetch_source_file_async(&specifier, None).then(
|r| {
assert!(r.is_ok());
futures::future::ok(())
},
));

let result = fs::File::open(&headers_file_name);
assert!(result.is_ok());
Expand Down Expand Up @@ -1443,20 +1452,24 @@ mod tests {
// Test failure case.
let specifier =
ModuleSpecifier::resolve_url(file_url!("/baddir/hello.ts")).unwrap();
tokio_util::run(fetcher.fetch_source_file_async(&specifier).then(|r| {
assert!(r.is_err());
futures::future::ok(())
}));
tokio_util::run(fetcher.fetch_source_file_async(&specifier, None).then(
|r| {
assert!(r.is_err());
futures::future::ok(())
},
));

let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("js/main.ts")
.to_owned();
let specifier =
ModuleSpecifier::resolve_url_or_path(p.to_str().unwrap()).unwrap();
tokio_util::run(fetcher.fetch_source_file_async(&specifier).then(|r| {
assert!(r.is_ok());
futures::future::ok(())
}));
tokio_util::run(fetcher.fetch_source_file_async(&specifier, None).then(
|r| {
assert!(r.is_ok());
futures::future::ok(())
},
));
}

#[test]
Expand All @@ -1467,20 +1480,24 @@ mod tests {
// Test failure case.
let specifier =
ModuleSpecifier::resolve_url(file_url!("/baddir/hello.ts")).unwrap();
tokio_util::run(fetcher.fetch_source_file_async(&specifier).then(|r| {
assert!(r.is_err());
futures::future::ok(())
}));
tokio_util::run(fetcher.fetch_source_file_async(&specifier, None).then(
|r| {
assert!(r.is_err());
futures::future::ok(())
},
));

let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("js/main.ts")
.to_owned();
let specifier =
ModuleSpecifier::resolve_url_or_path(p.to_str().unwrap()).unwrap();
tokio_util::run(fetcher.fetch_source_file_async(&specifier).then(|r| {
assert!(r.is_ok());
futures::future::ok(())
}));
tokio_util::run(fetcher.fetch_source_file_async(&specifier, None).then(
|r| {
assert!(r.is_ok());
futures::future::ok(())
},
));
}

#[test]
Expand Down
3 changes: 2 additions & 1 deletion cli/global_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,14 @@ impl ThreadSafeGlobalState {
pub fn fetch_compiled_module(
self: &Self,
module_specifier: &ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>,
) -> impl Future<Output = Result<CompiledModule, ErrBox>> {
let state1 = self.clone();
let state2 = self.clone();

self
.file_fetcher
.fetch_source_file_async(&module_specifier)
.fetch_source_file_async(&module_specifier, maybe_referrer)
.and_then(move |out| match out.media_type {
msg::MediaType::Unknown => state1.js_compiler.compile_async(&out),
msg::MediaType::Json => state1.json_compiler.compile_async(&out),
Expand Down
7 changes: 4 additions & 3 deletions cli/js/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ class SourceFile {

/** Cache the source file to be able to be retrieved by `moduleSpecifier` and
* `containingFile`. */
cache(moduleSpecifier: string, containingFile: string): void {
cache(moduleSpecifier: string, containingFile?: string): void {
containingFile = containingFile || "";
let innerCache = SourceFile._specifierCache.get(containingFile);
if (!innerCache) {
innerCache = new Map();
Expand Down Expand Up @@ -269,7 +270,7 @@ function fetchAsset(name: string): string {
/** Ops to Rust to resolve and fetch modules meta data. */
function fetchSourceFiles(
specifiers: string[],
referrer: string
referrer?: string
): Promise<SourceFileJson[]> {
util.log("compiler::fetchSourceFiles", { specifiers, referrer });
return sendAsync(dispatch.OP_FETCH_SOURCE_FILES, {
Expand All @@ -286,7 +287,7 @@ function fetchSourceFiles(
* that should be actually resolved. */
async function processImports(
specifiers: Array<[string, string]>,
referrer = ""
referrer?: string
): Promise<SourceFileJson[]> {
if (!specifiers.length) {
return [];
Expand Down
4 changes: 2 additions & 2 deletions cli/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ async fn print_file_info(worker: Worker, module_specifier: ModuleSpecifier) {

let maybe_source_file = global_state_
.file_fetcher
.fetch_source_file_async(&module_specifier)
.fetch_source_file_async(&module_specifier, None)
.await;
if let Err(err) = maybe_source_file {
println!("{}", err);
Expand All @@ -197,7 +197,7 @@ async fn print_file_info(worker: Worker, module_specifier: ModuleSpecifier) {

let maybe_compiled = global_state_
.clone()
.fetch_compiled_module(&module_specifier)
.fetch_compiled_module(&module_specifier, None)
.await;
if let Err(e) = maybe_compiled {
debug!("compiler error exiting!");
Expand Down
15 changes: 12 additions & 3 deletions cli/ops/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fn op_cache(
#[derive(Deserialize)]
struct FetchSourceFilesArgs {
specifiers: Vec<String>,
referrer: String,
referrer: Option<String>,
}

fn op_fetch_source_files(
Expand All @@ -65,14 +65,23 @@ fn op_fetch_source_files(
// to this. Need a test to demonstrate the hole.
let is_dyn_import = false;

let (referrer, ref_specifier) = if let Some(referrer) = args.referrer {
let specifier = ModuleSpecifier::resolve_url(&referrer)
.expect("Referrer is not a valid specifier");
(referrer, Some(specifier))
} else {
// main script import
(".".to_string(), None)
};

let mut futures = vec![];
for specifier in &args.specifiers {
let resolved_specifier =
state.resolve(specifier, &args.referrer, false, is_dyn_import)?;
state.resolve(specifier, &referrer, false, is_dyn_import)?;
let fut = state
.global_state
.file_fetcher
.fetch_source_file_async(&resolved_specifier);
.fetch_source_file_async(&resolved_specifier, ref_specifier.clone());
futures.push(fut);
}

Expand Down
3 changes: 2 additions & 1 deletion cli/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,13 @@ impl Loader for ThreadSafeState {
fn load(
&self,
module_specifier: &ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>,
) -> Pin<Box<deno::SourceCodeInfoFuture>> {
self.metrics.resolve_count.fetch_add(1, Ordering::SeqCst);
let module_url_specified = module_specifier.to_string();
let fut = self
.global_state
.fetch_compiled_module(module_specifier)
.fetch_compiled_module(module_specifier, maybe_referrer)
.map_ok(|compiled_module| deno::SourceCodeInfo {
// Real module name, might be different from initial specifier
// due to redirections.
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/error_004_missing_module.ts.out
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[WILDCARD]error: Uncaught NotFound: Cannot resolve module "[WILDCARD]/bad-module.ts"
[WILDCARD]error: Uncaught NotFound: Cannot resolve module "[WILDCARD]/bad-module.ts" from "[WILDCARD]/error_004_missing_module.ts"
[WILDCARD]dispatch_json.ts:[WILDCARD]
at DenoError ([WILDCARD]errors.ts:[WILDCARD])
at unwrapResponse ([WILDCARD]dispatch_json.ts:[WILDCARD])
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/error_005_missing_dynamic_import.ts.out
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[WILDCARD]error: Uncaught NotFound: Cannot resolve module "[WILDCARD]/bad-module.ts"
[WILDCARD]error: Uncaught NotFound: Cannot resolve module "[WILDCARD]/bad-module.ts" from "[WILDCARD]/error_005_missing_dynamic_import.ts"
[WILDCARD]dispatch_json.ts:[WILDCARD]
at DenoError ([WILDCARD]errors.ts:[WILDCARD])
at unwrapResponse ([WILDCARD]dispatch_json.ts:[WILDCARD])
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/error_006_import_ext_failure.ts.out
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[WILDCARD]error: Uncaught NotFound: Cannot resolve module "[WILDCARD]/non-existent"
[WILDCARD]error: Uncaught NotFound: Cannot resolve module "[WILDCARD]/non-existent" from "[WILDCARD]/error_006_import_ext_failure.ts"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

[WILDCARD]dispatch_json.ts:[WILDCARD]
at DenoError ([WILDCARD]errors.ts:[WILDCARD])
at unwrapResponse ([WILDCARD]dispatch_json.ts:[WILDCARD])
Expand Down
13 changes: 9 additions & 4 deletions core/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub trait Loader: Send + Sync {
fn load(
&self,
module_specifier: &ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>,
) -> Pin<Box<SourceCodeInfoFuture>>;
}

Expand Down Expand Up @@ -154,7 +155,7 @@ impl<L: Loader + Unpin> RecursiveLoad<L> {
// integrated into one thing.
self
.pending
.push(self.loader.load(&module_specifier).boxed());
.push(self.loader.load(&module_specifier, None).boxed());
self.state = State::LoadingRoot;

Ok(())
Expand All @@ -166,6 +167,8 @@ impl<L: Loader + Unpin> RecursiveLoad<L> {
referrer: &str,
parent_id: deno_mod,
) -> Result<(), ErrBox> {
let referrer_specifier = ModuleSpecifier::resolve_url(referrer)
.expect("Referrer should be a valid specifier");
let module_specifier = self.loader.resolve(
specifier,
referrer,
Expand All @@ -181,9 +184,10 @@ impl<L: Loader + Unpin> RecursiveLoad<L> {
if !modules.is_registered(module_name)
&& !self.is_pending.contains(&module_specifier)
{
self
.pending
.push(self.loader.load(&module_specifier).boxed());
let fut = self
.loader
.load(&module_specifier, Some(referrer_specifier.clone()));
self.pending.push(fut.boxed());
self.is_pending.insert(module_specifier);
}

Expand Down Expand Up @@ -739,6 +743,7 @@ mod tests {
fn load(
&self,
module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
) -> Pin<Box<SourceCodeInfoFuture>> {
let mut loads = self.loads.lock().unwrap();
loads.push(module_specifier.to_string());
Expand Down