504 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			504 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/*
 | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
						|
	Author Tobias Koppers @sokra
 | 
						|
*/
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
const RuntimeGlobals = require("../RuntimeGlobals");
 | 
						|
const AMDDefineDependency = require("./AMDDefineDependency");
 | 
						|
const AMDRequireArrayDependency = require("./AMDRequireArrayDependency");
 | 
						|
const AMDRequireContextDependency = require("./AMDRequireContextDependency");
 | 
						|
const AMDRequireItemDependency = require("./AMDRequireItemDependency");
 | 
						|
const ConstDependency = require("./ConstDependency");
 | 
						|
const ContextDependencyHelpers = require("./ContextDependencyHelpers");
 | 
						|
const DynamicExports = require("./DynamicExports");
 | 
						|
const LocalModuleDependency = require("./LocalModuleDependency");
 | 
						|
const { addLocalModule, getLocalModule } = require("./LocalModulesHelpers");
 | 
						|
 | 
						|
/** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */
 | 
						|
/** @typedef {import("estree").CallExpression} CallExpression */
 | 
						|
/** @typedef {import("estree").Expression} Expression */
 | 
						|
/** @typedef {import("estree").FunctionExpression} FunctionExpression */
 | 
						|
/** @typedef {import("estree").Identifier} Identifier */
 | 
						|
/** @typedef {import("estree").Literal} Literal */
 | 
						|
/** @typedef {import("estree").MemberExpression} MemberExpression */
 | 
						|
/** @typedef {import("estree").ObjectExpression} ObjectExpression */
 | 
						|
/** @typedef {import("estree").SimpleCallExpression} SimpleCallExpression */
 | 
						|
/** @typedef {import("estree").SpreadElement} SpreadElement */
 | 
						|
/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
 | 
						|
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
 | 
						|
/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
 | 
						|
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
 | 
						|
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Expression | SpreadElement} expr expression
 | 
						|
 * @returns {expr is CallExpression} true if it's a bound function expression
 | 
						|
 */
 | 
						|
const isBoundFunctionExpression = (expr) => {
 | 
						|
	if (expr.type !== "CallExpression") return false;
 | 
						|
	if (expr.callee.type !== "MemberExpression") return false;
 | 
						|
	if (expr.callee.computed) return false;
 | 
						|
	if (expr.callee.object.type !== "FunctionExpression") return false;
 | 
						|
	if (expr.callee.property.type !== "Identifier") return false;
 | 
						|
	if (expr.callee.property.name !== "bind") return false;
 | 
						|
	return true;
 | 
						|
};
 | 
						|
 | 
						|
/** @typedef {FunctionExpression | ArrowFunctionExpression} UnboundFunctionExpression */
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Expression | SpreadElement} expr expression
 | 
						|
 * @returns {expr is FunctionExpression | ArrowFunctionExpression} true when unbound function expression
 | 
						|
 */
 | 
						|
const isUnboundFunctionExpression = (expr) => {
 | 
						|
	if (expr.type === "FunctionExpression") return true;
 | 
						|
	if (expr.type === "ArrowFunctionExpression") return true;
 | 
						|
	return false;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Expression | SpreadElement} expr expression
 | 
						|
 * @returns {expr is FunctionExpression | ArrowFunctionExpression | CallExpression} true when callable
 | 
						|
 */
 | 
						|
const isCallable = (expr) => {
 | 
						|
	if (isUnboundFunctionExpression(expr)) return true;
 | 
						|
	if (isBoundFunctionExpression(expr)) return true;
 | 
						|
	return false;
 | 
						|
};
 | 
						|
 | 
						|
const PLUGIN_NAME = "AMDDefineDependencyParserPlugin";
 | 
						|
 | 
						|
class AMDDefineDependencyParserPlugin {
 | 
						|
	/**
 | 
						|
	 * @param {JavascriptParserOptions} options parserOptions
 | 
						|
	 */
 | 
						|
	constructor(options) {
 | 
						|
		this.options = options;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {JavascriptParser} parser the parser
 | 
						|
	 * @returns {void}
 | 
						|
	 */
 | 
						|
	apply(parser) {
 | 
						|
		parser.hooks.call
 | 
						|
			.for("define")
 | 
						|
			.tap(PLUGIN_NAME, this.processCallDefine.bind(this, parser));
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {JavascriptParser} parser the parser
 | 
						|
	 * @param {CallExpression} expr call expression
 | 
						|
	 * @param {BasicEvaluatedExpression} param param
 | 
						|
	 * @param {Record<number, string>} identifiers identifiers
 | 
						|
	 * @param {string=} namedModule named module
 | 
						|
	 * @returns {boolean | undefined} result
 | 
						|
	 */
 | 
						|
	processArray(parser, expr, param, identifiers, namedModule) {
 | 
						|
		if (param.isArray()) {
 | 
						|
			const items = /** @type {BasicEvaluatedExpression[]} */ (param.items);
 | 
						|
			for (const [idx, item] of items.entries()) {
 | 
						|
				if (
 | 
						|
					item.isString() &&
 | 
						|
					["require", "module", "exports"].includes(
 | 
						|
						/** @type {string} */ (item.string)
 | 
						|
					)
 | 
						|
				) {
 | 
						|
					identifiers[/** @type {number} */ (idx)] =
 | 
						|
						/** @type {string} */
 | 
						|
						(item.string);
 | 
						|
				}
 | 
						|
				const result = this.processItem(parser, expr, item, namedModule);
 | 
						|
				if (result === undefined) {
 | 
						|
					this.processContext(parser, expr, item);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return true;
 | 
						|
		} else if (param.isConstArray()) {
 | 
						|
			/** @type {(string | LocalModuleDependency | AMDRequireItemDependency)[]} */
 | 
						|
			const deps = [];
 | 
						|
			const array = /** @type {string[]} */ (param.array);
 | 
						|
			for (const [idx, request] of array.entries()) {
 | 
						|
				let dep;
 | 
						|
				let localModule;
 | 
						|
				if (request === "require") {
 | 
						|
					identifiers[idx] = request;
 | 
						|
					dep = RuntimeGlobals.require;
 | 
						|
				} else if (["exports", "module"].includes(request)) {
 | 
						|
					identifiers[idx] = request;
 | 
						|
					dep = request;
 | 
						|
				} else if ((localModule = getLocalModule(parser.state, request))) {
 | 
						|
					localModule.flagUsed();
 | 
						|
					dep = new LocalModuleDependency(localModule, undefined, false);
 | 
						|
					dep.loc = /** @type {DependencyLocation} */ (expr.loc);
 | 
						|
					parser.state.module.addPresentationalDependency(dep);
 | 
						|
				} else {
 | 
						|
					dep = this.newRequireItemDependency(request);
 | 
						|
					dep.loc = /** @type {DependencyLocation} */ (expr.loc);
 | 
						|
					dep.optional = Boolean(parser.scope.inTry);
 | 
						|
					parser.state.current.addDependency(dep);
 | 
						|
				}
 | 
						|
				deps.push(dep);
 | 
						|
			}
 | 
						|
			const dep = this.newRequireArrayDependency(
 | 
						|
				deps,
 | 
						|
				/** @type {Range} */ (param.range)
 | 
						|
			);
 | 
						|
			dep.loc = /** @type {DependencyLocation} */ (expr.loc);
 | 
						|
			dep.optional = Boolean(parser.scope.inTry);
 | 
						|
			parser.state.module.addPresentationalDependency(dep);
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {JavascriptParser} parser the parser
 | 
						|
	 * @param {CallExpression} expr call expression
 | 
						|
	 * @param {BasicEvaluatedExpression} param param
 | 
						|
	 * @param {string=} namedModule named module
 | 
						|
	 * @returns {boolean | undefined} result
 | 
						|
	 */
 | 
						|
	processItem(parser, expr, param, namedModule) {
 | 
						|
		if (param.isConditional()) {
 | 
						|
			const options = /** @type {BasicEvaluatedExpression[]} */ (param.options);
 | 
						|
			for (const item of options) {
 | 
						|
				const result = this.processItem(parser, expr, item);
 | 
						|
				if (result === undefined) {
 | 
						|
					this.processContext(parser, expr, item);
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			return true;
 | 
						|
		} else if (param.isString()) {
 | 
						|
			let dep;
 | 
						|
			let localModule;
 | 
						|
 | 
						|
			if (param.string === "require") {
 | 
						|
				dep = new ConstDependency(
 | 
						|
					RuntimeGlobals.require,
 | 
						|
					/** @type {Range} */ (param.range),
 | 
						|
					[RuntimeGlobals.require]
 | 
						|
				);
 | 
						|
			} else if (param.string === "exports") {
 | 
						|
				dep = new ConstDependency(
 | 
						|
					"exports",
 | 
						|
					/** @type {Range} */ (param.range),
 | 
						|
					[RuntimeGlobals.exports]
 | 
						|
				);
 | 
						|
			} else if (param.string === "module") {
 | 
						|
				dep = new ConstDependency(
 | 
						|
					"module",
 | 
						|
					/** @type {Range} */ (param.range),
 | 
						|
					[RuntimeGlobals.module]
 | 
						|
				);
 | 
						|
			} else if (
 | 
						|
				(localModule = getLocalModule(
 | 
						|
					parser.state,
 | 
						|
					/** @type {string} */ (param.string),
 | 
						|
					namedModule
 | 
						|
				))
 | 
						|
			) {
 | 
						|
				localModule.flagUsed();
 | 
						|
				dep = new LocalModuleDependency(localModule, param.range, false);
 | 
						|
			} else {
 | 
						|
				dep = this.newRequireItemDependency(
 | 
						|
					/** @type {string} */ (param.string),
 | 
						|
					param.range
 | 
						|
				);
 | 
						|
				dep.optional = Boolean(parser.scope.inTry);
 | 
						|
				parser.state.current.addDependency(dep);
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
			dep.loc = /** @type {DependencyLocation} */ (expr.loc);
 | 
						|
			parser.state.module.addPresentationalDependency(dep);
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {JavascriptParser} parser the parser
 | 
						|
	 * @param {CallExpression} expr call expression
 | 
						|
	 * @param {BasicEvaluatedExpression} param param
 | 
						|
	 * @returns {boolean | undefined} result
 | 
						|
	 */
 | 
						|
	processContext(parser, expr, param) {
 | 
						|
		const dep = ContextDependencyHelpers.create(
 | 
						|
			AMDRequireContextDependency,
 | 
						|
			/** @type {Range} */ (param.range),
 | 
						|
			param,
 | 
						|
			expr,
 | 
						|
			this.options,
 | 
						|
			{
 | 
						|
				category: "amd"
 | 
						|
			},
 | 
						|
			parser
 | 
						|
		);
 | 
						|
		if (!dep) return;
 | 
						|
		dep.loc = /** @type {DependencyLocation} */ (expr.loc);
 | 
						|
		dep.optional = Boolean(parser.scope.inTry);
 | 
						|
		parser.state.current.addDependency(dep);
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {JavascriptParser} parser the parser
 | 
						|
	 * @param {CallExpression} expr call expression
 | 
						|
	 * @returns {boolean | undefined} result
 | 
						|
	 */
 | 
						|
	processCallDefine(parser, expr) {
 | 
						|
		/** @type {Expression | SpreadElement | undefined} */
 | 
						|
		let array;
 | 
						|
		/** @type {FunctionExpression | ArrowFunctionExpression | CallExpression | Identifier | undefined} */
 | 
						|
		let fn;
 | 
						|
		/** @type {ObjectExpression | Identifier | undefined} */
 | 
						|
		let obj;
 | 
						|
		/** @type {string | undefined} */
 | 
						|
		let namedModule;
 | 
						|
		switch (expr.arguments.length) {
 | 
						|
			case 1:
 | 
						|
				if (isCallable(expr.arguments[0])) {
 | 
						|
					// define(f() {…})
 | 
						|
					fn = expr.arguments[0];
 | 
						|
				} else if (expr.arguments[0].type === "ObjectExpression") {
 | 
						|
					// define({…})
 | 
						|
					obj = expr.arguments[0];
 | 
						|
				} else {
 | 
						|
					// define(expr)
 | 
						|
					// unclear if function or object
 | 
						|
					obj = fn = /** @type {Identifier} */ (expr.arguments[0]);
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			case 2:
 | 
						|
				if (expr.arguments[0].type === "Literal") {
 | 
						|
					namedModule = /** @type {string} */ (expr.arguments[0].value);
 | 
						|
					// define("…", …)
 | 
						|
					if (isCallable(expr.arguments[1])) {
 | 
						|
						// define("…", f() {…})
 | 
						|
						fn = expr.arguments[1];
 | 
						|
					} else if (expr.arguments[1].type === "ObjectExpression") {
 | 
						|
						// define("…", {…})
 | 
						|
						obj = expr.arguments[1];
 | 
						|
					} else {
 | 
						|
						// define("…", expr)
 | 
						|
						// unclear if function or object
 | 
						|
						obj = fn = /** @type {Identifier} */ (expr.arguments[1]);
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					array = expr.arguments[0];
 | 
						|
					if (isCallable(expr.arguments[1])) {
 | 
						|
						// define([…], f() {})
 | 
						|
						fn = expr.arguments[1];
 | 
						|
					} else if (expr.arguments[1].type === "ObjectExpression") {
 | 
						|
						// define([…], {…})
 | 
						|
						obj = expr.arguments[1];
 | 
						|
					} else {
 | 
						|
						// define([…], expr)
 | 
						|
						// unclear if function or object
 | 
						|
						obj = fn = /** @type {Identifier} */ (expr.arguments[1]);
 | 
						|
					}
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			case 3:
 | 
						|
				// define("…", […], f() {…})
 | 
						|
				namedModule =
 | 
						|
					/** @type {string} */
 | 
						|
					(
 | 
						|
						/** @type {Literal} */
 | 
						|
						(expr.arguments[0]).value
 | 
						|
					);
 | 
						|
				array = expr.arguments[1];
 | 
						|
				if (isCallable(expr.arguments[2])) {
 | 
						|
					// define("…", […], f() {})
 | 
						|
					fn = expr.arguments[2];
 | 
						|
				} else if (expr.arguments[2].type === "ObjectExpression") {
 | 
						|
					// define("…", […], {…})
 | 
						|
					obj = expr.arguments[2];
 | 
						|
				} else {
 | 
						|
					// define("…", […], expr)
 | 
						|
					// unclear if function or object
 | 
						|
					obj = fn = /** @type {Identifier} */ (expr.arguments[2]);
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			default:
 | 
						|
				return;
 | 
						|
		}
 | 
						|
		DynamicExports.bailout(parser.state);
 | 
						|
		/** @type {Identifier[] | null} */
 | 
						|
		let fnParams = null;
 | 
						|
		let fnParamsOffset = 0;
 | 
						|
		if (fn) {
 | 
						|
			if (isUnboundFunctionExpression(fn)) {
 | 
						|
				fnParams =
 | 
						|
					/** @type {Identifier[]} */
 | 
						|
					(fn.params);
 | 
						|
			} else if (isBoundFunctionExpression(fn)) {
 | 
						|
				const object =
 | 
						|
					/** @type {FunctionExpression} */
 | 
						|
					(/** @type {MemberExpression} */ (fn.callee).object);
 | 
						|
 | 
						|
				fnParams =
 | 
						|
					/** @type {Identifier[]} */
 | 
						|
					(object.params);
 | 
						|
				fnParamsOffset = fn.arguments.length - 1;
 | 
						|
				if (fnParamsOffset < 0) {
 | 
						|
					fnParamsOffset = 0;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		const fnRenames = new Map();
 | 
						|
		if (array) {
 | 
						|
			/** @type {Record<number, string>} */
 | 
						|
			const identifiers = {};
 | 
						|
			const param = parser.evaluateExpression(array);
 | 
						|
			const result = this.processArray(
 | 
						|
				parser,
 | 
						|
				expr,
 | 
						|
				param,
 | 
						|
				identifiers,
 | 
						|
				namedModule
 | 
						|
			);
 | 
						|
			if (!result) return;
 | 
						|
			if (fnParams) {
 | 
						|
				fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => {
 | 
						|
					if (identifiers[idx]) {
 | 
						|
						fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx]));
 | 
						|
						return false;
 | 
						|
					}
 | 
						|
					return true;
 | 
						|
				});
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			const identifiers = ["require", "exports", "module"];
 | 
						|
			if (fnParams) {
 | 
						|
				fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => {
 | 
						|
					if (identifiers[idx]) {
 | 
						|
						fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx]));
 | 
						|
						return false;
 | 
						|
					}
 | 
						|
					return true;
 | 
						|
				});
 | 
						|
			}
 | 
						|
		}
 | 
						|
		/** @type {boolean | undefined} */
 | 
						|
		let inTry;
 | 
						|
		if (fn && isUnboundFunctionExpression(fn)) {
 | 
						|
			inTry = parser.scope.inTry;
 | 
						|
			parser.inScope(/** @type {Identifier[]} */ (fnParams), () => {
 | 
						|
				for (const [name, varInfo] of fnRenames) {
 | 
						|
					parser.setVariable(name, varInfo);
 | 
						|
				}
 | 
						|
				parser.scope.inTry = /** @type {boolean} */ (inTry);
 | 
						|
				if (fn.body.type === "BlockStatement") {
 | 
						|
					parser.detectMode(fn.body.body);
 | 
						|
					const prev = parser.prevStatement;
 | 
						|
					parser.preWalkStatement(fn.body);
 | 
						|
					parser.prevStatement = prev;
 | 
						|
					parser.walkStatement(fn.body);
 | 
						|
				} else {
 | 
						|
					parser.walkExpression(fn.body);
 | 
						|
				}
 | 
						|
			});
 | 
						|
		} else if (fn && isBoundFunctionExpression(fn)) {
 | 
						|
			inTry = parser.scope.inTry;
 | 
						|
 | 
						|
			const object =
 | 
						|
				/** @type {FunctionExpression} */
 | 
						|
				(/** @type {MemberExpression} */ (fn.callee).object);
 | 
						|
 | 
						|
			parser.inScope(
 | 
						|
				/** @type {Identifier[]} */
 | 
						|
				(object.params).filter(
 | 
						|
					(i) => !["require", "module", "exports"].includes(i.name)
 | 
						|
				),
 | 
						|
				() => {
 | 
						|
					for (const [name, varInfo] of fnRenames) {
 | 
						|
						parser.setVariable(name, varInfo);
 | 
						|
					}
 | 
						|
					parser.scope.inTry = /** @type {boolean} */ (inTry);
 | 
						|
 | 
						|
					if (object.body.type === "BlockStatement") {
 | 
						|
						parser.detectMode(object.body.body);
 | 
						|
						const prev = parser.prevStatement;
 | 
						|
						parser.preWalkStatement(object.body);
 | 
						|
						parser.prevStatement = prev;
 | 
						|
						parser.walkStatement(object.body);
 | 
						|
					} else {
 | 
						|
						parser.walkExpression(
 | 
						|
							/** @type {TODO} */
 | 
						|
							(object.body)
 | 
						|
						);
 | 
						|
					}
 | 
						|
				}
 | 
						|
			);
 | 
						|
			if (fn.arguments) {
 | 
						|
				parser.walkExpressions(fn.arguments);
 | 
						|
			}
 | 
						|
		} else if (fn || obj) {
 | 
						|
			parser.walkExpression(
 | 
						|
				/** @type {FunctionExpression | ArrowFunctionExpression | CallExpression | ObjectExpression | Identifier} */
 | 
						|
				(fn || obj)
 | 
						|
			);
 | 
						|
		}
 | 
						|
 | 
						|
		const dep = this.newDefineDependency(
 | 
						|
			/** @type {Range} */ (expr.range),
 | 
						|
			array ? /** @type {Range} */ (array.range) : null,
 | 
						|
			fn ? /** @type {Range} */ (fn.range) : null,
 | 
						|
			obj ? /** @type {Range} */ (obj.range) : null,
 | 
						|
			namedModule || null
 | 
						|
		);
 | 
						|
		dep.loc = /** @type {DependencyLocation} */ (expr.loc);
 | 
						|
		if (namedModule) {
 | 
						|
			dep.localModule = addLocalModule(parser.state, namedModule);
 | 
						|
		}
 | 
						|
		parser.state.module.addPresentationalDependency(dep);
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {Range} range range
 | 
						|
	 * @param {Range | null} arrayRange array range
 | 
						|
	 * @param {Range | null} functionRange function range
 | 
						|
	 * @param {Range | null} objectRange object range
 | 
						|
	 * @param {string | null} namedModule true, when define is called with a name
 | 
						|
	 * @returns {AMDDefineDependency} AMDDefineDependency
 | 
						|
	 */
 | 
						|
	newDefineDependency(
 | 
						|
		range,
 | 
						|
		arrayRange,
 | 
						|
		functionRange,
 | 
						|
		objectRange,
 | 
						|
		namedModule
 | 
						|
	) {
 | 
						|
		return new AMDDefineDependency(
 | 
						|
			range,
 | 
						|
			arrayRange,
 | 
						|
			functionRange,
 | 
						|
			objectRange,
 | 
						|
			namedModule
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {(string | LocalModuleDependency | AMDRequireItemDependency)[]} depsArray deps array
 | 
						|
	 * @param {Range} range range
 | 
						|
	 * @returns {AMDRequireArrayDependency} AMDRequireArrayDependency
 | 
						|
	 */
 | 
						|
	newRequireArrayDependency(depsArray, range) {
 | 
						|
		return new AMDRequireArrayDependency(depsArray, range);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {string} request request
 | 
						|
	 * @param {Range=} range range
 | 
						|
	 * @returns {AMDRequireItemDependency} AMDRequireItemDependency
 | 
						|
	 */
 | 
						|
	newRequireItemDependency(request, range) {
 | 
						|
		return new AMDRequireItemDependency(request, range);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
module.exports = AMDDefineDependencyParserPlugin;
 |