AMDDefineDependencyParserPlugin.js 16 KB

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