Repository: webpack/enhanced-resolve
Pull request #336 (2022-03-18 01:53:44)

chore: replace deprecated String.prototype.substr()
This PR contains a:

- [x] **bugfix**
- [ ] new **feature**
- [x] **code refactor**
- [ ] **test update** <!-- if bug or feature is checked, this should be too -->
- [ ] **typo fix**
- [ ] **metadata update**

### Motivation / Use-Case

[String.prototype.substr()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr) is deprecated so we replace it with [String.prototype.slice()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) or [String.prototype.substring()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring) which work similarily but aren't deprecated.
.substr() probably isn't going away anytime soon but the change is trivial so it doesn't hurt to do it.

### Breaking Changes

None

### Additional Info



Patch:
diff --git a/README.md b/README.md
index a710610..ad512e5 100644
--- a/README.md
+++ b/README.md
@@ -63,10 +63,10 @@ const myResolver = ResolverFactory.createResolver({
 
 // resolve a file with the new resolver
 const context = {};
+const resolveContext = {};
 const lookupStartPath = "/Users/webpack/some/root/dir";
 const request = "./path/to-look-up.js";
-const resolveContext = {};
-myResolver.resolve(context, lookupStartPath, request, resolveContext, (
+myResolver.resolve({}, lookupStartPath, request, resolveContext, (
 	err /*Error*/,
 	filepath /*string*/
 ) => {
@@ -77,13 +77,12 @@ myResolver.resolve(context, lookupStartPath, request, resolveContext, (
 #### Resolver Options
 
 | Field            | Default                     | Description                                                                                                                                               |
-|------------------|-----------------------------| --------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ---------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | alias            | []                          | A list of module alias configurations or an object which maps key to value                                                                                |
 | aliasFields      | []                          | A list of alias fields in description files                                                                                                               |
-| extensionAlias   | {}                          | An object which maps extension to extension aliases                                                                                                       |
 | cachePredicate   | function() { return true }; | A function which decides whether a request should be cached or not. An object is passed to the function with `path` and `request` properties.             |
 | cacheWithContext | true                        | If unsafe cache is enabled, includes `request.context` in the cache key                                                                                   |
-| conditionNames   | []                          | A list of exports field condition names                                                                                                                   |
+| conditionNames   | ["node"]                    | A list of exports field condition names                                                                                                                   |
 | descriptionFiles | ["package.json"]            | A list of description files to read from                                                                                                                  |
 | enforceExtension | false                       | Enforce that a extension from extensions must be used                                                                                                     |
 | exportsFields    | ["exports"]                 | A list of exports fields in description files                                                                                                             |
@@ -143,7 +142,7 @@ enhanced-resolve will try to resolve requests containing `#` as path and as frag
 ## Tests
 
 ```javascript
-yarn test
+npm test
 ```
 
 [![Build Status](https://secure.travis-ci.org/webpack/enhanced-resolve.png?branch=main)](http://travis-ci.org/webpack/enhanced-resolve)
diff --git a/lib/AliasFieldPlugin.js b/lib/AliasFieldPlugin.js
index b7c79e2..4699431 100644
--- a/lib/AliasFieldPlugin.js
+++ b/lib/AliasFieldPlugin.js
@@ -49,14 +49,9 @@ module.exports = class AliasFieldPlugin {
 						);
 					return callback();
 				}
-				const data = Object.prototype.hasOwnProperty.call(
-					fieldData,
-					innerRequest
-				)
-					? fieldData[innerRequest]
-					: innerRequest.startsWith("./")
-					? fieldData[innerRequest.slice(2)]
-					: undefined;
+				const data1 = fieldData[innerRequest];
+				const data2 = fieldData[innerRequest.replace(/^\.\//, "")];
+				const data = typeof data1 !== "undefined" ? data1 : data2;
 				if (data === innerRequest) return callback();
 				if (data === undefined) return callback();
 				if (data === false) {
diff --git a/lib/AliasPlugin.js b/lib/AliasPlugin.js
index 76f8dbb..8b8c6b1 100644
--- a/lib/AliasPlugin.js
+++ b/lib/AliasPlugin.js
@@ -6,7 +6,6 @@
 "use strict";
 
 const forEachBail = require("./forEachBail");
-const { PathType, getType } = require("./util/path");
 
 /** @typedef {import("./Resolver")} Resolver */
 /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
@@ -31,18 +30,6 @@ module.exports = class AliasPlugin {
 	 */
 	apply(resolver) {
 		const target = resolver.ensureHook(this.target);
-		const getAbsolutePathWithSlashEnding = maybeAbsolutePath => {
-			const type = getType(maybeAbsolutePath);
-			if (type === PathType.AbsolutePosix || type === PathType.AbsoluteWin) {
-				return resolver.join(maybeAbsolutePath, "_").slice(0, -1);
-			}
-			return null;
-		};
-		const isSubPath = (path, maybeSubPath) => {
-			const absolutePath = getAbsolutePathWithSlashEnding(maybeSubPath);
-			if (!absolutePath) return false;
-			return path.startsWith(absolutePath);
-		};
 		resolver
 			.getHook(this.source)
 			.tapAsync("AliasPlugin", (request, resolveContext, callback) => {
@@ -55,11 +42,13 @@ module.exports = class AliasPlugin {
 						if (
 							innerRequest === item.name ||
 							(!item.onlyModule &&
-								(request.request
-									? innerRequest.startsWith(`${item.name}/`)
-									: isSubPath(innerRequest, item.name)))
+								innerRequest.startsWith(
+									request.request
+										? `${item.name}/`
+										: resolver.join(item.name, "_").slice(0, -1)
+								))
 						) {
-							const remainingRequest = innerRequest.substr(item.name.length);
+							const remainingRequest = innerRequest.slice(item.name.length);
 							const resolveWithAlias = (alias, callback) => {
 								if (alias === false) {
 									/** @type {ResolveRequest} */
diff --git a/lib/DescriptionFilePlugin.js b/lib/DescriptionFilePlugin.js
index 83c0e08..29ffe0c 100644
--- a/lib/DescriptionFilePlugin.js
+++ b/lib/DescriptionFilePlugin.js
@@ -63,7 +63,7 @@ module.exports = class DescriptionFilePlugin {
 								return callback();
 							}
 							const relativePath =
-								"." + path.substr(result.directory.length).replace(/\\/g, "/");
+								"." + path.slice(result.directory.length).replace(/\\/g, "/");
 							const obj = {
 								...request,
 								descriptionFilePath: result.path,
diff --git a/lib/DescriptionFileUtils.js b/lib/DescriptionFileUtils.js
index 87e5f79..1377c04 100644
--- a/lib/DescriptionFileUtils.js
+++ b/lib/DescriptionFileUtils.js
@@ -162,7 +162,7 @@ function cdUp(directory) {
 		j = directory.lastIndexOf("\\");
 	const p = i < 0 ? j : j < 0 ? i : i < j ? j : i;
 	if (p < 0) return null;
-	return directory.substr(0, p || 1);
+	return directory.slice(0, p || 1);
 }
 
 exports.loadDescriptionFile = loadDescriptionFile;
diff --git a/lib/ExportsFieldPlugin.js b/lib/ExportsFieldPlugin.js
index 45a514f..2730753 100644
--- a/lib/ExportsFieldPlugin.js
+++ b/lib/ExportsFieldPlugin.js
@@ -10,7 +10,7 @@ const DescriptionFileUtils = require("./DescriptionFileUtils");
 const forEachBail = require("./forEachBail");
 const { processExportsField } = require("./util/entrypoints");
 const { parseIdentifier } = require("./util/identifier");
-const { checkImportsExportsFieldTarget } = require("./util/path");
+const { checkExportsFieldTarget } = require("./util/path");
 
 /** @typedef {import("./Resolver")} Resolver */
 /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
@@ -116,7 +116,7 @@ module.exports = class ExportsFieldPlugin {
 
 						const [relativePath, query, fragment] = parsedIdentifier;
 
-						const error = checkImportsExportsFieldTarget(relativePath);
+						const error = checkExportsFieldTarget(relativePath);
 
 						if (error) {
 							return callback(error);
diff --git a/lib/ExtensionAliasPlugin.js b/lib/ExtensionAliasPlugin.js
deleted file mode 100644
index 76ce9b4..0000000
--- a/lib/ExtensionAliasPlugin.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Ivan Kopeykin @vankop
-*/
-
-"use strict";
-
-const forEachBail = require("./forEachBail");
-
-/** @typedef {import("./Resolver")} Resolver */
-/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
-/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
-/** @typedef {{ alias: string|string[], extension: string }} ExtensionAliasOption */
-
-module.exports = class ExtensionAliasPlugin {
-	/**
-	 * @param {string | ResolveStepHook} source source
-	 * @param {ExtensionAliasOption} options options
-	 * @param {string | ResolveStepHook} target target
-	 */
-	constructor(source, options, target) {
-		this.source = source;
-		this.options = options;
-		this.target = target;
-	}
-
-	/**
-	 * @param {Resolver} resolver the resolver
-	 * @returns {void}
-	 */
-	apply(resolver) {
-		const target = resolver.ensureHook(this.target);
-		const { extension, alias } = this.options;
-		resolver
-			.getHook(this.source)
-			.tapAsync("ExtensionAliasPlugin", (request, resolveContext, callback) => {
-				const requestPath = request.request;
-				if (!requestPath || !requestPath.endsWith(extension)) return callback();
-				const resolve = (alias, callback) => {
-					resolver.doResolve(
-						target,
-						{
-							...request,
-							request: `${requestPath.slice(0, -extension.length)}${alias}`,
-							fullySpecified: true
-						},
-						`aliased from extension alias with mapping '${extension}' to '${alias}'`,
-						resolveContext,
-						callback
-					);
-				};
-
-				const stoppingCallback = (err, result) => {
-					if (err) return callback(err);
-					if (result) return callback(null, result);
-					// Don't allow other aliasing or raw request
-					return callback(null, null);
-				};
-				if (typeof alias === "string") {
-					resolve(alias, stoppingCallback);
-				} else if (alias.length > 1) {
-					forEachBail(alias, resolve, stoppingCallback);
-				} else {
-					resolve(alias[0], stoppingCallback);
-				}
-			});
-	}
-};
diff --git a/lib/ImportsFieldPlugin.js b/lib/ImportsFieldPlugin.js
index 1807160..8be64a0 100644
--- a/lib/ImportsFieldPlugin.js
+++ b/lib/ImportsFieldPlugin.js
@@ -10,7 +10,6 @@ const DescriptionFileUtils = require("./DescriptionFileUtils");
 const forEachBail = require("./forEachBail");
 const { processImportsField } = require("./util/entrypoints");
 const { parseIdentifier } = require("./util/identifier");
-const { checkImportsExportsFieldTarget } = require("./util/path");
 
 /** @typedef {import("./Resolver")} Resolver */
 /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
@@ -119,12 +118,6 @@ module.exports = class ImportsFieldPlugin {
 
 						const [path_, query, fragment] = parsedIdentifier;
 
-						const error = checkImportsExportsFieldTarget(path_);
-
-						if (error) {
-							return callback(error);
-						}
-
 						switch (path_.charCodeAt(0)) {
 							// should be relative
 							case dotCode: {
diff --git a/lib/Resolver.js b/lib/Resolver.js
index 9395144..7061ead 100644
--- a/lib/Resolver.js
+++ b/lib/Resolver.js
@@ -45,18 +45,10 @@ const {
  * @param {T=} result
  */
 
-/** @typedef {function((NodeJS.ErrnoException | null)=, (string | Buffer)[] | IDirent[]=): void} DirentArrayCallback */
-
-/**
- * @typedef {Object} ReaddirOptions
- * @property {BufferEncoding | null | 'buffer'} [encoding]
- * @property {boolean | undefined} [withFileTypes=false]
- */
-
 /**
  * @typedef {Object} FileSystem
  * @property {(function(string, FileSystemCallback<Buffer | string>): void) & function(string, object, FileSystemCallback<Buffer | string>): void} readFile
- * @property {function(string, (ReaddirOptions | BufferEncoding | null | undefined | 'buffer' | DirentArrayCallback)=,  DirentArrayCallback=): void} readdir
+ * @property {(function(string, FileSystemCallback<(Buffer | string)[] | FileSystemDirent[]>): void) & function(string, object, FileSystemCallback<(Buffer | string)[] | FileSystemDirent[]>): void} readdir
  * @property {((function(string, FileSystemCallback<object>): void) & function(string, object, FileSystemCallback<object>): void)=} readJson
  * @property {(function(string, FileSystemCallback<Buffer | string>): void) & function(string, object, FileSystemCallback<Buffer | string>): void} readlink
  * @property {(function(string, FileSystemCallback<FileSystemStats>): void) & function(string, object, FileSystemCallback<Buffer | string>): void=} lstat
@@ -122,7 +114,7 @@ const {
  * @returns {string} in camel case
  */
 function toCamelCase(str) {
-	return str.replace(/-([a-z])/g, str => str.substr(1).toUpperCase());
+	return str.replace(/-([a-z])/g, str => str.slice(1).toUpperCase());
 }
 
 class Resolver {
@@ -178,14 +170,14 @@ class Resolver {
 		name = toCamelCase(name);
 		if (/^before/.test(name)) {
 			return /** @type {ResolveStepHook} */ (this.ensureHook(
-				name[6].toLowerCase() + name.substr(7)
+				name[6].toLowerCase() + name.slice(7)
 			).withOptions({
 				stage: -10
 			}));
 		}
 		if (/^after/.test(name)) {
 			return /** @type {ResolveStepHook} */ (this.ensureHook(
-				name[5].toLowerCase() + name.substr(6)
+				name[5].toLowerCase() + name.slice(6)
 			).withOptions({
 				stage: 10
 			}));
@@ -211,14 +203,14 @@ class Resolver {
 		name = toCamelCase(name);
 		if (/^before/.test(name)) {
 			return /** @type {ResolveStepHook} */ (this.getHook(
-				name[6].toLowerCase() + name.substr(7)
+				name[6].toLowerCase() + name.slice(7)
 			).withOptions({
 				stage: -10
 			}));
 		}
 		if (/^after/.test(name)) {
 			return /** @type {ResolveStepHook} */ (this.getHook(
-				name[5].toLowerCase() + name.substr(6)
+				name[5].toLowerCase() + name.slice(6)
 			).withOptions({
 				stage: 10
 			}));
@@ -271,7 +263,7 @@ class Resolver {
 		if (typeof path !== "string")
 			return callback(new Error("path argument is not a string"));
 		if (typeof request !== "string")
-			return callback(new Error("request argument is not a string"));
+			return callback(new Error("path argument is not a string"));
 		if (!resolveContext)
 			return callback(new Error("resolveContext argument is not set"));
 
@@ -473,7 +465,7 @@ class Resolver {
 			part.module = this.isModule(part.request);
 			part.directory = this.isDirectory(part.request);
 			if (part.directory) {
-				part.request = part.request.substr(0, part.request.length - 1);
+				part.request = part.request.slice(0, -1);
 			}
 		}
 
diff --git a/lib/ResolverFactory.js b/lib/ResolverFactory.js
index 6af60a5..de8e1bb 100644
--- a/lib/ResolverFactory.js
+++ b/lib/ResolverFactory.js
@@ -18,7 +18,6 @@ const ConditionalPlugin = require("./ConditionalPlugin");
 const DescriptionFilePlugin = require("./DescriptionFilePlugin");
 const DirectoryExistsPlugin = require("./DirectoryExistsPlugin");
 const ExportsFieldPlugin = require("./ExportsFieldPlugin");
-const ExtensionAliasPlugin = require("./ExtensionAliasPlugin");
 const FileExistsPlugin = require("./FileExistsPlugin");
 const ImportsFieldPlugin = require("./ImportsFieldPlugin");
 const JoinRequestPartPlugin = require("./JoinRequestPartPlugin");
@@ -39,7 +38,6 @@ const UnsafeCachePlugin = require("./UnsafeCachePlugin");
 const UseFilePlugin = require("./UseFilePlugin");
 
 /** @typedef {import("./AliasPlugin").AliasOption} AliasOptionEntry */
-/** @typedef {import("./ExtensionAliasPlugin").ExtensionAliasOption} ExtensionAliasOption */
 /** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
 /** @typedef {import("./Resolver").FileSystem} FileSystem */
 /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
@@ -47,14 +45,12 @@ const UseFilePlugin = require("./UseFilePlugin");
 
 /** @typedef {string|string[]|false} AliasOptionNewRequest */
 /** @typedef {{[k: string]: AliasOptionNewRequest}} AliasOptions */
-/** @typedef {{[k: string]: string|string[] }} ExtensionAliasOptions */
 /** @typedef {{apply: function(Resolver): void} | function(this: Resolver, Resolver): void} Plugin */
 
 /**
  * @typedef {Object} UserResolveOptions
  * @property {(AliasOptions | AliasOptionEntry[])=} alias A list of module alias configurations or an object which maps key to value
  * @property {(AliasOptions | AliasOptionEntry[])=} fallback A list of module alias configurations or an object which maps key to value, applied only after modules option
- * @property {ExtensionAliasOptions=} extensionAlias An object which maps extension to extension aliases
  * @property {(string | string[])[]=} aliasFields A list of alias fields in description files
  * @property {(function(ResolveRequest): boolean)=} cachePredicate A function which decides whether a request should be cached or not. An object is passed with at least `path` and `request` properties.
  * @property {boolean=} cacheWithContext Whether or not the unsafeCache should include request context as part of the cache key.
@@ -87,7 +83,6 @@ const UseFilePlugin = require("./UseFilePlugin");
  * @property {AliasOptionEntry[]} alias
  * @property {AliasOptionEntry[]} fallback
  * @property {Set<string | string[]>} aliasFields
- * @property {ExtensionAliasOption[]} extensionAlias
  * @property {(function(ResolveRequest): boolean)} cachePredicate
  * @property {boolean} cacheWithContext
  * @property {Set<string>} conditionNames A list of exports field condition names.
@@ -141,7 +136,7 @@ function normalizeAlias(alias) {
 
 				if (/\$$/.test(key)) {
 					obj.onlyModule = true;
-					obj.name = key.substr(0, key.length - 1);
+					obj.name = key.slice(0, -1);
 				}
 
 				return obj;
@@ -202,14 +197,6 @@ function createOptions(options) {
 					: false
 				: options.enforceExtension,
 		extensions: new Set(options.extensions || [".js", ".json", ".node"]),
-		extensionAlias: options.extensionAlias
-			? Object.keys(options.extensionAlias).map(k => ({
-					extension: k,
-					alias: /** @type {ExtensionAliasOptions} */ (options.extensionAlias)[
-						k
-					]
-			  }))
-			: [],
 		fileSystem: options.useSyncFileSystemCalls
 			? new SyncAsyncFileSystemDecorator(
 					/** @type {SyncFileSystem} */ (
@@ -264,7 +251,6 @@ exports.createResolver = function (options) {
 		descriptionFiles,
 		enforceExtension,
 		exportsFields,
-		extensionAlias,
 		importsFields,
 		extensions,
 		fileSystem,
@@ -297,8 +283,6 @@ exports.createResolver = function (options) {
 	resolver.ensureHook("newInternalResolve");
 	resolver.ensureHook("parsedResolve");
 	resolver.ensureHook("describedResolve");
-	resolver.ensureHook("rawResolve");
-	resolver.ensureHook("normalResolve");
 	resolver.ensureHook("internal");
 	resolver.ensureHook("rawModule");
 	resolver.ensureHook("module");
@@ -358,28 +342,21 @@ exports.createResolver = function (options) {
 	plugins.push(new NextPlugin("after-parsed-resolve", "described-resolve"));
 
 	// described-resolve
-	plugins.push(new NextPlugin("described-resolve", "raw-resolve"));
+	plugins.push(new NextPlugin("described-resolve", "normal-resolve"));
 	if (fallback.length > 0) {
 		plugins.push(
 			new AliasPlugin("described-resolve", fallback, "internal-resolve")
 		);
 	}
 
-	// raw-resolve
-	if (alias.length > 0) {
-		plugins.push(new AliasPlugin("raw-resolve", alias, "internal-resolve"));
-	}
+	// normal-resolve
+	if (alias.length > 0)
+		plugins.push(new AliasPlugin("normal-resolve", alias, "internal-resolve"));
 	aliasFields.forEach(item => {
-		plugins.push(new AliasFieldPlugin("raw-resolve", item, "internal-resolve"));
-	});
-	extensionAlias.forEach(item =>
 		plugins.push(
-			new ExtensionAliasPlugin("raw-resolve", item, "normal-resolve")
-		)
-	);
-	plugins.push(new NextPlugin("raw-resolve", "normal-resolve"));
-
-	// normal-resolve
+			new AliasFieldPlugin("normal-resolve", item, "internal-resolve")
+		);
+	});
 	if (preferRelative) {
 		plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
 	}
diff --git a/lib/getPaths.js b/lib/getPaths.js
index fad33e5..835a3b6 100644
--- a/lib/getPaths.js
+++ b/lib/getPaths.js
@@ -11,12 +11,12 @@ module.exports = function getPaths(path) {
 	const paths = [path];
 	const segments = [parts[parts.length - 1]];
 	let part = parts[parts.length - 1];
-	path = path.substr(0, path.length - part.length - 1);
+	path = path.substring(0, path.length - part.length - 1);
 	for (let i = parts.length - 2; i > 2; i -= 2) {
 		paths.push(path);
 		part = parts[i];
-		path = path.substr(0, path.length - part.length) || "/";
-		segments.push(part.substr(0, part.length - 1));
+		path = path.substring(0, path.length - part.length) || "/";
+		segments.push(part.slice(0, -1));
 	}
 	part = parts[1];
 	segments.push(part);
@@ -32,6 +32,6 @@ module.exports.basename = function basename(path) {
 		j = path.lastIndexOf("\\");
 	const p = i < 0 ? j : j < 0 ? i : i < j ? j : i;
 	if (p < 0) return null;
-	const s = path.substr(p + 1);
+	const s = path.slice(p + 1);
 	return s;
 };
diff --git a/lib/util/path.js b/lib/util/path.js
index d7c148a..b84c146 100644
--- a/lib/util/path.js
+++ b/lib/util/path.js
@@ -192,9 +192,9 @@ const cachedJoin = (rootPath, request) => {
 };
 exports.cachedJoin = cachedJoin;
 
-const checkImportsExportsFieldTarget = relativePath => {
-	let lastNonSlashIndex = 0;
-	let slashIndex = relativePath.indexOf("/", 1);
+const checkExportsFieldTarget = relativePath => {
+	let lastNonSlashIndex = 2;
+	let slashIndex = relativePath.indexOf("/", 2);
 	let cd = 0;
 
 	while (slashIndex !== -1) {
@@ -209,8 +209,6 @@ const checkImportsExportsFieldTarget = relativePath => {
 					);
 				break;
 			}
-			case ".":
-				break;
 			default:
 				cd++;
 				break;
@@ -220,4 +218,4 @@ const checkImportsExportsFieldTarget = relativePath => {
 		slashIndex = relativePath.indexOf("/", lastNonSlashIndex);
 	}
 };
-exports.checkImportsExportsFieldTarget = checkImportsExportsFieldTarget;
+exports.checkExportsFieldTarget = checkExportsFieldTarget;
diff --git a/package.json b/package.json
index 5ddabd6..879a095 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "enhanced-resolve",
-  "version": "5.12.0",
+  "version": "5.9.2",
   "author": "Tobias Koppers @sokra",
   "description": "Offers a async require.resolve function. It's highly configurable.",
   "files": [
diff --git a/tooling/format-file-header.js b/tooling/format-file-header.js
index 8a8e5a3..d6b143f 100644
--- a/tooling/format-file-header.js
+++ b/tooling/format-file-header.js
@@ -149,7 +149,7 @@ for (const filePath of allFiles) {
 			const update = current.update(...match);
 			if (update !== match[0]) {
 				newContent =
-					newContent.substr(0, pos) +
+					newContent.slice(0, pos) +
 					update +
 					newContent.slice(pos + match[0].length);
 				pos += update.length;
diff --git a/types.d.ts b/types.d.ts
index 2c0b668..c418c89 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -45,31 +45,17 @@ declare class CachedInputFileSystem {
 		): void;
 	};
 	statSync: (arg0: string, arg1?: object) => FileSystemStats;
-	readdir: (
-		arg0: string,
-		arg1?:
-			| null
-			| ((
-					arg0?: null | NodeJS.ErrnoException,
-					arg1?: (string | Buffer)[] | any[]
-			  ) => void)
-			| ReaddirOptions
-			| "ascii"
-			| "utf8"
-			| "utf-8"
-			| "utf16le"
-			| "ucs2"
-			| "ucs-2"
-			| "base64"
-			| "latin1"
-			| "binary"
-			| "hex"
-			| "buffer",
-		arg2?: (
-			arg0?: null | NodeJS.ErrnoException,
-			arg1?: (string | Buffer)[] | any[]
-		) => void
-	) => void;
+	readdir: {
+		(
+			arg0: string,
+			arg1: FileSystemCallback<(string | Buffer)[] | FileSystemDirent[]>
+		): void;
+		(
+			arg0: string,
+			arg1: object,
+			arg2: FileSystemCallback<(string | Buffer)[] | FileSystemDirent[]>
+		): void;
+	};
 	readdirSync: (
 		arg0: string,
 		arg1?: object
@@ -105,13 +91,6 @@ declare class CloneBasenamePlugin {
 	target: any;
 	apply(resolver: Resolver): void;
 }
-declare interface ExtensionAliasOption {
-	alias: string | string[];
-	extension: string;
-}
-declare interface ExtensionAliasOptions {
-	[index: string]: string | string[];
-}
 declare interface FileSystem {
 	readFile: {
 		(arg0: string, arg1: FileSystemCallback<string | Buffer>): void;
@@ -121,31 +100,17 @@ declare interface FileSystem {
 			arg2: FileSystemCallback<string | Buffer>
 		): void;
 	};
-	readdir: (
-		arg0: string,
-		arg1?:
-			| null
-			| ((
-					arg0?: null | NodeJS.ErrnoException,
-					arg1?: (string | Buffer)[] | any[]
-			  ) => void)
-			| ReaddirOptions
-			| "ascii"
-			| "utf8"
-			| "utf-8"
-			| "utf16le"
-			| "ucs2"
-			| "ucs-2"
-			| "base64"
-			| "latin1"
-			| "binary"
-			| "hex"
-			| "buffer",
-		arg2?: (
-			arg0?: null | NodeJS.ErrnoException,
-			arg1?: (string | Buffer)[] | any[]
-		) => void
-	) => void;
+	readdir: {
+		(
+			arg0: string,
+			arg1: FileSystemCallback<(string | Buffer)[] | FileSystemDirent[]>
+		): void;
+		(
+			arg0: string,
+			arg1: object,
+			arg2: FileSystemCallback<(string | Buffer)[] | FileSystemDirent[]>
+		): void;
+	};
 	readJson?: {
 		(arg0: string, arg1: FileSystemCallback<object>): void;
 		(arg0: string, arg1: object, arg2: FileSystemCallback<object>): void;
@@ -213,22 +178,6 @@ declare interface PossibleFileSystemError {
 	path?: string;
 	syscall?: string;
 }
-declare interface ReaddirOptions {
-	encoding?:
-		| null
-		| "ascii"
-		| "utf8"
-		| "utf-8"
-		| "utf16le"
-		| "ucs2"
-		| "ucs-2"
-		| "base64"
-		| "latin1"
-		| "binary"
-		| "hex"
-		| "buffer";
-	withFileTypes?: boolean;
-}
 
 /**
  * Resolve context
@@ -265,7 +214,6 @@ declare interface ResolveOptions {
 	alias: AliasOption[];
 	fallback: AliasOption[];
 	aliasFields: Set<string | string[]>;
-	extensionAlias: ExtensionAliasOption[];
 	cachePredicate: (arg0: ResolveRequest) => boolean;
 	cacheWithContext: boolean;
 
@@ -374,11 +322,6 @@ declare interface UserResolveOptions {
 	 */
 	fallback?: AliasOptions | AliasOption[];
 
-	/**
-	 * An object which maps extension to extension aliases
-	 */
-	extensionAlias?: ExtensionAliasOptions;
-
 	/**
 	 * A list of alias fields in description files
 	 */
diff --git a/yarn.lock b/yarn.lock
index c2c1c8f..72cf4c5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1933,9 +1933,11 @@ json-stringify-safe@^5.0.1:
   integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
 
 json5@^2.1.2:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
-  integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
+  integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
+  dependencies:
+    minimist "^1.2.5"
 
 jsonfile@^6.0.1:
   version "6.1.0"
@@ -2104,9 +2106,9 @@ minimatch@3.0.4, minimatch@^3.0.4:
     brace-expansion "^1.1.7"
 
 minimist@^1.2.5:
-  version "1.2.8"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
-  integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+  integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
 
 mkdirp@^0.5.1:
   version "0.5.5"


Test patch:
diff --git a/test/alias.js b/test/alias.js
index a167294..84479f0 100644
--- a/test/alias.js
+++ b/test/alias.js
@@ -39,9 +39,6 @@ describe("alias", function () {
 				recursive: "recursive/dir",
 				"/d/dir": "/c/dir",
 				"/d/index.js": "/c/index",
-				// alias configuration should work
-				"#": "/c/dir",
-				"@": "/c/dir",
 				ignored: false
 			},
 			modules: "/",
@@ -64,14 +61,6 @@ describe("alias", function () {
 			.resolveSync({}, "/", "aliasA/dir/index")
 			.should.be.eql("/a/dir/index");
 	});
-	it('should resolve "#" alias', () => {
-		resolver.resolveSync({}, "/", "#").should.be.eql("/c/dir/index");
-		resolver.resolveSync({}, "/", "#/index").should.be.eql("/c/dir/index");
-	});
-	it('should resolve "@" alias', () => {
-		resolver.resolveSync({}, "/", "@").should.be.eql("/c/dir/index");
-		resolver.resolveSync({}, "/", "@/index").should.be.eql("/c/dir/index");
-	});
 	it("should resolve an ignore module", () => {
 		resolver.resolveSync({}, "/", "ignored").should.be.eql(false);
 	});
diff --git a/test/browserField.js b/test/browserField.js
index 95cbf9d..4a31f42 100644
--- a/test/browserField.js
+++ b/test/browserField.js
@@ -84,10 +84,4 @@ describe("browserField", function () {
 			.resolveSync({}, p(), "./lib/main2.js")
 			.should.be.eql(p("lib", "browser.js"));
 	});
-
-	it("should check only alias field properties", () => {
-		resolver
-			.resolveSync({}, p(), "./toString")
-			.should.be.eql(p("lib", "toString.js"));
-	});
 });
diff --git a/test/exportsField.js b/test/exportsField.js
index d2f2d68..03f62b8 100644
--- a/test/exportsField.js
+++ b/test/exportsField.js
@@ -10,7 +10,6 @@ const CachedInputFileSystem = require("../lib/CachedInputFileSystem");
 const fixture = path.resolve(__dirname, "fixtures", "exports-field");
 const fixture2 = path.resolve(__dirname, "fixtures", "exports-field2");
 const fixture3 = path.resolve(__dirname, "fixtures", "exports-field3");
-const fixture4 = path.resolve(__dirname, "fixtures", "exports-field-error");
 
 describe("Process exports field", function exportsField() {
 	/** @type {Array<{name: string, expect: string[]|Error, suite: [ExportsField, string, string[]]}>} */
@@ -2392,15 +2391,6 @@ describe("ExportsFieldPlugin", () => {
 		});
 	});
 
-	it("should throw error if target is invalid", done => {
-		resolver.resolve({}, fixture4, "exports-field", {}, (err, result) => {
-			if (!err) throw new Error(`expect error, got ${result}`);
-			err.should.be.instanceof(Error);
-			err.message.should.match(/Trying to access out of package scope/);
-			done();
-		});
-	});
-
 	it("throw error if exports field is invalid", done => {
 		resolver.resolve(
 			{},
diff --git a/test/extension-alias.js b/test/extension-alias.js
deleted file mode 100644
index 0ebca62..0000000
--- a/test/extension-alias.js
+++ /dev/null
@@ -1,89 +0,0 @@
-const path = require("path");
-const fs = require("fs");
-const should = require("should");
-
-const CachedInputFileSystem = require("../lib/CachedInputFileSystem");
-const ResolverFactory = require("../lib/ResolverFactory");
-
-/** @typedef {import("../lib/util/entrypoints").ImportsField} ImportsField */
-
-describe("extension-alias", () => {
-	const fixture = path.resolve(__dirname, "fixtures", "extension-alias");
-	const nodeFileSystem = new CachedInputFileSystem(fs, 4000);
-
-	const resolver = ResolverFactory.createResolver({
-		extensions: [".js"],
-		fileSystem: nodeFileSystem,
-		mainFiles: ["index.js"],
-		extensionAlias: {
-			".js": [".ts", ".js"],
-			".mjs": ".mts"
-		}
-	});
-
-	it("should alias fully specified file", done => {
-		resolver.resolve({}, fixture, "./index.js", {}, (err, result) => {
-			if (err) return done(err);
-			should(result).be.eql(path.resolve(fixture, "index.ts"));
-			done();
-		});
-	});
-
-	it("should alias fully specified file when there are two alternatives", done => {
-		resolver.resolve({}, fixture, "./dir/index.js", {}, (err, result) => {
-			if (err) return done(err);
-			should(result).be.eql(path.resolve(fixture, "dir", "index.ts"));
-			done();
-		});
-	});
-
-	it("should also allow the second alternative", done => {
-		resolver.resolve({}, fixture, "./dir2/index.js", {}, (err, result) => {
-			if (err) return done(err);
-			should(result).be.eql(path.resolve(fixture, "dir2", "index.js"));
-			done();
-		});
-	});
-
-	it("should support alias option without an array", done => {
-		resolver.resolve({}, fixture, "./dir2/index.mjs", {}, (err, result) => {
-			if (err) return done(err);
-			should(result).be.eql(path.resolve(fixture, "dir2", "index.mts"));
-			done();
-		});
-	});
-
-	it("should not allow to fallback to the original extension or add extensions", done => {
-		resolver.resolve({}, fixture, "./index.mjs", {}, (err, result) => {
-			should(err).be.instanceOf(Error);
-			done();
-		});
-	});
-
-	describe("should not apply extension alias to extensions or mainFiles field", () => {
-		const resolver = ResolverFactory.createResolver({
-			extensions: [".js"],
-			fileSystem: nodeFileSystem,
-			mainFiles: ["index.js"],
-			extensionAlias: {
-				".js": []
-			}
-		});
-
-		it("directory", done => {
-			resolver.resolve({}, fixture, "./dir2", {}, (err, result) => {
-				if (err) return done(err);
-				should(result).be.eql(path.resolve(fixture, "dir2", "index.js"));
-				done();
-			});
-		});
-
-		it("file", done => {
-			resolver.resolve({}, fixture, "./dir2/index", {}, (err, result) => {
-				if (err) return done(err);
-				should(result).be.eql(path.resolve(fixture, "dir2", "index.js"));
-				done();
-			});
-		});
-	});
-});
diff --git a/test/fixtures/browser-module/lib/toString.js b/test/fixtures/browser-module/lib/toString.js
deleted file mode 100644
index e69de29..0000000
diff --git a/test/fixtures/browser-module/package.json b/test/fixtures/browser-module/package.json
index 217e0a9..8e4cbc7 100644
--- a/test/fixtures/browser-module/package.json
+++ b/test/fixtures/browser-module/package.json
@@ -4,7 +4,6 @@
     "./lib/replaced.js": "./lib/browser",
     "module-a": "./browser/module-a.js",
     "module-b": "module-c",
-    "./toString": "./lib/toString.js",
     ".": false
   },
   "innerBrowser1": {
diff --git a/test/fixtures/exports-field-error/node_modules/exports-field/package.json b/test/fixtures/exports-field-error/node_modules/exports-field/package.json
deleted file mode 100644
index f0edf44..0000000
--- a/test/fixtures/exports-field-error/node_modules/exports-field/package.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "name": "exports-field",
-  "version": "1.0.0",
-  "exports": {
-    ".": "./a/../b/../../pack1/index.js"
-  }
-}
diff --git a/test/fixtures/exports-field-error/node_modules/pack1/index.js b/test/fixtures/exports-field-error/node_modules/pack1/index.js
deleted file mode 100644
index e69de29..0000000
diff --git a/test/fixtures/extension-alias/dir/index.js b/test/fixtures/extension-alias/dir/index.js
deleted file mode 100644
index e69de29..0000000
diff --git a/test/fixtures/extension-alias/dir/index.ts b/test/fixtures/extension-alias/dir/index.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/test/fixtures/extension-alias/dir2/index.js b/test/fixtures/extension-alias/dir2/index.js
deleted file mode 100644
index e69de29..0000000
diff --git a/test/fixtures/extension-alias/dir2/index.mts b/test/fixtures/extension-alias/dir2/index.mts
deleted file mode 100644
index e69de29..0000000
diff --git a/test/fixtures/extension-alias/index.js b/test/fixtures/extension-alias/index.js
deleted file mode 100644
index e69de29..0000000
diff --git a/test/fixtures/extension-alias/index.mjs b/test/fixtures/extension-alias/index.mjs
deleted file mode 100644
index e69de29..0000000
diff --git a/test/fixtures/extension-alias/index.mts.js b/test/fixtures/extension-alias/index.mts.js
deleted file mode 100644
index e69de29..0000000
diff --git a/test/fixtures/extension-alias/index.ts b/test/fixtures/extension-alias/index.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/test/importsField.js b/test/importsField.js
index 334cd87..a075a31 100644
--- a/test/importsField.js
+++ b/test/importsField.js
@@ -1211,11 +1211,11 @@ describe("ImportsFieldPlugin", () => {
 		);
 	});
 
-	it("should disallow resolve out of package scope", done => {
+	it("should resolve out of package scope", done => {
 		resolver.resolve({}, fixture, "#b", {}, (err, result) => {
-			if (!err) throw new Error(`expect error, got ${result}`);
-			err.should.be.instanceof(Error);
-			err.message.should.match(/Trying to access out of package scope/);
+			if (err) return done(err);
+			if (!result) throw new Error("No result");
+			result.should.equal(path.resolve(fixture, "../b.js"));
 			done();
 		});
 	});
@@ -1229,10 +1229,10 @@ describe("ImportsFieldPlugin", () => {
 			conditionNames: ["webpack"]
 		});
 
-		resolver.resolve({}, fixture, "#imports-field", {}, (err, result) => {
+		resolver.resolve({}, fixture, "#b", {}, (err, result) => {
 			if (err) return done(err);
 			if (!result) throw new Error("No result");
-			result.should.equal(path.resolve(fixture, "./b.js"));
+			result.should.equal(path.resolve(fixture, "../b.js"));
 			done();
 		});
 	});
diff --git a/test/path.js b/test/path.js
deleted file mode 100644
index 8e9fd8f..0000000
--- a/test/path.js
+++ /dev/null
@@ -1,30 +0,0 @@
-require("should");
-
-const { checkImportsExportsFieldTarget } = require("../lib/util/path");
-
-describe("checkImportsExportsFieldTarget", () => {
-	/**
-	 * @type {string[]}
-	 */
-	const errorCases = [
-		"../a.js",
-		"../",
-		"./a/b/../../../c.js",
-		"./a/b/../../../",
-		"./../../c.js",
-		"./../../",
-		"./a/../b/../../c.js",
-		"./a/../b/../../",
-		"./././../"
-	];
-
-	errorCases.forEach(case_ => {
-		it(case_, done => {
-			const error = checkImportsExportsFieldTarget(case_);
-			if (!error) return done("expect error");
-			error.should.be.instanceof(Error);
-			error.message.should.match(/Trying to access out of package scope/);
-			done();
-		});
-	});
-});