aboutsummaryrefslogtreecommitdiff
path: root/node_modules/enhanced-resolve/lib/Resolver.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/enhanced-resolve/lib/Resolver.js')
-rw-r--r--node_modules/enhanced-resolve/lib/Resolver.js475
1 files changed, 475 insertions, 0 deletions
diff --git a/node_modules/enhanced-resolve/lib/Resolver.js b/node_modules/enhanced-resolve/lib/Resolver.js
new file mode 100644
index 0000000..a312008
--- /dev/null
+++ b/node_modules/enhanced-resolve/lib/Resolver.js
@@ -0,0 +1,475 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { AsyncSeriesBailHook, AsyncSeriesHook, SyncHook } = require("tapable");
+const createInnerContext = require("./createInnerContext");
+const { parseIdentifier } = require("./util/identifier");
+const {
+ normalize,
+ cachedJoin: join,
+ getType,
+ PathType
+} = require("./util/path");
+
+/** @typedef {import("./ResolverFactory").ResolveOptions} ResolveOptions */
+
+/**
+ * @typedef {Object} FileSystemStats
+ * @property {function(): boolean} isDirectory
+ * @property {function(): boolean} isFile
+ */
+
+/**
+ * @typedef {Object} FileSystemDirent
+ * @property {Buffer | string} name
+ * @property {function(): boolean} isDirectory
+ * @property {function(): boolean} isFile
+ */
+
+/**
+ * @typedef {Object} PossibleFileSystemError
+ * @property {string=} code
+ * @property {number=} errno
+ * @property {string=} path
+ * @property {string=} syscall
+ */
+
+/**
+ * @template T
+ * @callback FileSystemCallback
+ * @param {PossibleFileSystemError & Error | null | undefined} err
+ * @param {T=} result
+ */
+
+/**
+ * @typedef {Object} FileSystem
+ * @property {(function(string, FileSystemCallback<Buffer | string>): void) & function(string, object, FileSystemCallback<Buffer | string>): void} readFile
+ * @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
+ * @property {(function(string, FileSystemCallback<FileSystemStats>): void) & function(string, object, FileSystemCallback<Buffer | string>): void} stat
+ */
+
+/**
+ * @typedef {Object} SyncFileSystem
+ * @property {function(string, object=): Buffer | string} readFileSync
+ * @property {function(string, object=): (Buffer | string)[] | FileSystemDirent[]} readdirSync
+ * @property {(function(string, object=): object)=} readJsonSync
+ * @property {function(string, object=): Buffer | string} readlinkSync
+ * @property {function(string, object=): FileSystemStats=} lstatSync
+ * @property {function(string, object=): FileSystemStats} statSync
+ */
+
+/**
+ * @typedef {Object} ParsedIdentifier
+ * @property {string} request
+ * @property {string} query
+ * @property {string} fragment
+ * @property {boolean} directory
+ * @property {boolean} module
+ * @property {boolean} file
+ * @property {boolean} internal
+ */
+
+/**
+ * @typedef {Object} BaseResolveRequest
+ * @property {string | false} path
+ * @property {string=} descriptionFilePath
+ * @property {string=} descriptionFileRoot
+ * @property {object=} descriptionFileData
+ * @property {string=} relativePath
+ * @property {boolean=} ignoreSymlinks
+ * @property {boolean=} fullySpecified
+ */
+
+/** @typedef {BaseResolveRequest & Partial<ParsedIdentifier>} ResolveRequest */
+
+/**
+ * String with special formatting
+ * @typedef {string} StackEntry
+ */
+
+/** @template T @typedef {{ add: (T) => void }} WriteOnlySet */
+
+/**
+ * Resolve context
+ * @typedef {Object} ResolveContext
+ * @property {WriteOnlySet<string>=} contextDependencies
+ * @property {WriteOnlySet<string>=} fileDependencies files that was found on file system
+ * @property {WriteOnlySet<string>=} missingDependencies dependencies that was not found on file system
+ * @property {Set<StackEntry>=} stack set of hooks' calls. For instance, `resolve → parsedResolve → describedResolve`,
+ * @property {(function(string): void)=} log log function
+ */
+
+/** @typedef {AsyncSeriesBailHook<[ResolveRequest, ResolveContext], ResolveRequest | null>} ResolveStepHook */
+
+/**
+ * @param {string} str input string
+ * @returns {string} in camel case
+ */
+function toCamelCase(str) {
+ return str.replace(/-([a-z])/g, str => str.substr(1).toUpperCase());
+}
+
+class Resolver {
+ /**
+ * @param {ResolveStepHook} hook hook
+ * @param {ResolveRequest} request request
+ * @returns {StackEntry} stack entry
+ */
+ static createStackEntry(hook, request) {
+ return (
+ hook.name +
+ ": (" +
+ request.path +
+ ") " +
+ (request.request || "") +
+ (request.query || "") +
+ (request.fragment || "") +
+ (request.directory ? " directory" : "") +
+ (request.module ? " module" : "")
+ );
+ }
+
+ /**
+ * @param {FileSystem} fileSystem a filesystem
+ * @param {ResolveOptions} options options
+ */
+ constructor(fileSystem, options) {
+ this.fileSystem = fileSystem;
+ this.options = options;
+ this.hooks = {
+ /** @type {SyncHook<[ResolveStepHook, ResolveRequest], void>} */
+ resolveStep: new SyncHook(["hook", "request"], "resolveStep"),
+ /** @type {SyncHook<[ResolveRequest, Error]>} */
+ noResolve: new SyncHook(["request", "error"], "noResolve"),
+ /** @type {ResolveStepHook} */
+ resolve: new AsyncSeriesBailHook(
+ ["request", "resolveContext"],
+ "resolve"
+ ),
+ /** @type {AsyncSeriesHook<[ResolveRequest, ResolveContext]>} */
+ result: new AsyncSeriesHook(["result", "resolveContext"], "result")
+ };
+ }
+
+ /**
+ * @param {string | ResolveStepHook} name hook name or hook itself
+ * @returns {ResolveStepHook} the hook
+ */
+ ensureHook(name) {
+ if (typeof name !== "string") {
+ return name;
+ }
+ name = toCamelCase(name);
+ if (/^before/.test(name)) {
+ return /** @type {ResolveStepHook} */ (this.ensureHook(
+ name[6].toLowerCase() + name.substr(7)
+ ).withOptions({
+ stage: -10
+ }));
+ }
+ if (/^after/.test(name)) {
+ return /** @type {ResolveStepHook} */ (this.ensureHook(
+ name[5].toLowerCase() + name.substr(6)
+ ).withOptions({
+ stage: 10
+ }));
+ }
+ const hook = this.hooks[name];
+ if (!hook) {
+ return (this.hooks[name] = new AsyncSeriesBailHook(
+ ["request", "resolveContext"],
+ name
+ ));
+ }
+ return hook;
+ }
+
+ /**
+ * @param {string | ResolveStepHook} name hook name or hook itself
+ * @returns {ResolveStepHook} the hook
+ */
+ getHook(name) {
+ if (typeof name !== "string") {
+ return name;
+ }
+ name = toCamelCase(name);
+ if (/^before/.test(name)) {
+ return /** @type {ResolveStepHook} */ (this.getHook(
+ name[6].toLowerCase() + name.substr(7)
+ ).withOptions({
+ stage: -10
+ }));
+ }
+ if (/^after/.test(name)) {
+ return /** @type {ResolveStepHook} */ (this.getHook(
+ name[5].toLowerCase() + name.substr(6)
+ ).withOptions({
+ stage: 10
+ }));
+ }
+ const hook = this.hooks[name];
+ if (!hook) {
+ throw new Error(`Hook ${name} doesn't exist`);
+ }
+ return hook;
+ }
+
+ /**
+ * @param {object} context context information object
+ * @param {string} path context path
+ * @param {string} request request string
+ * @returns {string | false} result
+ */
+ resolveSync(context, path, request) {
+ /** @type {Error | null | undefined} */
+ let err = undefined;
+ /** @type {string | false | undefined} */
+ let result = undefined;
+ let sync = false;
+ this.resolve(context, path, request, {}, (e, r) => {
+ err = e;
+ result = r;
+ sync = true;
+ });
+ if (!sync) {
+ throw new Error(
+ "Cannot 'resolveSync' because the fileSystem is not sync. Use 'resolve'!"
+ );
+ }
+ if (err) throw err;
+ if (result === undefined) throw new Error("No result");
+ return result;
+ }
+
+ /**
+ * @param {object} context context information object
+ * @param {string} path context path
+ * @param {string} request request string
+ * @param {ResolveContext} resolveContext resolve context
+ * @param {function(Error | null, (string|false)=, ResolveRequest=): void} callback callback function
+ * @returns {void}
+ */
+ resolve(context, path, request, resolveContext, callback) {
+ if (!context || typeof context !== "object")
+ return callback(new Error("context argument is not an object"));
+ if (typeof path !== "string")
+ return callback(new Error("path argument is not a string"));
+ if (typeof request !== "string")
+ return callback(new Error("path argument is not a string"));
+ if (!resolveContext)
+ return callback(new Error("resolveContext argument is not set"));
+
+ const obj = {
+ context: context,
+ path: path,
+ request: request
+ };
+
+ const message = `resolve '${request}' in '${path}'`;
+
+ const finishResolved = result => {
+ return callback(
+ null,
+ result.path === false
+ ? false
+ : `${result.path.replace(/#/g, "\0#")}${
+ result.query ? result.query.replace(/#/g, "\0#") : ""
+ }${result.fragment || ""}`,
+ result
+ );
+ };
+
+ const finishWithoutResolve = log => {
+ /**
+ * @type {Error & {details?: string}}
+ */
+ const error = new Error("Can't " + message);
+ error.details = log.join("\n");
+ this.hooks.noResolve.call(obj, error);
+ return callback(error);
+ };
+
+ if (resolveContext.log) {
+ // We need log anyway to capture it in case of an error
+ const parentLog = resolveContext.log;
+ const log = [];
+ return this.doResolve(
+ this.hooks.resolve,
+ obj,
+ message,
+ {
+ log: msg => {
+ parentLog(msg);
+ log.push(msg);
+ },
+ fileDependencies: resolveContext.fileDependencies,
+ contextDependencies: resolveContext.contextDependencies,
+ missingDependencies: resolveContext.missingDependencies,
+ stack: resolveContext.stack
+ },
+ (err, result) => {
+ if (err) return callback(err);
+
+ if (result) return finishResolved(result);
+
+ return finishWithoutResolve(log);
+ }
+ );
+ } else {
+ // Try to resolve assuming there is no error
+ // We don't log stuff in this case
+ return this.doResolve(
+ this.hooks.resolve,
+ obj,
+ message,
+ {
+ log: undefined,
+ fileDependencies: resolveContext.fileDependencies,
+ contextDependencies: resolveContext.contextDependencies,
+ missingDependencies: resolveContext.missingDependencies,
+ stack: resolveContext.stack
+ },
+ (err, result) => {
+ if (err) return callback(err);
+
+ if (result) return finishResolved(result);
+
+ // log is missing for the error details
+ // so we redo the resolving for the log info
+ // this is more expensive to the success case
+ // is assumed by default
+
+ const log = [];
+
+ return this.doResolve(
+ this.hooks.resolve,
+ obj,
+ message,
+ {
+ log: msg => log.push(msg),
+ stack: resolveContext.stack
+ },
+ (err, result) => {
+ if (err) return callback(err);
+
+ return finishWithoutResolve(log);
+ }
+ );
+ }
+ );
+ }
+ }
+
+ doResolve(hook, request, message, resolveContext, callback) {
+ const stackEntry = Resolver.createStackEntry(hook, request);
+
+ let newStack;
+ if (resolveContext.stack) {
+ newStack = new Set(resolveContext.stack);
+ if (resolveContext.stack.has(stackEntry)) {
+ /**
+ * Prevent recursion
+ * @type {Error & {recursion?: boolean}}
+ */
+ const recursionError = new Error(
+ "Recursion in resolving\nStack:\n " +
+ Array.from(newStack).join("\n ")
+ );
+ recursionError.recursion = true;
+ if (resolveContext.log)
+ resolveContext.log("abort resolving because of recursion");
+ return callback(recursionError);
+ }
+ newStack.add(stackEntry);
+ } else {
+ newStack = new Set([stackEntry]);
+ }
+ this.hooks.resolveStep.call(hook, request);
+
+ if (hook.isUsed()) {
+ const innerContext = createInnerContext(
+ {
+ log: resolveContext.log,
+ fileDependencies: resolveContext.fileDependencies,
+ contextDependencies: resolveContext.contextDependencies,
+ missingDependencies: resolveContext.missingDependencies,
+ stack: newStack
+ },
+ message
+ );
+ return hook.callAsync(request, innerContext, (err, result) => {
+ if (err) return callback(err);
+ if (result) return callback(null, result);
+ callback();
+ });
+ } else {
+ callback();
+ }
+ }
+
+ /**
+ * @param {string} identifier identifier
+ * @returns {ParsedIdentifier} parsed identifier
+ */
+ parse(identifier) {
+ const part = {
+ request: "",
+ query: "",
+ fragment: "",
+ module: false,
+ directory: false,
+ file: false,
+ internal: false
+ };
+
+ const parsedIdentifier = parseIdentifier(identifier);
+
+ if (!parsedIdentifier) return part;
+
+ [part.request, part.query, part.fragment] = parsedIdentifier;
+
+ if (part.request.length > 0) {
+ part.internal = this.isPrivate(identifier);
+ 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);
+ }
+ }
+
+ return part;
+ }
+
+ isModule(path) {
+ return getType(path) === PathType.Normal;
+ }
+
+ isPrivate(path) {
+ return getType(path) === PathType.Internal;
+ }
+
+ /**
+ * @param {string} path a path
+ * @returns {boolean} true, if the path is a directory path
+ */
+ isDirectory(path) {
+ return path.endsWith("/");
+ }
+
+ join(path, request) {
+ return join(path, request);
+ }
+
+ normalize(path) {
+ return normalize(path);
+ }
+}
+
+module.exports = Resolver;