AMDDefineDependencyParserPlugin.js 15 KB

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