AMDDefineDependencyParserPlugin.js 15 KB

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