AMDDefineDependencyParserPlugin.js 16 KB

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