AMDDefineDependencyParserPlugin.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const RuntimeGlobals = require("../RuntimeGlobals");
  7. const AMDDefineDependency = require("./AMDDefineDependency");
  8. const AMDRequireArrayDependency = require("./AMDRequireArrayDependency");
  9. const AMDRequireContextDependency = require("./AMDRequireContextDependency");
  10. const AMDRequireItemDependency = require("./AMDRequireItemDependency");
  11. const ConstDependency = require("./ConstDependency");
  12. const ContextDependencyHelpers = require("./ContextDependencyHelpers");
  13. const DynamicExports = require("./DynamicExports");
  14. const LocalModuleDependency = require("./LocalModuleDependency");
  15. const { addLocalModule, getLocalModule } = require("./LocalModulesHelpers");
  16. /** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */
  17. /** @typedef {import("estree").CallExpression} CallExpression */
  18. /** @typedef {import("estree").Expression} Expression */
  19. /** @typedef {import("estree").FunctionExpression} FunctionExpression */
  20. /** @typedef {import("estree").Literal} Literal */
  21. /** @typedef {import("estree").SpreadElement} SpreadElement */
  22. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  23. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  24. /** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
  25. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  26. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  27. /**
  28. * @param {Expression | SpreadElement} expr expression
  29. * @returns {boolean} true if it's a bound function expression
  30. */
  31. const isBoundFunctionExpression = expr => {
  32. if (expr.type !== "CallExpression") return false;
  33. if (expr.callee.type !== "MemberExpression") return false;
  34. if (expr.callee.computed) return false;
  35. if (expr.callee.object.type !== "FunctionExpression") return false;
  36. if (expr.callee.property.type !== "Identifier") return false;
  37. if (expr.callee.property.name !== "bind") return false;
  38. return true;
  39. };
  40. /** @typedef {FunctionExpression | ArrowFunctionExpression} UnboundFunctionExpression */
  41. /**
  42. * @param {Expression | SpreadElement} expr expression
  43. * @returns {boolean} true when unbound function expression
  44. */
  45. const isUnboundFunctionExpression = expr => {
  46. if (expr.type === "FunctionExpression") return true;
  47. if (expr.type === "ArrowFunctionExpression") return true;
  48. return false;
  49. };
  50. /**
  51. * @param {Expression | SpreadElement} expr expression
  52. * @returns {boolean} true when callable
  53. */
  54. const isCallable = expr => {
  55. if (isUnboundFunctionExpression(expr)) return true;
  56. if (isBoundFunctionExpression(expr)) return true;
  57. return false;
  58. };
  59. class AMDDefineDependencyParserPlugin {
  60. /**
  61. * @param {JavascriptParserOptions} options parserOptions
  62. */
  63. constructor(options) {
  64. this.options = options;
  65. }
  66. /**
  67. * @param {JavascriptParser} parser the parser
  68. * @returns {void}
  69. */
  70. apply(parser) {
  71. parser.hooks.call
  72. .for("define")
  73. .tap(
  74. "AMDDefineDependencyParserPlugin",
  75. this.processCallDefine.bind(this, parser)
  76. );
  77. }
  78. /**
  79. * @param {JavascriptParser} parser the parser
  80. * @param {CallExpression} expr call expression
  81. * @param {BasicEvaluatedExpression} param param
  82. * @param {Record<number, string>} identifiers identifiers
  83. * @param {string=} namedModule named module
  84. * @returns {boolean | undefined} result
  85. */
  86. processArray(parser, expr, param, identifiers, namedModule) {
  87. if (param.isArray()) {
  88. /** @type {BasicEvaluatedExpression[]} */
  89. (param.items).forEach((param, idx) => {
  90. if (
  91. param.isString() &&
  92. ["require", "module", "exports"].includes(
  93. /** @type {string} */ (param.string)
  94. )
  95. )
  96. identifiers[/** @type {number} */ (idx)] = param.string;
  97. const result = this.processItem(parser, expr, param, namedModule);
  98. if (result === undefined) {
  99. this.processContext(parser, expr, param);
  100. }
  101. });
  102. return true;
  103. } else if (param.isConstArray()) {
  104. /** @type {(string | LocalModuleDependency | AMDRequireItemDependency)[]} */
  105. const deps = [];
  106. /** @type {string[]} */
  107. (param.array).forEach((request, idx) => {
  108. let dep;
  109. let localModule;
  110. if (request === "require") {
  111. identifiers[idx] = request;
  112. dep = RuntimeGlobals.require;
  113. } else if (["exports", "module"].includes(request)) {
  114. identifiers[idx] = request;
  115. dep = request;
  116. } else if ((localModule = getLocalModule(parser.state, request))) {
  117. localModule.flagUsed();
  118. dep = new LocalModuleDependency(localModule, undefined, false);
  119. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  120. parser.state.module.addPresentationalDependency(dep);
  121. } else {
  122. dep = this.newRequireItemDependency(request);
  123. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  124. dep.optional = !!parser.scope.inTry;
  125. parser.state.current.addDependency(dep);
  126. }
  127. deps.push(dep);
  128. });
  129. const dep = this.newRequireArrayDependency(
  130. deps,
  131. /** @type {Range} */ (param.range)
  132. );
  133. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  134. dep.optional = !!parser.scope.inTry;
  135. parser.state.module.addPresentationalDependency(dep);
  136. return true;
  137. }
  138. }
  139. /**
  140. * @param {JavascriptParser} parser the parser
  141. * @param {CallExpression} expr call expression
  142. * @param {BasicEvaluatedExpression} param param
  143. * @param {string=} namedModule named module
  144. * @returns {boolean | undefined} result
  145. */
  146. processItem(parser, expr, param, namedModule) {
  147. if (param.isConditional()) {
  148. /** @type {BasicEvaluatedExpression[]} */
  149. (param.options).forEach(param => {
  150. const result = this.processItem(parser, expr, param);
  151. if (result === undefined) {
  152. this.processContext(parser, expr, param);
  153. }
  154. });
  155. return true;
  156. } else if (param.isString()) {
  157. let dep, localModule;
  158. if (param.string === "require") {
  159. dep = new ConstDependency(
  160. RuntimeGlobals.require,
  161. /** @type {Range} */ (param.range),
  162. [RuntimeGlobals.require]
  163. );
  164. } else if (param.string === "exports") {
  165. dep = new ConstDependency(
  166. "exports",
  167. /** @type {Range} */ (param.range),
  168. [RuntimeGlobals.exports]
  169. );
  170. } else if (param.string === "module") {
  171. dep = new ConstDependency(
  172. "module",
  173. /** @type {Range} */ (param.range),
  174. [RuntimeGlobals.module]
  175. );
  176. } else if (
  177. (localModule = getLocalModule(
  178. parser.state,
  179. /** @type {string} */ (param.string),
  180. namedModule
  181. ))
  182. ) {
  183. localModule.flagUsed();
  184. dep = new LocalModuleDependency(localModule, param.range, false);
  185. } else {
  186. dep = this.newRequireItemDependency(
  187. /** @type {string} */ (param.string),
  188. param.range
  189. );
  190. dep.optional = !!parser.scope.inTry;
  191. parser.state.current.addDependency(dep);
  192. return true;
  193. }
  194. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  195. parser.state.module.addPresentationalDependency(dep);
  196. return true;
  197. }
  198. }
  199. /**
  200. * @param {JavascriptParser} parser the parser
  201. * @param {CallExpression} expr call expression
  202. * @param {BasicEvaluatedExpression} param param
  203. * @returns {boolean | undefined} result
  204. */
  205. processContext(parser, expr, param) {
  206. const dep = ContextDependencyHelpers.create(
  207. AMDRequireContextDependency,
  208. /** @type {Range} */ (param.range),
  209. param,
  210. expr,
  211. this.options,
  212. {
  213. category: "amd"
  214. },
  215. parser
  216. );
  217. if (!dep) return;
  218. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  219. dep.optional = !!parser.scope.inTry;
  220. parser.state.current.addDependency(dep);
  221. return true;
  222. }
  223. /**
  224. * @param {JavascriptParser} parser the parser
  225. * @param {CallExpression} expr call expression
  226. * @returns {boolean | undefined} result
  227. */
  228. processCallDefine(parser, expr) {
  229. let array, fn, obj, namedModule;
  230. switch (expr.arguments.length) {
  231. case 1:
  232. if (isCallable(expr.arguments[0])) {
  233. // define(f() {…})
  234. fn = expr.arguments[0];
  235. } else if (expr.arguments[0].type === "ObjectExpression") {
  236. // define({…})
  237. obj = expr.arguments[0];
  238. } else {
  239. // define(expr)
  240. // unclear if function or object
  241. obj = fn = expr.arguments[0];
  242. }
  243. break;
  244. case 2:
  245. if (expr.arguments[0].type === "Literal") {
  246. namedModule = expr.arguments[0].value;
  247. // define("…", …)
  248. if (isCallable(expr.arguments[1])) {
  249. // define("…", f() {…})
  250. fn = expr.arguments[1];
  251. } else if (expr.arguments[1].type === "ObjectExpression") {
  252. // define("…", {…})
  253. obj = expr.arguments[1];
  254. } else {
  255. // define("…", expr)
  256. // unclear if function or object
  257. obj = fn = expr.arguments[1];
  258. }
  259. } else {
  260. array = expr.arguments[0];
  261. if (isCallable(expr.arguments[1])) {
  262. // define([…], f() {})
  263. fn = expr.arguments[1];
  264. } else if (expr.arguments[1].type === "ObjectExpression") {
  265. // define([…], {…})
  266. obj = expr.arguments[1];
  267. } else {
  268. // define([…], expr)
  269. // unclear if function or object
  270. obj = fn = expr.arguments[1];
  271. }
  272. }
  273. break;
  274. case 3:
  275. // define("…", […], f() {…})
  276. namedModule = /** @type {TODO} */ (expr).arguments[0].value;
  277. array = expr.arguments[1];
  278. if (isCallable(expr.arguments[2])) {
  279. // define("…", […], f() {})
  280. fn = expr.arguments[2];
  281. } else if (expr.arguments[2].type === "ObjectExpression") {
  282. // define("…", […], {…})
  283. obj = expr.arguments[2];
  284. } else {
  285. // define("…", […], expr)
  286. // unclear if function or object
  287. obj = fn = expr.arguments[2];
  288. }
  289. break;
  290. default:
  291. return;
  292. }
  293. DynamicExports.bailout(parser.state);
  294. let fnParams = null;
  295. let fnParamsOffset = 0;
  296. if (fn) {
  297. if (isUnboundFunctionExpression(fn)) {
  298. fnParams = /** @type {UnboundFunctionExpression} */ (fn).params;
  299. } else if (isBoundFunctionExpression(fn)) {
  300. fnParams = /** @type {TODO} */ (fn).callee.object.params;
  301. fnParamsOffset = /** @type {TODO} */ (fn).arguments.length - 1;
  302. if (fnParamsOffset < 0) {
  303. fnParamsOffset = 0;
  304. }
  305. }
  306. }
  307. let fnRenames = new Map();
  308. if (array) {
  309. /** @type {Record<number, string>} */
  310. const identifiers = {};
  311. const param = parser.evaluateExpression(array);
  312. const result = this.processArray(
  313. parser,
  314. expr,
  315. param,
  316. identifiers,
  317. namedModule
  318. );
  319. if (!result) return;
  320. if (fnParams) {
  321. fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => {
  322. if (identifiers[idx]) {
  323. fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx]));
  324. return false;
  325. }
  326. return true;
  327. });
  328. }
  329. } else {
  330. const identifiers = ["require", "exports", "module"];
  331. if (fnParams) {
  332. fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => {
  333. if (identifiers[idx]) {
  334. fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx]));
  335. return false;
  336. }
  337. return true;
  338. });
  339. }
  340. }
  341. /** @type {boolean | undefined} */
  342. let inTry;
  343. if (fn && isUnboundFunctionExpression(fn)) {
  344. inTry = parser.scope.inTry;
  345. parser.inScope(fnParams, () => {
  346. for (const [name, varInfo] of fnRenames) {
  347. parser.setVariable(name, varInfo);
  348. }
  349. parser.scope.inTry = /** @type {boolean} */ (inTry);
  350. if (fn.body.type === "BlockStatement") {
  351. parser.detectMode(fn.body.body);
  352. const prev = parser.prevStatement;
  353. parser.preWalkStatement(fn.body);
  354. parser.prevStatement = prev;
  355. parser.walkStatement(fn.body);
  356. } else {
  357. parser.walkExpression(fn.body);
  358. }
  359. });
  360. } else if (fn && isBoundFunctionExpression(fn)) {
  361. inTry = parser.scope.inTry;
  362. parser.inScope(
  363. /** @type {TODO} */
  364. (fn).callee.object.params.filter(
  365. i => !["require", "module", "exports"].includes(i.name)
  366. ),
  367. () => {
  368. for (const [name, varInfo] of fnRenames) {
  369. parser.setVariable(name, varInfo);
  370. }
  371. parser.scope.inTry = /** @type {boolean} */ (inTry);
  372. if (fn.callee.object.body.type === "BlockStatement") {
  373. parser.detectMode(fn.callee.object.body.body);
  374. const prev = parser.prevStatement;
  375. parser.preWalkStatement(fn.callee.object.body);
  376. parser.prevStatement = prev;
  377. parser.walkStatement(fn.callee.object.body);
  378. } else {
  379. parser.walkExpression(fn.callee.object.body);
  380. }
  381. }
  382. );
  383. if (/** @type {TODO} */ (fn).arguments) {
  384. parser.walkExpressions(/** @type {TODO} */ (fn).arguments);
  385. }
  386. } else if (fn || obj) {
  387. parser.walkExpression(fn || obj);
  388. }
  389. const dep = this.newDefineDependency(
  390. /** @type {Range} */ (expr.range),
  391. array ? /** @type {Range} */ (array.range) : null,
  392. fn ? /** @type {Range} */ (fn.range) : null,
  393. obj ? /** @type {Range} */ (obj.range) : null,
  394. namedModule ? namedModule : null
  395. );
  396. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  397. if (namedModule) {
  398. dep.localModule = addLocalModule(parser.state, namedModule);
  399. }
  400. parser.state.module.addPresentationalDependency(dep);
  401. return true;
  402. }
  403. /**
  404. * @param {Range} range range
  405. * @param {Range | null} arrayRange array range
  406. * @param {Range | null} functionRange function range
  407. * @param {Range | null} objectRange object range
  408. * @param {boolean | null} namedModule true, when define is called with a name
  409. * @returns {AMDDefineDependency} AMDDefineDependency
  410. */
  411. newDefineDependency(
  412. range,
  413. arrayRange,
  414. functionRange,
  415. objectRange,
  416. namedModule
  417. ) {
  418. return new AMDDefineDependency(
  419. range,
  420. arrayRange,
  421. functionRange,
  422. objectRange,
  423. namedModule
  424. );
  425. }
  426. /**
  427. * @param {TODO[]} depsArray deps array
  428. * @param {Range} range range
  429. * @returns {AMDRequireArrayDependency} AMDRequireArrayDependency
  430. */
  431. newRequireArrayDependency(depsArray, range) {
  432. return new AMDRequireArrayDependency(depsArray, range);
  433. }
  434. /**
  435. * @param {string} request request
  436. * @param {Range=} range range
  437. * @returns {AMDRequireItemDependency} AMDRequireItemDependency
  438. */
  439. newRequireItemDependency(request, range) {
  440. return new AMDRequireItemDependency(request, range);
  441. }
  442. }
  443. module.exports = AMDDefineDependencyParserPlugin;