Skip to content
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
56 changes: 43 additions & 13 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ export interface WorkspaceOptions {
noTranspile?: boolean;
}

export class ResolveError extends Error {
/**
* Possible specifier this would resolve to if the error did not occur.
*
* This is useful for implementing something like `import.meta.resolve` where
* you want the resolution to always occur and not error.
*/
specifier?: string;
/** Node.js error code. */
code?: string;
}

/** File type. */
export enum MediaType {
JavaScript = 0,
Expand Down Expand Up @@ -203,7 +215,9 @@ export class Loader implements Disposable {
return messages.map((message) => ({ message }));
}

/** Synchronously resolves a specifier using the given referrer and resolution mode. */
/** Synchronously resolves a specifier using the given referrer and resolution mode.
* @throws {ResolveError}
*/
resolveSync(
specifier: string,
referrer: string | undefined,
Expand All @@ -216,11 +230,20 @@ export class Loader implements Disposable {
}' (${resolutionModeToString(resolutionMode)})`,
);
}
const value = this.#inner.resolve_sync(specifier, referrer, resolutionMode);
if (this.#debug) {
console.error(`DEBUG - Resolved to '${value}'`);
try {
const value = this.#inner.resolve_sync(
specifier,
referrer,
resolutionMode,
);
if (this.#debug) {
console.error(`DEBUG - Resolved to '${value}'`);
}
return value;
} catch (err) {
Object.setPrototypeOf(err, ResolveError.prototype);
throw err;
}
return value;
}

/** Asynchronously resolves a specifier using the given referrer and resolution mode.
Expand All @@ -230,6 +253,8 @@ export class Loader implements Disposable {
* npm or jsr resolution than Deno. For that reason it's better to provide the list of
* entrypoints up front so the loader can create the npm and jsr graph, and then after use
* synchronous resolution to resolve jsr and npm specifiers.
*
* @throws {ResolveError}
*/
async resolve(
specifier: string,
Expand All @@ -243,15 +268,20 @@ export class Loader implements Disposable {
}' (${resolutionModeToString(resolutionMode)})`,
);
}
const value = await this.#inner.resolve(
specifier,
referrer,
resolutionMode,
);
if (this.#debug) {
console.error(`DEBUG - Resolved to '${value}'`);
try {
const value = await this.#inner.resolve(
specifier,
referrer,
resolutionMode,
);
if (this.#debug) {
console.error(`DEBUG - Resolved to '${value}'`);
}
return value;
} catch (err) {
Object.setPrototypeOf(err, ResolveError.prototype);
throw err;
}
return value;
}

/** Loads a specifier. */
Expand Down
76 changes: 74 additions & 2 deletions src/rs_lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod http_client;

use std::borrow::Cow;
use std::cell::RefCell;
use std::error::Error;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
Expand Down Expand Up @@ -29,6 +30,8 @@ use deno_npm_installer::NpmInstallerFactory;
use deno_npm_installer::NpmInstallerFactoryOptions;
use deno_npm_installer::Reporter;
use deno_npm_installer::lifecycle_scripts::NullLifecycleScriptsExecutor;
use deno_resolver::DenoResolveError;
use deno_resolver::DenoResolveErrorKind;
use deno_resolver::cache::ParsedSourceCache;
use deno_resolver::cjs::CjsTrackerRc;
use deno_resolver::deno_json::CompilerOptionsOverrides;
Expand All @@ -46,12 +49,16 @@ use deno_resolver::file_fetcher::DenoGraphLoaderOptions;
use deno_resolver::file_fetcher::PermissionedFileFetcher;
use deno_resolver::file_fetcher::PermissionedFileFetcherOptions;
use deno_resolver::graph::DefaultDenoResolverRc;
use deno_resolver::graph::ResolveWithGraphError;
use deno_resolver::graph::ResolveWithGraphErrorKind;
use deno_resolver::graph::ResolveWithGraphOptions;
use deno_resolver::loader::LoadCodeSourceErrorKind;
use deno_resolver::loader::LoadedModuleOrAsset;
use deno_resolver::loader::MemoryFilesRc;
use deno_resolver::loader::ModuleLoader;
use deno_resolver::loader::RequestedModuleType;
use deno_resolver::npm::DenoInNpmPackageChecker;
use deno_resolver::workspace::MappedResolutionError;
use deno_semver::SmallStackString;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
Expand All @@ -65,6 +72,7 @@ use node_resolver::NodeResolverOptions;
use node_resolver::PackageJsonThreadLocalCache;
use node_resolver::analyze::NodeCodeTranslatorMode;
use node_resolver::cache::NodeResolutionThreadLocalCache;
use node_resolver::errors::NodeJsErrorCoded;
use serde::Deserialize;
use serde::Serialize;
use sys_traits::EnvCurrentDir;
Expand Down Expand Up @@ -340,6 +348,7 @@ impl DenoWorkspace {
NullBlobStore,
Arc::new(self.workspace_factory.http_cache()?.clone()),
self.http_client.clone(),
MemoryFilesRc::default(),
self.workspace_factory.sys().clone(),
PermissionedFileFetcherOptions {
allow_remote: true,
Expand Down Expand Up @@ -804,6 +813,49 @@ impl DenoLoader {
}
}

fn resolve_with_graph_error_code(
err: &ResolveWithGraphError,
) -> Option<&'static str> {
match err.as_kind() {
ResolveWithGraphErrorKind::CouldNotResolveNpmNv(err) => {
Some(err.code().as_str())
}
ResolveWithGraphErrorKind::ResolvePkgFolderFromDenoModule(_) => None,
ResolveWithGraphErrorKind::ResolveNpmReqRef(err) => {
err.err.maybe_code().map(|c| c.as_str())
}
ResolveWithGraphErrorKind::Resolution(err) => err
.source()
.and_then(|s| s.downcast_ref::<DenoResolveError>())
.and_then(deno_resolve_error_code),
ResolveWithGraphErrorKind::Resolve(err) => deno_resolve_error_code(err),
ResolveWithGraphErrorKind::PathToUrl(_) => None,
}
}

fn deno_resolve_error_code(err: &DenoResolveError) -> Option<&'static str> {
match err.as_kind() {
DenoResolveErrorKind::InvalidVendorFolderImport
| DenoResolveErrorKind::UnsupportedPackageJsonFileSpecifier
| DenoResolveErrorKind::UnsupportedPackageJsonJsrReq => None,
DenoResolveErrorKind::MappedResolution(err) => match err {
MappedResolutionError::Specifier(_)
| MappedResolutionError::ImportMap(_)
| MappedResolutionError::Workspace(_) => None,
},
DenoResolveErrorKind::Node(err) => err.maybe_code().map(|c| c.as_str()),
DenoResolveErrorKind::ResolveNpmReqRef(err) => {
err.err.maybe_code().map(|c| c.as_str())
}
DenoResolveErrorKind::NodeModulesOutOfDate(_)
| DenoResolveErrorKind::PackageJsonDepValueParse(_)
| DenoResolveErrorKind::PackageJsonDepValueUrlParse(_)
| DenoResolveErrorKind::PathToUrl(_)
| DenoResolveErrorKind::ResolvePkgFolderFromDenoReq(_)
| DenoResolveErrorKind::WorkspaceResolvePkgJsonFolder(_) => None,
}
}

fn create_module_response(
url: &Url,
media_type: MediaType,
Expand Down Expand Up @@ -858,7 +910,27 @@ fn resolve_absolute_path(
}

fn create_js_error(err: anyhow::Error) -> JsValue {
wasm_bindgen::JsError::new(&format!("{:#}", err)).into()
let err_value: JsValue =
wasm_bindgen::JsError::new(&format!("{:#}", err)).into();
if let Some(err) = err.downcast_ref::<ResolveWithGraphError>() {
if let Some(code) = resolve_with_graph_error_code(err) {
_ = js_sys::Reflect::set(
&err_value,
&JsValue::from_str("code"),
&JsValue::from_str(code),
);
}
if let Some(specifier) = err.maybe_specifier() {
if let Ok(url) = specifier.into_owned().into_url() {
_ = js_sys::Reflect::set(
&err_value,
&JsValue::from_str("specifier"),
&JsValue::from_str(url.as_str()),
);
}
}
}
err_value
}

fn parse_resolution_mode(resolution_mode: u8) -> node_resolver::ResolutionMode {
Expand Down Expand Up @@ -943,7 +1015,7 @@ struct CapturingModuleAnalyzerRef<'a> {
}

impl<'a> CapturingModuleAnalyzerRef<'a> {
pub fn as_capturing_parser(&self) -> CapturingEsParser {
pub fn as_capturing_parser(&self) -> CapturingEsParser<'_> {
CapturingEsParser::new(Some(self.parser), self.store)
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/invalid_graph/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { assertEquals } from "@std/assert";
import { createLoaderWithDiagnostics } from "../helpers.ts";

Deno.test("loads linked entrypoint", async () => {
Deno.test("surfaces graph diagnostic", async () => {
const mainFile = import.meta.dirname + "/testdata/main.ts";
const { diagnostics } = await createLoaderWithDiagnostics({
configPath: import.meta.dirname + "/testdata/deno.json",
Expand Down
40 changes: 40 additions & 0 deletions tests/resolve_error/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { assertEquals, assertRejects, assertThrows } from "@std/assert";
import { createLoader, ResolutionMode, ResolveError } from "../helpers.ts";

Deno.test("error has extra properties", async (t) => {
const mainFile = import.meta.dirname + "/testdata/main.ts";
const { loader } = await createLoader({
configPath: import.meta.dirname + "/testdata/deno.json",
}, {
entrypoints: [mainFile],
});

await t.step("code", () => {
const err = assertThrows(() =>
loader.resolveSync(
"export-package/non-existent",
import.meta.resolve("./testdata/main.ts"),
ResolutionMode.Import,
), ResolveError);
assertEquals(err.code, "ERR_PACKAGE_PATH_NOT_EXPORTED");
});

await t.step("specifier", async () => {
const err = await assertRejects(
() =>
loader.resolve(
"open-package/non-existent.js",
import.meta.resolve("./testdata/main.ts"),
ResolutionMode.Import,
),
ResolveError,
);
assertEquals(err.code, "ERR_MODULE_NOT_FOUND");
assertEquals(
err.specifier,
import.meta.resolve(
"./testdata/node_modules/open-package/non-existent.js",
),
);
});
});
1 change: 1 addition & 0 deletions tests/resolve_error/testdata/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Empty file.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions tests/resolve_error/testdata/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
Loading