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

module: Refine and unify exports resolution error handling #31625

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
20 changes: 20 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,12 @@ An invalid HTTP token was supplied.

An IP address is not valid.

<a id="ERR_INVALID_MODULE_SPECIFIER"></a>
### `ERR_INVALID_MODULE_SPECIFIER`

The imported module string is an invalid URL, package name, or package subpath
specifier.

<a id="ERR_INVALID_OPT_VALUE"></a>
### `ERR_INVALID_OPT_VALUE`

Expand All @@ -1334,6 +1340,12 @@ An invalid or unknown file encoding was passed.

An invalid `package.json` file was found which failed parsing.

<a id="ERR_INVALID_PACKAGE_TARGET"></a>
### `ERR_INVALID_PACKAGE_TARGET`

The `package.json` [exports][] field contains an invalid target mapping value
for the attempted module resolution.

<a id="ERR_INVALID_PERFORMANCE_MARK"></a>
### `ERR_INVALID_PERFORMANCE_MARK`

Expand Down Expand Up @@ -1640,6 +1652,13 @@ A non-context-aware native addon was loaded in a process that disallows them.

A given value is out of the accepted range.

<a id="ERR_PKG_PATH_NOT_EXPORTED"></a>
### `ERR_PKG_PATH_NOT_EXPORTED`
guybedford marked this conversation as resolved.
Show resolved Hide resolved

The `package.json` [exports][] field does not export the requested subpath.
Because exports are encapsulated, private internal modules that are not exported
cannot be imported through the package resolution, unless using an absolute URL.

<a id="ERR_REQUIRE_ESM"></a>
### `ERR_REQUIRE_ESM`

Expand Down Expand Up @@ -2505,6 +2524,7 @@ such as `process.stdout.on('data')`.
[crypto digest algorithm]: crypto.html#crypto_crypto_gethashes
[domains]: domain.html
[event emitter-based]: events.html#events_class_eventemitter
[exports]: esm.html#esm_package_exports
[file descriptors]: https://en.wikipedia.org/wiki/File_descriptor
[policy]: policy.html
[stream-based]: stream.html
Expand Down
75 changes: 44 additions & 31 deletions doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,17 @@ of these top-level routines unless stated otherwise.
_defaultEnv_ is the conditional environment name priority array,
`["node", "import"]`.

The resolver can throw the following errors:
* _Invalid Module Specifier_: Module specifier is an invalid URL, package name
or package subpath specifier.
* _Invalid Package Configuration_: package.json configuration is invalid or
contains an invalid configuration.
* _Invalid Package Target_: Package exports define a target module within the
package that is an invalid type or string target.
* _Package Path Not Exported_: Package exports do not define or permit a target
subpath in the package for the given module.
* _Module Not Found_: The package or module requested does not exist.

<details>
<summary>Resolver algorithm specification</summary>

Expand All @@ -1401,7 +1412,7 @@ _defaultEnv_ is the conditional environment name priority array,
> 1. Set _resolvedURL_ to the result of parsing and reserializing
> _specifier_ as a URL.
> 1. Otherwise, if _specifier_ starts with _"/"_, then
> 1. Throw an _Invalid Specifier_ error.
> 1. Throw an _Invalid Module Specifier_ error.
> 1. Otherwise, if _specifier_ starts with _"./"_ or _"../"_, then
> 1. Set _resolvedURL_ to the URL resolution of _specifier_ relative to
> _parentURL_.
Expand All @@ -1411,7 +1422,7 @@ _defaultEnv_ is the conditional environment name priority array,
> **PACKAGE_RESOLVE**(_specifier_, _parentURL_).
> 1. If _resolvedURL_ contains any percent encodings of _"/"_ or _"\\"_ (_"%2f"_
> and _"%5C"_ respectively), then
> 1. Throw an _Invalid Specifier_ error.
> 1. Throw an _Invalid Module Specifier_ error.
> 1. If _resolvedURL_ does not end with a trailing _"/"_ and the file at
> _resolvedURL_ does not exist, then
> 1. Throw a _Module Not Found_ error.
Expand All @@ -1425,22 +1436,22 @@ _defaultEnv_ is the conditional environment name priority array,
> 1. Let _packageName_ be *undefined*.
> 1. Let _packageSubpath_ be *undefined*.
> 1. If _packageSpecifier_ is an empty string, then
> 1. Throw an _Invalid Specifier_ error.
> 1. Throw an _Invalid Module Specifier_ error.
> 1. Otherwise,
> 1. If _packageSpecifier_ does not contain a _"/"_ separator, then
> 1. Throw an _Invalid Specifier_ error.
> 1. Throw an _Invalid Module Specifier_ error.
> 1. Set _packageName_ to the substring of _packageSpecifier_
> until the second _"/"_ separator or the end of the string.
> 1. If _packageName_ starts with _"."_ or contains _"\\"_ or _"%"_, then
> 1. Throw an _Invalid Specifier_ error.
> 1. Throw an _Invalid Module Specifier_ error.
> 1. Let _packageSubpath_ be _undefined_.
> 1. If the length of _packageSpecifier_ is greater than the length of
> _packageName_, then
> 1. Set _packageSubpath_ to _"."_ concatenated with the substring of
> _packageSpecifier_ from the position at the length of _packageName_.
> 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent
> encoded strings for _"/"_ or _"\\"_, then
> 1. Throw an _Invalid Specifier_ error.
> 1. Throw an _Invalid Module Specifier_ error.
> 1. Set _selfUrl_ to the result of
> **SELF_REFERENCE_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_).
> 1. If _selfUrl_ isn't empty, return _selfUrl_.
Expand Down Expand Up @@ -1497,7 +1508,7 @@ _defaultEnv_ is the conditional environment name priority array,
> 1. Throw a _Module Not Found_ error.
> 1. If _pjson.exports_ is not **null** or **undefined**, then
> 1. If _exports_ is an Object with both a key starting with _"."_ and a key
> not starting with _"."_, throw an "Invalid Package Configuration" error.
> not starting with _"."_, throw an _Invalid Package Configuration_ error.
> 1. If _pjson.exports_ is a String or Array, or an Object containing no
> keys starting with _"."_, then
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
Expand All @@ -1506,6 +1517,7 @@ _defaultEnv_ is the conditional environment name priority array,
> 1. Let _mainExport_ be the _"."_ property in _pjson.exports_.
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
> _mainExport_, _""_).
> 1. Throw a _Package Path Not Exported_ error.
> 1. If _pjson.main_ is a String, then
> 1. Let _resolvedMain_ be the URL resolution of _packageURL_, "/", and
> _pjson.main_.
Expand All @@ -1520,7 +1532,7 @@ _defaultEnv_ is the conditional environment name priority array,

**PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _packagePath_, _exports_)
> 1. If _exports_ is an Object with both a key starting with _"."_ and a key not
> starting with _"."_, throw an "Invalid Package Configuration" error.
> starting with _"."_, throw an _Invalid Package Configuration_ error.
> 1. If _exports_ is an Object and all keys of _exports_ start with _"."_, then
> 1. Set _packagePath_ to _"./"_ concatenated with _packagePath_.
> 1. If _packagePath_ is a key of _exports_, then
Expand All @@ -1536,43 +1548,44 @@ _defaultEnv_ is the conditional environment name priority array,
> of the length of _directory_.
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_,
> _subpath_, _defaultEnv_).
> 1. Throw a _Module Not Found_ error.
> 1. Throw a _Package Path Not Exported_ error.

**PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _env_)

> 1. If _target_ is a String, then
> 1. If _target_ does not start with _"./"_, throw a _Module Not Found_
> error.
> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_,
> throw a _Module Not Found_ error.
> 1. If _target_ or _subpath_ contain any _"node_modules"_ segments including
> _"node_modules"_ percent-encoding, throw a _Module Not Found_ error.
> 1.If _target_ is a String, then
> 1. If _target_ does not start with _"./"_ or contains any _"node_modules"_
> segments including _"node_modules"_ percent-encoding, throw an
> _Invalid Package Target_ error.
> 1. Let _resolvedTarget_ be the URL resolution of the concatenation of
> _packageURL_ and _target_.
> 1. If _resolvedTarget_ is contained in _packageURL_, then
> 1. Let _resolved_ be the URL resolution of the concatenation of
> _subpath_ and _resolvedTarget_.
> 1. If _resolved_ is contained in _resolvedTarget_, then
> 1. Return _resolved_.
> 1. If _resolvedTarget_ is not contained in _packageURL_, throw an
> _Invalid Package Target_ error.
> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_,
> throw an _Invalid Module Specifier_ error.
> 1. Let _resolved_ be the URL resolution of the concatenation of
> _subpath_ and _resolvedTarget_.
> 1. If _resolved_ is not contained in _resolvedTarget_, throw an
> _Invalid Module Specifier_ error.
> 1. Return _resolved_.
> 1. Otherwise, if _target_ is a non-null Object, then
> 1. If _exports_ contains any index property keys, as defined in ECMA-262
> [6.1.7 Array Index][], throw an _Invalid Package Configuration_ error.
> 1. For each property _p_ of _target_, in object insertion order as,
> 1. If _env_ contains an entry for _p_, then
> 1. Let _targetValue_ be the value of the _p_ property in _target_.
> 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**
> (_packageURL_, _targetValue_, _subpath_, _env_).
> 1. Assert: _resolved_ is a String.
> 1. Return _resolved_.
> 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**(
> _packageURL_, _targetValue_, _subpath_, _env_), continuing the
> loop on any _Package Path Not Exported_ error.
> 1. Throw a _Package Path Not Exported_ error.
> 1. Otherwise, if _target_ is an Array, then
> 1. If _target.length is zero, throw an _Invalid Package Target_ error.
> 1. For each item _targetValue_ in _target_, do
> 1. If _targetValue_ is an Array, continue the loop.
> 1. Let _resolved_ be the result of
> **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _targetValue_,
> _subpath_, _env_), continuing the loop on abrupt completion.
> 1. Assert: _resolved_ is a String.
> 1. Return _resolved_.
> 1. Throw a _Module Not Found_ error.
> 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
> _targetValue_, _subpath_, _env_), continuing the loop on any
> _Package Path Not Exported_ or _Invalid Package Target_ error.
> 1. Throw the last fallback resolution error.
> 1. Otherwise throw an _Invalid Package Target_ error.

**ESM_FORMAT**(_url_)

Expand Down
27 changes: 26 additions & 1 deletion lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
const {
ArrayIsArray,
Error,
JSON,
Map,
MathAbs,
NumberIsInteger,
Expand All @@ -22,6 +23,7 @@ const {
SymbolFor,
WeakMap,
} = primordials;
const sep = process.platform === 'win32' ? '\\' : '/';
guybedford marked this conversation as resolved.
Show resolved Hide resolved

const messages = new Map();
const codes = {};
Expand Down Expand Up @@ -1073,14 +1075,29 @@ E('ERR_INVALID_FILE_URL_PATH', 'File URL path %s', TypeError);
E('ERR_INVALID_HANDLE_TYPE', 'This handle type cannot be sent', TypeError);
E('ERR_INVALID_HTTP_TOKEN', '%s must be a valid HTTP token ["%s"]', TypeError);
E('ERR_INVALID_IP_ADDRESS', 'Invalid IP address: %s', TypeError);
E('ERR_INVALID_MODULE_SPECIFIER', (pkgPath, subpath) => {
assert(subpath !== '.');
return `Package subpath '${subpath}' is not a valid module request for the ` +
`"exports" resolution of ${pkgPath}${sep}package.json`;
}, TypeError);
E('ERR_INVALID_OPT_VALUE', (name, value) =>
`The value "${String(value)}" is invalid for option "${name}"`,
TypeError,
RangeError);
E('ERR_INVALID_OPT_VALUE_ENCODING',
'The value "%s" is invalid for option "encoding"', TypeError);
E('ERR_INVALID_PACKAGE_CONFIG',
'Invalid package config for \'%s\', %s', Error);
`Invalid package config %s${sep}package.json, %s`, Error);
E('ERR_INVALID_PACKAGE_TARGET', (pkgPath, key, subpath, target) => {
if (key === '.') {
return `Invalid "exports" main target ${JSON.stringify(target)} defined ` +
`in the package config ${pkgPath}${sep}package.json`;
} else {
return `Invalid "exports" target ${JSON.stringify(target)} defined for '${
key.slice(0, -subpath.length || key.length)}' in the package config ${
ljharb marked this conversation as resolved.
Show resolved Hide resolved
pkgPath}${sep}package.json`;
}
}, Error);
E('ERR_INVALID_PERFORMANCE_MARK',
'The "%s" performance mark has not been set', Error);
E('ERR_INVALID_PROTOCOL',
Expand Down Expand Up @@ -1225,6 +1242,14 @@ E('ERR_OUT_OF_RANGE',
msg += ` It must be ${range}. Received ${received}`;
return msg;
}, RangeError);
E('ERR_PKG_PATH_NOT_EXPORTED', (pkgPath, subpath) => {
if (subpath === '.') {
return `No "exports" main resolved in ${pkgPath}${sep}package.json`;
} else {
return `Package subpath '${subpath}' is not defined by "exports" in ${
pkgPath}${sep}package.json`;
}
}, Error);
E('ERR_REQUIRE_ESM',
(filename, parentPath = null, packageJsonPath = null) => {
let msg = `Must use import to load ES Module: ${filename}`;
Expand Down
Loading