Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b63315d

Browse filesBrowse files
guybedfordRafaelGSS
authored andcommitted
module: exports & imports map invalid slash deprecation
PR-URL: #44477 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Jacob Smith <jacob@frende.me>
1 parent 26f25c9 commit b63315d
Copy full SHA for b63315d

File tree

Expand file treeCollapse file tree

10 files changed

+176
-65
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

10 files changed

+176
-65
lines changed
Open diff view settings
Collapse file

‎doc/api/deprecations.md‎

Copy file name to clipboardExpand all lines: doc/api/deprecations.md
+17Lines changed: 17 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -3182,6 +3182,23 @@ Type: Documentation-only
31823182

31833183
The [`--trace-atomics-wait`][] flag is deprecated.
31843184

3185+
### DEP0166: Double slashes in imports and exports targets
3186+
3187+
<!-- YAML
3188+
changes:
3189+
- version: REPLACEME
3190+
pr-url: https://github.com/nodejs/node/pull/44477
3191+
description: Documentation-only deprecation
3192+
with `--pending-deprecation` support.
3193+
-->
3194+
3195+
Type: Documentation-only (supports [`--pending-deprecation`][])
3196+
3197+
Package imports and exports targets mapping into paths including a double slash
3198+
(of _"/"_ or _"\\"_) are deprecated and will fail with a resolution validation
3199+
error in a future release. This same deprecation also applies to pattern matches
3200+
starting or ending in a slash.
3201+
31853202
[Legacy URL API]: url.md#legacy-url-api
31863203
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
31873204
[RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3
Collapse file

‎doc/api/esm.md‎

Copy file name to clipboardExpand all lines: doc/api/esm.md
+29-35Lines changed: 29 additions & 35 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1349,8 +1349,7 @@ The resolver can throw the following errors:
13491349
> 1. Set _mainExport_ to _exports_\[_"."_].
13501350
> 4. If _mainExport_ is not **undefined**, then
13511351
> 1. Let _resolved_ be the result of **PACKAGE\_TARGET\_RESOLVE**(
1352-
> _packageURL_, _mainExport_, _""_, **false**, **false**,
1353-
> _conditions_).
1352+
> _packageURL_, _mainExport_, **null**, **false**, _conditions_).
13541353
> 2. If _resolved_ is not **null** or **undefined**, return _resolved_.
13551354
> 3. Otherwise, if _exports_ is an Object and all keys of _exports_ start with
13561355
> _"."_, then
@@ -1381,7 +1380,7 @@ _isImports_, _conditions_)
13811380
> 1. If _matchKey_ is a key of _matchObj_ and does not contain _"\*"_, then
13821381
> 1. Let _target_ be the value of _matchObj_\[_matchKey_].
13831382
> 2. Return the result of **PACKAGE\_TARGET\_RESOLVE**(_packageURL_,
1384-
> _target_, _""_, **false**, _isImports_, _conditions_).
1383+
> _target_, **null**, _isImports_, _conditions_).
13851384
> 2. Let _expansionKeys_ be the list of keys of _matchObj_ containing only a
13861385
> single _"\*"_, sorted by the sorting function **PATTERN\_KEY\_COMPARE**
13871386
> which orders in descending order of specificity.
@@ -1395,11 +1394,11 @@ _isImports_, _conditions_)
13951394
> _patternTrailer_ and the length of _matchKey_ is greater than or
13961395
> equal to the length of _expansionKey_, then
13971396
> 1. Let _target_ be the value of _matchObj_\[_expansionKey_].
1398-
> 2. Let _subpath_ be the substring of _matchKey_ starting at the
1397+
> 2. Let _patternMatch_ be the substring of _matchKey_ starting at the
13991398
> index of the length of _patternBase_ up to the length of
14001399
> _matchKey_ minus the length of _patternTrailer_.
14011400
> 3. Return the result of **PACKAGE\_TARGET\_RESOLVE**(_packageURL_,
1402-
> _target_, _subpath_, **true**, _isImports_, _conditions_).
1401+
> _target_, _patternMatch_, _isImports_, _conditions_).
14031402
> 4. Return **null**.
14041403
14051404
**PATTERN\_KEY\_COMPARE**(_keyA_, _keyB_)
@@ -1418,37 +1417,32 @@ _isImports_, _conditions_)
14181417
> 10. If the length of _keyB_ is greater than the length of _keyA_, return 1.
14191418
> 11. Return 0.
14201419
1421-
**PACKAGE\_TARGET\_RESOLVE**(_packageURL_, _target_, _subpath_, _pattern_,
1422-
_internal_, _conditions_)
1420+
**PACKAGE\_TARGET\_RESOLVE**(_packageURL_, _target_, _patternMatch_,
1421+
_isImports_, _conditions_)
14231422
14241423
> 1. If _target_ is a String, then
1425-
> 1. If _pattern_ is **false**, _subpath_ has non-zero length and _target_
1426-
> does not end with _"/"_, throw an _Invalid Module Specifier_ error.
1427-
> 2. If _target_ does not start with _"./"_, then
1428-
> 1. If _internal_ is **true** and _target_ does not start with _"../"_ or
1429-
> _"/"_ and is not a valid URL, then
1430-
> 1. If _pattern_ is **true**, then
1431-
> 1. Return **PACKAGE\_RESOLVE**(_target_ with every instance of
1432-
> _"\*"_ replaced by _subpath_, _packageURL_ + _"/"_).
1433-
> 2. Return **PACKAGE\_RESOLVE**(_target_ + _subpath_,
1434-
> _packageURL_ + _"/"_).
1435-
> 2. Otherwise, throw an _Invalid Package Target_ error.
1436-
> 3. If _target_ split on _"/"_ or _"\\"_ contains any _"."_, _".."_, or
1437-
> _"node\_modules"_ segments after the first segment, case insensitive and
1438-
> including percent encoded variants, throw an _Invalid Package Target_
1439-
> error.
1440-
> 4. Let _resolvedTarget_ be the URL resolution of the concatenation of
1424+
> 1. If _target_ does not start with _"./"_, then
1425+
> 1. If _isImports_ is **false**, or if _target_ starts with _"../"_ or
1426+
> _"/"_, or if _target_ is a valid URL, then
1427+
> 1. Throw an _Invalid Package Target_ error.
1428+
> 2. If _patternMatch_ is a String, then
1429+
> 1. Return **PACKAGE\_RESOLVE**(_target_ with every instance of _"\*"_
1430+
> replaced by _patternMatch_, _packageURL_ + _"/"_).
1431+
> 3. Return **PACKAGE\_RESOLVE**(_target_, _packageURL_ + _"/"_).
1432+
> 2. If _target_ split on _"/"_ or _"\\"_ contains any _""_, _"."_, _".."_,
1433+
> or _"node\_modules"_ segments after the first _"."_ segment, case
1434+
> insensitive and including percent encoded variants, throw an _Invalid
1435+
> Package Target_ error.
1436+
> 3. Let _resolvedTarget_ be the URL resolution of the concatenation of
14411437
> _packageURL_ and _target_.
1442-
> 5. Assert: _resolvedTarget_ is contained in _packageURL_.
1443-
> 6. If _subpath_ split on _"/"_ or _"\\"_ contains any _"."_, _".."_, or
1444-
> _"node\_modules"_ segments, case insensitive and including percent
1445-
> encoded variants, throw an _Invalid Module Specifier_ error.
1446-
> 7. If _pattern_ is **true**, then
1447-
> 1. Return the URL resolution of _resolvedTarget_ with every instance of
1448-
> _"\*"_ replaced with _subpath_.
1449-
> 8. Otherwise,
1450-
> 1. Return the URL resolution of the concatenation of _subpath_ and
1451-
> _resolvedTarget_.
1438+
> 4. Assert: _resolvedTarget_ is contained in _packageURL_.
1439+
> 5. If _patternMatch_ is **null**, then
1440+
> 1. Return _resolvedTarget_.
1441+
> 6. If _patternMatch_ split on _"/"_ or _"\\"_ contains any _""_, _"."_,
1442+
> _".."_, or _"node\_modules"_ segments, case insensitive and including
1443+
> percent encoded variants, throw an _Invalid Module Specifier_ error.
1444+
> 7. Return the URL resolution of _resolvedTarget_ with every instance of
1445+
> _"\*"_ replaced with _patternMatch_.
14521446
> 2. Otherwise, if _target_ is a non-null Object, then
14531447
> 1. If _exports_ contains any index property keys, as defined in ECMA-262
14541448
> [6.1.7 Array Index][], throw an _Invalid Package Configuration_ error.
@@ -1457,7 +1451,7 @@ _internal_, _conditions_)
14571451
> then
14581452
> 1. Let _targetValue_ be the value of the _p_ property in _target_.
14591453
> 2. Let _resolved_ be the result of **PACKAGE\_TARGET\_RESOLVE**(
1460-
> _packageURL_, _targetValue_, _subpath_, _pattern_, _internal_,
1454+
> _packageURL_, _targetValue_, _patternMatch_, _isImports_,
14611455
> _conditions_).
14621456
> 3. If _resolved_ is equal to **undefined**, continue the loop.
14631457
> 4. Return _resolved_.
@@ -1466,7 +1460,7 @@ _internal_, _conditions_)
14661460
> 1. If \_target.length is zero, return **null**.
14671461
> 2. For each item _targetValue_ in _target_, do
14681462
> 1. Let _resolved_ be the result of **PACKAGE\_TARGET\_RESOLVE**(
1469-
> _packageURL_, _targetValue_, _subpath_, _pattern_, _internal_,
1463+
> _packageURL_, _targetValue_, _patternMatch_, _isImports_,
14701464
> _conditions_), continuing the loop on any _Invalid Package Target_
14711465
> error.
14721466
> 2. If _resolved_ is **undefined**, continue the loop.
Collapse file

‎lib/internal/modules/esm/resolve.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/resolve.js
+74-24Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const {
3333
Stats,
3434
} = require('fs');
3535
const { getOptionValue } = require('internal/options');
36+
const pendingDeprecation = getOptionValue('--pending-deprecation');
3637
// Do not eagerly grab .manifest, it may be in TDZ
3738
const policy = getOptionValue('--experimental-policy') ?
3839
require('internal/process/policy') :
@@ -98,6 +99,23 @@ function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) {
9899
);
99100
}
100101

102+
const doubleSlashRegEx = /[/\\][/\\]/;
103+
104+
function emitInvalidSegmentDeprecation(target, request, match, pjsonUrl, base) {
105+
if (!pendingDeprecation) { return; }
106+
const pjsonPath = fileURLToPath(pjsonUrl);
107+
const double = RegExpPrototypeExec(doubleSlashRegEx, target) !== null;
108+
process.emitWarning(
109+
`Use of deprecated ${double ? 'double slash' :
110+
'leading or trailing slash matching'} resolving "${target}" for module ` +
111+
`request "${request}" ${request !== match ? `matched to "${match}" ` : ''
112+
}in the "exports" field module resolution of the package at ${pjsonPath}${
113+
base ? ` imported from ${fileURLToPath(base)}` : ''}.`,
114+
'DeprecationWarning',
115+
'DEP0166'
116+
);
117+
}
118+
101119
/**
102120
* @param {URL} url
103121
* @param {URL} packageJSONUrl
@@ -344,15 +362,17 @@ function throwExportsNotFound(subpath, packageJSONUrl, base) {
344362

345363
/**
346364
*
347-
* @param {string | URL} subpath
365+
* @param {string} request
366+
* @param {string} match
348367
* @param {URL} packageJSONUrl
349368
* @param {boolean} internal
350369
* @param {string | URL | undefined} base
351370
*/
352-
function throwInvalidSubpath(subpath, packageJSONUrl, internal, base) {
353-
const reason = `request is not a valid subpath for the "${internal ?
354-
'imports' : 'exports'}" resolution of ${fileURLToPath(packageJSONUrl)}`;
355-
throw new ERR_INVALID_MODULE_SPECIFIER(subpath, reason,
371+
function throwInvalidSubpath(request, match, packageJSONUrl, internal, base) {
372+
const reason = `request is not a valid match in pattern "${match}" for the "${
373+
internal ? 'imports' : 'exports'}" resolution of ${
374+
fileURLToPath(packageJSONUrl)}`;
375+
throw new ERR_INVALID_MODULE_SPECIFIER(request, reason,
356376
base && fileURLToPath(base));
357377
}
358378

@@ -368,12 +388,22 @@ function throwInvalidPackageTarget(
368388
internal, base && fileURLToPath(base));
369389
}
370390

371-
const invalidSegmentRegEx = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
391+
const invalidSegmentRegEx = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i;
392+
const deprecatedInvalidSegmentRegEx = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
372393
const invalidPackageNameRegEx = /^\.|%|\\/;
373394
const patternRegEx = /\*/g;
374395

375396
function resolvePackageTargetString(
376-
target, subpath, match, packageJSONUrl, base, pattern, internal, conditions) {
397+
target,
398+
subpath,
399+
match,
400+
packageJSONUrl,
401+
base,
402+
pattern,
403+
internal,
404+
isPathMap,
405+
conditions,
406+
) {
377407

378408
if (subpath !== '' && !pattern && target[target.length - 1] !== '/')
379409
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
@@ -399,8 +429,21 @@ function resolvePackageTargetString(
399429
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
400430
}
401431

402-
if (RegExpPrototypeExec(invalidSegmentRegEx, StringPrototypeSlice(target, 2)) !== null)
403-
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
432+
if (RegExpPrototypeExec(invalidSegmentRegEx, StringPrototypeSlice(target, 2)) !== null) {
433+
if (RegExpPrototypeExec(deprecatedInvalidSegmentRegEx, StringPrototypeSlice(target, 2)) === null) {
434+
if (!isPathMap) {
435+
const request = pattern ?
436+
StringPrototypeReplace(match, '*', () => subpath) :
437+
match + subpath;
438+
const resolvedTarget = pattern ?
439+
RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
440+
target;
441+
emitInvalidSegmentDeprecation(resolvedTarget, request, match, packageJSONUrl, base);
442+
}
443+
} else {
444+
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
445+
}
446+
}
404447

405448
const resolved = new URL(target, packageJSONUrl);
406449
const resolvedPath = resolved.pathname;
@@ -412,18 +455,22 @@ function resolvePackageTargetString(
412455
if (subpath === '') return resolved;
413456

414457
if (RegExpPrototypeExec(invalidSegmentRegEx, subpath) !== null) {
415-
const request = pattern ?
416-
StringPrototypeReplace(match, '*', () => subpath) : match + subpath;
417-
throwInvalidSubpath(request, packageJSONUrl, internal, base);
458+
const request = pattern ? StringPrototypeReplace(match, '*', () => subpath) : match + subpath;
459+
if (RegExpPrototypeExec(deprecatedInvalidSegmentRegEx, subpath) === null) {
460+
if (!isPathMap) {
461+
const resolvedTarget = pattern ?
462+
RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
463+
target;
464+
emitInvalidSegmentDeprecation(resolvedTarget, request, match, packageJSONUrl, base);
465+
}
466+
} else {
467+
throwInvalidSubpath(request, match, packageJSONUrl, internal, base);
468+
}
418469
}
419470

420471
if (pattern) {
421472
return new URL(
422-
RegExpPrototypeSymbolReplace(
423-
patternRegEx,
424-
resolved.href,
425-
() => subpath
426-
)
473+
RegExpPrototypeSymbolReplace(patternRegEx, resolved.href, () => subpath)
427474
);
428475
}
429476

@@ -441,11 +488,11 @@ function isArrayIndex(key) {
441488
}
442489

443490
function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
444-
base, pattern, internal, conditions) {
491+
base, pattern, internal, isPathMap, conditions) {
445492
if (typeof target === 'string') {
446493
return resolvePackageTargetString(
447494
target, subpath, packageSubpath, packageJSONUrl, base, pattern, internal,
448-
conditions);
495+
isPathMap, conditions);
449496
} else if (ArrayIsArray(target)) {
450497
if (target.length === 0) {
451498
return null;
@@ -458,7 +505,7 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
458505
try {
459506
resolveResult = resolvePackageTarget(
460507
packageJSONUrl, targetItem, subpath, packageSubpath, base, pattern,
461-
internal, conditions);
508+
internal, isPathMap, conditions);
462509
} catch (e) {
463510
lastException = e;
464511
if (e.code === 'ERR_INVALID_PACKAGE_TARGET') {
@@ -494,7 +541,7 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
494541
const conditionalTarget = target[key];
495542
const resolveResult = resolvePackageTarget(
496543
packageJSONUrl, conditionalTarget, subpath, packageSubpath, base,
497-
pattern, internal, conditions);
544+
pattern, internal, isPathMap, conditions);
498545
if (resolveResult === undefined)
499546
continue;
500547
return resolveResult;
@@ -557,7 +604,8 @@ function packageExportsResolve(
557604
!StringPrototypeEndsWith(packageSubpath, '/')) {
558605
const target = exports[packageSubpath];
559606
const resolveResult = resolvePackageTarget(
560-
packageJSONUrl, target, '', packageSubpath, base, false, false, conditions
607+
packageJSONUrl, target, '', packageSubpath, base, false, false, false,
608+
conditions
561609
);
562610

563611
if (resolveResult == null) {
@@ -608,6 +656,7 @@ function packageExportsResolve(
608656
base,
609657
true,
610658
false,
659+
StringPrototypeEndsWith(packageSubpath, '/'),
611660
conditions);
612661

613662
if (resolveResult == null) {
@@ -654,7 +703,8 @@ function packageImportsResolve(name, base, conditions) {
654703
if (ObjectPrototypeHasOwnProperty(imports, name) &&
655704
!StringPrototypeIncludes(name, '*')) {
656705
const resolveResult = resolvePackageTarget(
657-
packageJSONUrl, imports[name], '', name, base, false, true, conditions
706+
packageJSONUrl, imports[name], '', name, base, false, true, false,
707+
conditions
658708
);
659709
if (resolveResult != null) {
660710
return resolveResult;
@@ -687,7 +737,7 @@ function packageImportsResolve(name, base, conditions) {
687737
const resolveResult = resolvePackageTarget(packageJSONUrl, target,
688738
bestMatchSubpath,
689739
bestMatch, base, true,
690-
true, conditions);
740+
true, false, conditions);
691741
if (resolveResult != null) {
692742
return resolveResult;
693743
}
Collapse file

‎test/es-module/test-esm-exports-deprecations.mjs‎

Copy file name to clipboardExpand all lines: test/es-module/test-esm-exports-deprecations.mjs
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
1+
// Flags: --pending-deprecation
12
import { mustCall } from '../common/index.mjs';
23
import assert from 'assert';
34

45
let curWarning = 0;
56
const expectedWarnings = [
7+
'Use of deprecated leading or trailing slash',
8+
'Use of deprecated double slash',
9+
'.//asdf.js',
10+
'".//internal/test.js"',
11+
'".//internal//test.js"',
12+
'"./////internal/////test.js"',
613
'"./trailing-pattern-slash/"',
14+
'"./subpath/dir1/dir1.js"',
15+
'"./subpath//dir1/dir1.js"',
16+
'.//asdf.js',
17+
'".//internal/test.js"',
18+
'".//internal//test.js"',
19+
'"./////internal/////test.js"',
720
'no_exports',
821
'default_index',
922
];
Collapse file

‎test/es-module/test-esm-exports.mjs‎

Copy file name to clipboardExpand all lines: test/es-module/test-esm-exports.mjs
+14-1Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
4141
['pkgexports/a/b/dir1/dir1', { default: 'main' }],
4242

4343
// Deprecated:
44+
// Double slashes:
45+
['pkgexports/a//dir1/dir1', { default: 'main' }],
46+
// double slash target
47+
['pkgexports/doubleslash', { default: 'asdf' }],
48+
// Null target with several slashes
49+
['pkgexports/sub//internal/test.js', { default: 'internal only' }],
50+
['pkgexports/sub//internal//test.js', { default: 'internal only' }],
51+
['pkgexports/sub/////internal/////test.js', { default: 'internal only' }],
52+
// trailing slash
4453
['pkgexports/trailing-pattern-slash/',
4554
{ default: 'trailing-pattern-slash' }],
4655
]);
@@ -74,7 +83,11 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
7483
['pkgexports/invalid1', './invalid1'],
7584
['pkgexports/invalid4', './invalid4'],
7685
// Null mapping
86+
['pkgexports/sub/internal/test.js', './sub/internal/test.js'],
87+
['pkgexports/sub/internal//test.js', './sub/internal//test.js'],
7788
['pkgexports/null', './null'],
89+
['pkgexports//null', './/null'],
90+
['pkgexports/////null', './////null'],
7891
['pkgexports/null/subpath', './null/subpath'],
7992
// Empty fallback
8093
['pkgexports/nofallback1', './nofallback1'],
@@ -133,7 +146,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
133146
loadFixture(specifier).catch(mustCall((err) => {
134147
strictEqual(err.code, 'ERR_INVALID_MODULE_SPECIFIER');
135148
assertStartsWith(err.message, 'Invalid module ');
136-
assertIncludes(err.message, 'is not a valid subpath');
149+
assertIncludes(err.message, 'is not a valid match in pattern');
137150
assertIncludes(err.message, subpath);
138151
}));
139152
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.