| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Tobias Koppers @sokra*/"use strict";const util = require("util");const Tapable = require("tapable/lib/Tapable");const SyncHook = require("tapable/lib/SyncHook");const AsyncSeriesBailHook = require("tapable/lib/AsyncSeriesBailHook");const AsyncSeriesHook = require("tapable/lib/AsyncSeriesHook");const createInnerContext = require("./createInnerContext");const REGEXP_NOT_MODULE = /^\.$|^\.[\\/]|^\.\.$|^\.\.[\\/]|^\/|^[A-Z]:[\\/]/i;const REGEXP_DIRECTORY = /[\\/]$/i;const memoryFsJoin = require("memory-fs/lib/join");const memoizedJoin = new Map();const memoryFsNormalize = require("memory-fs/lib/normalize");function withName(name, hook) {	hook.name = name;	return hook;}function toCamelCase(str) {	return str.replace(/-([a-z])/g, str => str.substr(1).toUpperCase());}const deprecatedPushToMissing = util.deprecate((set, item) => {	set.add(item);}, "Resolver: 'missing' is now a Set. Use add instead of push.");const deprecatedResolveContextInCallback = util.deprecate(x => {	return x;}, "Resolver: The callback argument was splitted into resolveContext and callback.");const deprecatedHookAsString = util.deprecate(x => {	return x;}, "Resolver#doResolve: The type arguments (string) is now a hook argument (Hook). Pass a reference to the hook instead.");class Resolver extends Tapable {	constructor(fileSystem) {		super();		this.fileSystem = fileSystem;		this.hooks = {			resolveStep: withName("resolveStep", new SyncHook(["hook", "request"])),			noResolve: withName("noResolve", new SyncHook(["request", "error"])),			resolve: withName(				"resolve",				new AsyncSeriesBailHook(["request", "resolveContext"])			),			result: new AsyncSeriesHook(["result", "resolveContext"])		};		this._pluginCompat.tap("Resolver: before/after", options => {			if (/^before-/.test(options.name)) {				options.name = options.name.substr(7);				options.stage = -10;			} else if (/^after-/.test(options.name)) {				options.name = options.name.substr(6);				options.stage = 10;			}		});		this._pluginCompat.tap("Resolver: step hooks", options => {			const name = options.name;			const stepHook = !/^resolve(-s|S)tep$|^no(-r|R)esolve$/.test(name);			if (stepHook) {				options.async = true;				this.ensureHook(name);				const fn = options.fn;				options.fn = (request, resolverContext, callback) => {					const innerCallback = (err, result) => {						if (err) return callback(err);						if (result !== undefined) return callback(null, result);						callback();					};					for (const key in resolverContext) {						innerCallback[key] = resolverContext[key];					}					fn.call(this, request, innerCallback);				};			}		});	}	ensureHook(name) {		if (typeof name !== "string") return name;		name = toCamelCase(name);		if (/^before/.test(name)) {			return this.ensureHook(				name[6].toLowerCase() + name.substr(7)			).withOptions({				stage: -10			});		}		if (/^after/.test(name)) {			return this.ensureHook(				name[5].toLowerCase() + name.substr(6)			).withOptions({				stage: 10			});		}		const hook = this.hooks[name];		if (!hook) {			return (this.hooks[name] = withName(				name,				new AsyncSeriesBailHook(["request", "resolveContext"])			));		}		return hook;	}	getHook(name) {		if (typeof name !== "string") return name;		name = toCamelCase(name);		if (/^before/.test(name)) {			return this.getHook(name[6].toLowerCase() + name.substr(7)).withOptions({				stage: -10			});		}		if (/^after/.test(name)) {			return 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;	}	resolveSync(context, path, request) {		let err,			result,			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;		return result;	}	resolve(context, path, request, resolveContext, callback) {		// TODO remove in enhanced-resolve 5		// For backward compatiblity START		if (typeof callback !== "function") {			callback = deprecatedResolveContextInCallback(resolveContext);			// resolveContext is a function containing additional properties			// It's now used for resolveContext and callback		}		// END		const obj = {			context: context,			path: path,			request: request		};		const message = "resolve '" + request + "' in '" + path + "'";		// Try to resolve assuming there is no error		// We don't log stuff in this case		return this.doResolve(			this.hooks.resolve,			obj,			message,			{				missing: resolveContext.missing,				stack: resolveContext.stack			},			(err, result) => {				if (!err && result) {					return callback(						null,						result.path === false ? false : result.path + (result.query || ""),						result					);				}				const localMissing = new Set();				// TODO remove in enhanced-resolve 5				localMissing.push = item => deprecatedPushToMissing(localMissing, item);				const log = [];				return this.doResolve(					this.hooks.resolve,					obj,					message,					{						log: msg => {							if (resolveContext.log) {								resolveContext.log(msg);							}							log.push(msg);						},						missing: localMissing,						stack: resolveContext.stack					},					(err, result) => {						if (err) return callback(err);						const error = new Error("Can't " + message);						error.details = log.join("\n");						error.missing = Array.from(localMissing);						this.hooks.noResolve.call(obj, error);						return callback(error);					}				);			}		);	}	doResolve(hook, request, message, resolveContext, callback) {		// TODO remove in enhanced-resolve 5		// For backward compatiblity START		if (typeof callback !== "function") {			callback = deprecatedResolveContextInCallback(resolveContext);			// resolveContext is a function containing additional properties			// It's now used for resolveContext and callback		}		if (typeof hook === "string") {			const name = toCamelCase(hook);			hook = deprecatedHookAsString(this.hooks[name]);			if (!hook) {				throw new Error(`Hook "${name}" doesn't exist`);			}		}		// END		if (typeof callback !== "function")			throw new Error("callback is not a function " + Array.from(arguments));		if (!resolveContext)			throw new Error(				"resolveContext is not an object " + Array.from(arguments)			);		const stackLine =			hook.name +			": (" +			request.path +			") " +			(request.request || "") +			(request.query || "") +			(request.directory ? " directory" : "") +			(request.module ? " module" : "");		let newStack;		if (resolveContext.stack) {			newStack = new Set(resolveContext.stack);			if (resolveContext.stack.has(stackLine)) {				// Prevent recursion				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(stackLine);		} else {			newStack = new Set([stackLine]);		}		this.hooks.resolveStep.call(hook, request);		if (hook.isUsed()) {			const innerContext = createInnerContext(				{					log: resolveContext.log,					missing: resolveContext.missing,					stack: newStack				},				message			);			return hook.callAsync(request, innerContext, (err, result) => {				if (err) return callback(err);				if (result) return callback(null, result);				callback();			});		} else {			callback();		}	}	parse(identifier) {		if (identifier === "") return null;		const part = {			request: "",			query: "",			module: false,			directory: false,			file: false		};		const idxQuery = identifier.indexOf("?");		if (idxQuery === 0) {			part.query = identifier;		} else if (idxQuery > 0) {			part.request = identifier.slice(0, idxQuery);			part.query = identifier.slice(idxQuery);		} else {			part.request = identifier;		}		if (part.request) {			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 !REGEXP_NOT_MODULE.test(path);	}	isDirectory(path) {		return REGEXP_DIRECTORY.test(path);	}	join(path, request) {		let cacheEntry;		let pathCache = memoizedJoin.get(path);		if (typeof pathCache === "undefined") {			memoizedJoin.set(path, (pathCache = new Map()));		} else {			cacheEntry = pathCache.get(request);			if (typeof cacheEntry !== "undefined") return cacheEntry;		}		cacheEntry = memoryFsJoin(path, request);		pathCache.set(request, cacheEntry);		return cacheEntry;	}	normalize(path) {		return memoryFsNormalize(path);	}}module.exports = Resolver;
 |