You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2441 lines
69 KiB

4 years ago
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. // Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
  7. const acorn = require("acorn");
  8. const { Tapable, SyncBailHook, HookMap } = require("tapable");
  9. const util = require("util");
  10. const vm = require("vm");
  11. const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
  12. const StackedSetMap = require("./util/StackedSetMap");
  13. const acornParser = acorn.Parser;
  14. const joinRanges = (startRange, endRange) => {
  15. if (!endRange) return startRange;
  16. if (!startRange) return endRange;
  17. return [startRange[0], endRange[1]];
  18. };
  19. const defaultParserOptions = {
  20. ranges: true,
  21. locations: true,
  22. ecmaVersion: 11,
  23. sourceType: "module",
  24. onComment: null
  25. };
  26. // regexp to match at least one "magic comment"
  27. const webpackCommentRegExp = new RegExp(/(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/);
  28. const EMPTY_COMMENT_OPTIONS = {
  29. options: null,
  30. errors: null
  31. };
  32. class Parser extends Tapable {
  33. constructor(options, sourceType = "auto") {
  34. super();
  35. this.hooks = {
  36. evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])),
  37. evaluate: new HookMap(() => new SyncBailHook(["expression"])),
  38. evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])),
  39. evaluateDefinedIdentifier: new HookMap(
  40. () => new SyncBailHook(["expression"])
  41. ),
  42. evaluateCallExpressionMember: new HookMap(
  43. () => new SyncBailHook(["expression", "param"])
  44. ),
  45. statement: new SyncBailHook(["statement"]),
  46. statementIf: new SyncBailHook(["statement"]),
  47. label: new HookMap(() => new SyncBailHook(["statement"])),
  48. import: new SyncBailHook(["statement", "source"]),
  49. importSpecifier: new SyncBailHook([
  50. "statement",
  51. "source",
  52. "exportName",
  53. "identifierName"
  54. ]),
  55. export: new SyncBailHook(["statement"]),
  56. exportImport: new SyncBailHook(["statement", "source"]),
  57. exportDeclaration: new SyncBailHook(["statement", "declaration"]),
  58. exportExpression: new SyncBailHook(["statement", "declaration"]),
  59. exportSpecifier: new SyncBailHook([
  60. "statement",
  61. "identifierName",
  62. "exportName",
  63. "index"
  64. ]),
  65. exportImportSpecifier: new SyncBailHook([
  66. "statement",
  67. "source",
  68. "identifierName",
  69. "exportName",
  70. "index"
  71. ]),
  72. varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])),
  73. varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])),
  74. varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])),
  75. varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])),
  76. canRename: new HookMap(() => new SyncBailHook(["initExpression"])),
  77. rename: new HookMap(() => new SyncBailHook(["initExpression"])),
  78. assigned: new HookMap(() => new SyncBailHook(["expression"])),
  79. assign: new HookMap(() => new SyncBailHook(["expression"])),
  80. typeof: new HookMap(() => new SyncBailHook(["expression"])),
  81. importCall: new SyncBailHook(["expression"]),
  82. call: new HookMap(() => new SyncBailHook(["expression"])),
  83. callAnyMember: new HookMap(() => new SyncBailHook(["expression"])),
  84. new: new HookMap(() => new SyncBailHook(["expression"])),
  85. expression: new HookMap(() => new SyncBailHook(["expression"])),
  86. expressionAnyMember: new HookMap(() => new SyncBailHook(["expression"])),
  87. expressionConditionalOperator: new SyncBailHook(["expression"]),
  88. expressionLogicalOperator: new SyncBailHook(["expression"]),
  89. program: new SyncBailHook(["ast", "comments"])
  90. };
  91. const HOOK_MAP_COMPAT_CONFIG = {
  92. evaluateTypeof: /^evaluate typeof (.+)$/,
  93. evaluateIdentifier: /^evaluate Identifier (.+)$/,
  94. evaluateDefinedIdentifier: /^evaluate defined Identifier (.+)$/,
  95. evaluateCallExpressionMember: /^evaluate CallExpression .(.+)$/,
  96. evaluate: /^evaluate (.+)$/,
  97. label: /^label (.+)$/,
  98. varDeclarationLet: /^var-let (.+)$/,
  99. varDeclarationConst: /^var-const (.+)$/,
  100. varDeclarationVar: /^var-var (.+)$/,
  101. varDeclaration: /^var (.+)$/,
  102. canRename: /^can-rename (.+)$/,
  103. rename: /^rename (.+)$/,
  104. typeof: /^typeof (.+)$/,
  105. assigned: /^assigned (.+)$/,
  106. assign: /^assign (.+)$/,
  107. callAnyMember: /^call (.+)\.\*$/,
  108. call: /^call (.+)$/,
  109. new: /^new (.+)$/,
  110. expressionConditionalOperator: /^expression \?:$/,
  111. expressionAnyMember: /^expression (.+)\.\*$/,
  112. expression: /^expression (.+)$/
  113. };
  114. this._pluginCompat.tap("Parser", options => {
  115. for (const name of Object.keys(HOOK_MAP_COMPAT_CONFIG)) {
  116. const regexp = HOOK_MAP_COMPAT_CONFIG[name];
  117. const match = regexp.exec(options.name);
  118. if (match) {
  119. if (match[1]) {
  120. this.hooks[name].tap(
  121. match[1],
  122. options.fn.name || "unnamed compat plugin",
  123. options.fn.bind(this)
  124. );
  125. } else {
  126. this.hooks[name].tap(
  127. options.fn.name || "unnamed compat plugin",
  128. options.fn.bind(this)
  129. );
  130. }
  131. return true;
  132. }
  133. }
  134. });
  135. this.options = options;
  136. this.sourceType = sourceType;
  137. this.scope = undefined;
  138. this.state = undefined;
  139. this.comments = undefined;
  140. this.initializeEvaluating();
  141. }
  142. initializeEvaluating() {
  143. this.hooks.evaluate.for("Literal").tap("Parser", expr => {
  144. switch (typeof expr.value) {
  145. case "number":
  146. return new BasicEvaluatedExpression()
  147. .setNumber(expr.value)
  148. .setRange(expr.range);
  149. case "string":
  150. return new BasicEvaluatedExpression()
  151. .setString(expr.value)
  152. .setRange(expr.range);
  153. case "boolean":
  154. return new BasicEvaluatedExpression()
  155. .setBoolean(expr.value)
  156. .setRange(expr.range);
  157. }
  158. if (expr.value === null) {
  159. return new BasicEvaluatedExpression().setNull().setRange(expr.range);
  160. }
  161. if (expr.value instanceof RegExp) {
  162. return new BasicEvaluatedExpression()
  163. .setRegExp(expr.value)
  164. .setRange(expr.range);
  165. }
  166. });
  167. this.hooks.evaluate.for("LogicalExpression").tap("Parser", expr => {
  168. let left;
  169. let leftAsBool;
  170. let right;
  171. if (expr.operator === "&&") {
  172. left = this.evaluateExpression(expr.left);
  173. leftAsBool = left && left.asBool();
  174. if (leftAsBool === false) return left.setRange(expr.range);
  175. if (leftAsBool !== true) return;
  176. right = this.evaluateExpression(expr.right);
  177. return right.setRange(expr.range);
  178. } else if (expr.operator === "||") {
  179. left = this.evaluateExpression(expr.left);
  180. leftAsBool = left && left.asBool();
  181. if (leftAsBool === true) return left.setRange(expr.range);
  182. if (leftAsBool !== false) return;
  183. right = this.evaluateExpression(expr.right);
  184. return right.setRange(expr.range);
  185. }
  186. });
  187. this.hooks.evaluate.for("BinaryExpression").tap("Parser", expr => {
  188. let left;
  189. let right;
  190. let res;
  191. if (expr.operator === "+") {
  192. left = this.evaluateExpression(expr.left);
  193. right = this.evaluateExpression(expr.right);
  194. if (!left || !right) return;
  195. res = new BasicEvaluatedExpression();
  196. if (left.isString()) {
  197. if (right.isString()) {
  198. res.setString(left.string + right.string);
  199. } else if (right.isNumber()) {
  200. res.setString(left.string + right.number);
  201. } else if (
  202. right.isWrapped() &&
  203. right.prefix &&
  204. right.prefix.isString()
  205. ) {
  206. // "left" + ("prefix" + inner + "postfix")
  207. // => ("leftprefix" + inner + "postfix")
  208. res.setWrapped(
  209. new BasicEvaluatedExpression()
  210. .setString(left.string + right.prefix.string)
  211. .setRange(joinRanges(left.range, right.prefix.range)),
  212. right.postfix,
  213. right.wrappedInnerExpressions
  214. );
  215. } else if (right.isWrapped()) {
  216. // "left" + ([null] + inner + "postfix")
  217. // => ("left" + inner + "postfix")
  218. res.setWrapped(left, right.postfix, right.wrappedInnerExpressions);
  219. } else {
  220. // "left" + expr
  221. // => ("left" + expr + "")
  222. res.setWrapped(left, null, [right]);
  223. }
  224. } else if (left.isNumber()) {
  225. if (right.isString()) {
  226. res.setString(left.number + right.string);
  227. } else if (right.isNumber()) {
  228. res.setNumber(left.number + right.number);
  229. } else {
  230. return;
  231. }
  232. } else if (left.isWrapped()) {
  233. if (left.postfix && left.postfix.isString() && right.isString()) {
  234. // ("prefix" + inner + "postfix") + "right"
  235. // => ("prefix" + inner + "postfixright")
  236. res.setWrapped(
  237. left.prefix,
  238. new BasicEvaluatedExpression()
  239. .setString(left.postfix.string + right.string)
  240. .setRange(joinRanges(left.postfix.range, right.range)),
  241. left.wrappedInnerExpressions
  242. );
  243. } else if (
  244. left.postfix &&
  245. left.postfix.isString() &&
  246. right.isNumber()
  247. ) {
  248. // ("prefix" + inner + "postfix") + 123
  249. // => ("prefix" + inner + "postfix123")
  250. res.setWrapped(
  251. left.prefix,
  252. new BasicEvaluatedExpression()
  253. .setString(left.postfix.string + right.number)
  254. .setRange(joinRanges(left.postfix.range, right.range)),
  255. left.wrappedInnerExpressions
  256. );
  257. } else if (right.isString()) {
  258. // ("prefix" + inner + [null]) + "right"
  259. // => ("prefix" + inner + "right")
  260. res.setWrapped(left.prefix, right, left.wrappedInnerExpressions);
  261. } else if (right.isNumber()) {
  262. // ("prefix" + inner + [null]) + 123
  263. // => ("prefix" + inner + "123")
  264. res.setWrapped(
  265. left.prefix,
  266. new BasicEvaluatedExpression()
  267. .setString(right.number + "")
  268. .setRange(right.range),
  269. left.wrappedInnerExpressions
  270. );
  271. } else if (right.isWrapped()) {
  272. // ("prefix1" + inner1 + "postfix1") + ("prefix2" + inner2 + "postfix2")
  273. // ("prefix1" + inner1 + "postfix1" + "prefix2" + inner2 + "postfix2")
  274. res.setWrapped(
  275. left.prefix,
  276. right.postfix,
  277. left.wrappedInnerExpressions &&
  278. right.wrappedInnerExpressions &&
  279. left.wrappedInnerExpressions
  280. .concat(left.postfix ? [left.postfix] : [])
  281. .concat(right.prefix ? [right.prefix] : [])
  282. .concat(right.wrappedInnerExpressions)
  283. );
  284. } else {
  285. // ("prefix" + inner + postfix) + expr
  286. // => ("prefix" + inner + postfix + expr + [null])
  287. res.setWrapped(
  288. left.prefix,
  289. null,
  290. left.wrappedInnerExpressions &&
  291. left.wrappedInnerExpressions.concat(
  292. left.postfix ? [left.postfix, right] : [right]
  293. )
  294. );
  295. }
  296. } else {
  297. if (right.isString()) {
  298. // left + "right"
  299. // => ([null] + left + "right")
  300. res.setWrapped(null, right, [left]);
  301. } else if (right.isWrapped()) {
  302. // left + (prefix + inner + "postfix")
  303. // => ([null] + left + prefix + inner + "postfix")
  304. res.setWrapped(
  305. null,
  306. right.postfix,
  307. right.wrappedInnerExpressions &&
  308. (right.prefix ? [left, right.prefix] : [left]).concat(
  309. right.wrappedInnerExpressions
  310. )
  311. );
  312. } else {
  313. return;
  314. }
  315. }
  316. res.setRange(expr.range);
  317. return res;
  318. } else if (expr.operator === "-") {
  319. left = this.evaluateExpression(expr.left);
  320. right = this.evaluateExpression(expr.right);
  321. if (!left || !right) return;
  322. if (!left.isNumber() || !right.isNumber()) return;
  323. res = new BasicEvaluatedExpression();
  324. res.setNumber(left.number - right.number);
  325. res.setRange(expr.range);
  326. return res;
  327. } else if (expr.operator === "*") {
  328. left = this.evaluateExpression(expr.left);
  329. right = this.evaluateExpression(expr.right);
  330. if (!left || !right) return;
  331. if (!left.isNumber() || !right.isNumber()) return;
  332. res = new BasicEvaluatedExpression();
  333. res.setNumber(left.number * right.number);
  334. res.setRange(expr.range);
  335. return res;
  336. } else if (expr.operator === "/") {
  337. left = this.evaluateExpression(expr.left);
  338. right = this.evaluateExpression(expr.right);
  339. if (!left || !right) return;
  340. if (!left.isNumber() || !right.isNumber()) return;
  341. res = new BasicEvaluatedExpression();
  342. res.setNumber(left.number / right.number);
  343. res.setRange(expr.range);
  344. return res;
  345. } else if (expr.operator === "**") {
  346. left = this.evaluateExpression(expr.left);
  347. right = this.evaluateExpression(expr.right);
  348. if (!left || !right) return;
  349. if (!left.isNumber() || !right.isNumber()) return;
  350. res = new BasicEvaluatedExpression();
  351. res.setNumber(Math.pow(left.number, right.number));
  352. res.setRange(expr.range);
  353. return res;
  354. } else if (expr.operator === "==" || expr.operator === "===") {
  355. left = this.evaluateExpression(expr.left);
  356. right = this.evaluateExpression(expr.right);
  357. if (!left || !right) return;
  358. res = new BasicEvaluatedExpression();
  359. res.setRange(expr.range);
  360. if (left.isString() && right.isString()) {
  361. return res.setBoolean(left.string === right.string);
  362. } else if (left.isNumber() && right.isNumber()) {
  363. return res.setBoolean(left.number === right.number);
  364. } else if (left.isBoolean() && right.isBoolean()) {
  365. return res.setBoolean(left.bool === right.bool);
  366. }
  367. } else if (expr.operator === "!=" || expr.operator === "!==") {
  368. left = this.evaluateExpression(expr.left);
  369. right = this.evaluateExpression(expr.right);
  370. if (!left || !right) return;
  371. res = new BasicEvaluatedExpression();
  372. res.setRange(expr.range);
  373. if (left.isString() && right.isString()) {
  374. return res.setBoolean(left.string !== right.string);
  375. } else if (left.isNumber() && right.isNumber()) {
  376. return res.setBoolean(left.number !== right.number);
  377. } else if (left.isBoolean() && right.isBoolean()) {
  378. return res.setBoolean(left.bool !== right.bool);
  379. }
  380. } else if (expr.operator === "&") {
  381. left = this.evaluateExpression(expr.left);
  382. right = this.evaluateExpression(expr.right);
  383. if (!left || !right) return;
  384. if (!left.isNumber() || !right.isNumber()) return;
  385. res = new BasicEvaluatedExpression();
  386. res.setNumber(left.number & right.number);
  387. res.setRange(expr.range);
  388. return res;
  389. } else if (expr.operator === "|") {
  390. left = this.evaluateExpression(expr.left);
  391. right = this.evaluateExpression(expr.right);
  392. if (!left || !right) return;
  393. if (!left.isNumber() || !right.isNumber()) return;
  394. res = new BasicEvaluatedExpression();
  395. res.setNumber(left.number | right.number);
  396. res.setRange(expr.range);
  397. return res;
  398. } else if (expr.operator === "^") {
  399. left = this.evaluateExpression(expr.left);
  400. right = this.evaluateExpression(expr.right);
  401. if (!left || !right) return;
  402. if (!left.isNumber() || !right.isNumber()) return;
  403. res = new BasicEvaluatedExpression();
  404. res.setNumber(left.number ^ right.number);
  405. res.setRange(expr.range);
  406. return res;
  407. } else if (expr.operator === ">>>") {
  408. left = this.evaluateExpression(expr.left);
  409. right = this.evaluateExpression(expr.right);
  410. if (!left || !right) return;
  411. if (!left.isNumber() || !right.isNumber()) return;
  412. res = new BasicEvaluatedExpression();
  413. res.setNumber(left.number >>> right.number);
  414. res.setRange(expr.range);
  415. return res;
  416. } else if (expr.operator === ">>") {
  417. left = this.evaluateExpression(expr.left);
  418. right = this.evaluateExpression(expr.right);
  419. if (!left || !right) return;
  420. if (!left.isNumber() || !right.isNumber()) return;
  421. res = new BasicEvaluatedExpression();
  422. res.setNumber(left.number >> right.number);
  423. res.setRange(expr.range);
  424. return res;
  425. } else if (expr.operator === "<<") {
  426. left = this.evaluateExpression(expr.left);
  427. right = this.evaluateExpression(expr.right);
  428. if (!left || !right) return;
  429. if (!left.isNumber() || !right.isNumber()) return;
  430. res = new BasicEvaluatedExpression();
  431. res.setNumber(left.number << right.number);
  432. res.setRange(expr.range);
  433. return res;
  434. }
  435. });
  436. this.hooks.evaluate.for("UnaryExpression").tap("Parser", expr => {
  437. if (expr.operator === "typeof") {
  438. let res;
  439. let name;
  440. if (expr.argument.type === "Identifier") {
  441. name =
  442. this.scope.renames.get(expr.argument.name) || expr.argument.name;
  443. if (!this.scope.definitions.has(name)) {
  444. const hook = this.hooks.evaluateTypeof.get(name);
  445. if (hook !== undefined) {
  446. res = hook.call(expr);
  447. if (res !== undefined) return res;
  448. }
  449. }
  450. }
  451. if (expr.argument.type === "MemberExpression") {
  452. const exprName = this.getNameForExpression(expr.argument);
  453. if (exprName && exprName.free) {
  454. const hook = this.hooks.evaluateTypeof.get(exprName.name);
  455. if (hook !== undefined) {
  456. res = hook.call(expr);
  457. if (res !== undefined) return res;
  458. }
  459. }
  460. }
  461. if (expr.argument.type === "FunctionExpression") {
  462. return new BasicEvaluatedExpression()
  463. .setString("function")
  464. .setRange(expr.range);
  465. }
  466. const arg = this.evaluateExpression(expr.argument);
  467. if (arg.isString() || arg.isWrapped()) {
  468. return new BasicEvaluatedExpression()
  469. .setString("string")
  470. .setRange(expr.range);
  471. }
  472. if (arg.isNumber()) {
  473. return new BasicEvaluatedExpression()
  474. .setString("number")
  475. .setRange(expr.range);
  476. }
  477. if (arg.isBoolean()) {
  478. return new BasicEvaluatedExpression()
  479. .setString("boolean")
  480. .setRange(expr.range);
  481. }
  482. if (arg.isArray() || arg.isConstArray() || arg.isRegExp()) {
  483. return new BasicEvaluatedExpression()
  484. .setString("object")
  485. .setRange(expr.range);
  486. }
  487. } else if (expr.operator === "!") {
  488. const argument = this.evaluateExpression(expr.argument);
  489. if (!argument) return;
  490. if (argument.isBoolean()) {
  491. return new BasicEvaluatedExpression()
  492. .setBoolean(!argument.bool)
  493. .setRange(expr.range);
  494. }
  495. if (argument.isTruthy()) {
  496. return new BasicEvaluatedExpression()
  497. .setBoolean(false)
  498. .setRange(expr.range);
  499. }
  500. if (argument.isFalsy()) {
  501. return new BasicEvaluatedExpression()
  502. .setBoolean(true)
  503. .setRange(expr.range);
  504. }
  505. if (argument.isString()) {
  506. return new BasicEvaluatedExpression()
  507. .setBoolean(!argument.string)
  508. .setRange(expr.range);
  509. }
  510. if (argument.isNumber()) {
  511. return new BasicEvaluatedExpression()
  512. .setBoolean(!argument.number)
  513. .setRange(expr.range);
  514. }
  515. } else if (expr.operator === "~") {
  516. const argument = this.evaluateExpression(expr.argument);
  517. if (!argument) return;
  518. if (!argument.isNumber()) return;
  519. const res = new BasicEvaluatedExpression();
  520. res.setNumber(~argument.number);
  521. res.setRange(expr.range);
  522. return res;
  523. }
  524. });
  525. this.hooks.evaluateTypeof.for("undefined").tap("Parser", expr => {
  526. return new BasicEvaluatedExpression()
  527. .setString("undefined")
  528. .setRange(expr.range);
  529. });
  530. this.hooks.evaluate.for("Identifier").tap("Parser", expr => {
  531. const name = this.scope.renames.get(expr.name) || expr.name;
  532. if (!this.scope.definitions.has(expr.name)) {
  533. const hook = this.hooks.evaluateIdentifier.get(name);
  534. if (hook !== undefined) {
  535. const result = hook.call(expr);
  536. if (result) return result;
  537. }
  538. return new BasicEvaluatedExpression()
  539. .setIdentifier(name)
  540. .setRange(expr.range);
  541. } else {
  542. const hook = this.hooks.evaluateDefinedIdentifier.get(name);
  543. if (hook !== undefined) {
  544. return hook.call(expr);
  545. }
  546. }
  547. });
  548. this.hooks.evaluate.for("ThisExpression").tap("Parser", expr => {
  549. const name = this.scope.renames.get("this");
  550. if (name) {
  551. const hook = this.hooks.evaluateIdentifier.get(name);
  552. if (hook !== undefined) {
  553. const result = hook.call(expr);
  554. if (result) return result;
  555. }
  556. return new BasicEvaluatedExpression()
  557. .setIdentifier(name)
  558. .setRange(expr.range);
  559. }
  560. });
  561. this.hooks.evaluate.for("MemberExpression").tap("Parser", expression => {
  562. let exprName = this.getNameForExpression(expression);
  563. if (exprName) {
  564. if (exprName.free) {
  565. const hook = this.hooks.evaluateIdentifier.get(exprName.name);
  566. if (hook !== undefined) {
  567. const result = hook.call(expression);
  568. if (result) return result;
  569. }
  570. return new BasicEvaluatedExpression()
  571. .setIdentifier(exprName.name)
  572. .setRange(expression.range);
  573. } else {
  574. const hook = this.hooks.evaluateDefinedIdentifier.get(exprName.name);
  575. if (hook !== undefined) {
  576. return hook.call(expression);
  577. }
  578. }
  579. }
  580. });
  581. this.hooks.evaluate.for("CallExpression").tap("Parser", expr => {
  582. if (expr.callee.type !== "MemberExpression") return;
  583. if (
  584. expr.callee.property.type !==
  585. (expr.callee.computed ? "Literal" : "Identifier")
  586. )
  587. return;
  588. const param = this.evaluateExpression(expr.callee.object);
  589. if (!param) return;
  590. const property = expr.callee.property.name || expr.callee.property.value;
  591. const hook = this.hooks.evaluateCallExpressionMember.get(property);
  592. if (hook !== undefined) {
  593. return hook.call(expr, param);
  594. }
  595. });
  596. this.hooks.evaluateCallExpressionMember
  597. .for("replace")
  598. .tap("Parser", (expr, param) => {
  599. if (!param.isString()) return;
  600. if (expr.arguments.length !== 2) return;
  601. let arg1 = this.evaluateExpression(expr.arguments[0]);
  602. let arg2 = this.evaluateExpression(expr.arguments[1]);
  603. if (!arg1.isString() && !arg1.isRegExp()) return;
  604. arg1 = arg1.regExp || arg1.string;
  605. if (!arg2.isString()) return;
  606. arg2 = arg2.string;
  607. return new BasicEvaluatedExpression()
  608. .setString(param.string.replace(arg1, arg2))
  609. .setRange(expr.range);
  610. });
  611. ["substr", "substring"].forEach(fn => {
  612. this.hooks.evaluateCallExpressionMember
  613. .for(fn)
  614. .tap("Parser", (expr, param) => {
  615. if (!param.isString()) return;
  616. let arg1;
  617. let result,
  618. str = param.string;
  619. switch (expr.arguments.length) {
  620. case 1:
  621. arg1 = this.evaluateExpression(expr.arguments[0]);
  622. if (!arg1.isNumber()) return;
  623. result = str[fn](arg1.number);
  624. break;
  625. case 2: {
  626. arg1 = this.evaluateExpression(expr.arguments[0]);
  627. const arg2 = this.evaluateExpression(expr.arguments[1]);
  628. if (!arg1.isNumber()) return;
  629. if (!arg2.isNumber()) return;
  630. result = str[fn](arg1.number, arg2.number);
  631. break;
  632. }
  633. default:
  634. return;
  635. }
  636. return new BasicEvaluatedExpression()
  637. .setString(result)
  638. .setRange(expr.range);
  639. });
  640. });
  641. /**
  642. * @param {string} kind "cooked" | "raw"
  643. * @param {TODO} templateLiteralExpr TemplateLiteral expr
  644. * @returns {{quasis: BasicEvaluatedExpression[], parts: BasicEvaluatedExpression[]}} Simplified template
  645. */
  646. const getSimplifiedTemplateResult = (kind, templateLiteralExpr) => {
  647. const quasis = [];
  648. const parts = [];
  649. for (let i = 0; i < templateLiteralExpr.quasis.length; i++) {
  650. const quasiExpr = templateLiteralExpr.quasis[i];
  651. const quasi = quasiExpr.value[kind];
  652. if (i > 0) {
  653. const prevExpr = parts[parts.length - 1];
  654. const expr = this.evaluateExpression(
  655. templateLiteralExpr.expressions[i - 1]
  656. );
  657. const exprAsString = expr.asString();
  658. if (typeof exprAsString === "string") {
  659. // We can merge quasi + expr + quasi when expr
  660. // is a const string
  661. prevExpr.setString(prevExpr.string + exprAsString + quasi);
  662. prevExpr.setRange([prevExpr.range[0], quasiExpr.range[1]]);
  663. // We unset the expression as it doesn't match to a single expression
  664. prevExpr.setExpression(undefined);
  665. continue;
  666. }
  667. parts.push(expr);
  668. }
  669. const part = new BasicEvaluatedExpression()
  670. .setString(quasi)
  671. .setRange(quasiExpr.range)
  672. .setExpression(quasiExpr);
  673. quasis.push(part);
  674. parts.push(part);
  675. }
  676. return {
  677. quasis,
  678. parts
  679. };
  680. };
  681. this.hooks.evaluate.for("TemplateLiteral").tap("Parser", node => {
  682. const { quasis, parts } = getSimplifiedTemplateResult("cooked", node);
  683. if (parts.length === 1) {
  684. return parts[0].setRange(node.range);
  685. }
  686. return new BasicEvaluatedExpression()
  687. .setTemplateString(quasis, parts, "cooked")
  688. .setRange(node.range);
  689. });
  690. this.hooks.evaluate.for("TaggedTemplateExpression").tap("Parser", node => {
  691. if (this.evaluateExpression(node.tag).identifier !== "String.raw") return;
  692. const { quasis, parts } = getSimplifiedTemplateResult("raw", node.quasi);
  693. if (parts.length === 1) {
  694. return parts[0].setRange(node.range);
  695. }
  696. return new BasicEvaluatedExpression()
  697. .setTemplateString(quasis, parts, "raw")
  698. .setRange(node.range);
  699. });
  700. this.hooks.evaluateCallExpressionMember
  701. .for("concat")
  702. .tap("Parser", (expr, param) => {
  703. if (!param.isString() && !param.isWrapped()) return;
  704. let stringSuffix = null;
  705. let hasUnknownParams = false;
  706. for (let i = expr.arguments.length - 1; i >= 0; i--) {
  707. const argExpr = this.evaluateExpression(expr.arguments[i]);
  708. if (!argExpr.isString() && !argExpr.isNumber()) {
  709. hasUnknownParams = true;
  710. break;
  711. }
  712. const value = argExpr.isString()
  713. ? argExpr.string
  714. : "" + argExpr.number;
  715. const newString = value + (stringSuffix ? stringSuffix.string : "");
  716. const newRange = [
  717. argExpr.range[0],
  718. (stringSuffix || argExpr).range[1]
  719. ];
  720. stringSuffix = new BasicEvaluatedExpression()
  721. .setString(newString)
  722. .setRange(newRange);
  723. }
  724. if (hasUnknownParams) {
  725. const prefix = param.isString() ? param : param.prefix;
  726. return new BasicEvaluatedExpression()
  727. .setWrapped(prefix, stringSuffix)
  728. .setRange(expr.range);
  729. } else if (param.isWrapped()) {
  730. const postfix = stringSuffix || param.postfix;
  731. return new BasicEvaluatedExpression()
  732. .setWrapped(param.prefix, postfix)
  733. .setRange(expr.range);
  734. } else {
  735. const newString =
  736. param.string + (stringSuffix ? stringSuffix.string : "");
  737. return new BasicEvaluatedExpression()
  738. .setString(newString)
  739. .setRange(expr.range);
  740. }
  741. });
  742. this.hooks.evaluateCallExpressionMember
  743. .for("split")
  744. .tap("Parser", (expr, param) => {
  745. if (!param.isString()) return;
  746. if (expr.arguments.length !== 1) return;
  747. let result;
  748. const arg = this.evaluateExpression(expr.arguments[0]);
  749. if (arg.isString()) {
  750. result = param.string.split(arg.string);
  751. } else if (arg.isRegExp()) {
  752. result = param.string.split(arg.regExp);
  753. } else {
  754. return;
  755. }
  756. return new BasicEvaluatedExpression()
  757. .setArray(result)
  758. .setRange(expr.range);
  759. });
  760. this.hooks.evaluate.for("ConditionalExpression").tap("Parser", expr => {
  761. const condition = this.evaluateExpression(expr.test);
  762. const conditionValue = condition.asBool();
  763. let res;
  764. if (conditionValue === undefined) {
  765. const consequent = this.evaluateExpression(expr.consequent);
  766. const alternate = this.evaluateExpression(expr.alternate);
  767. if (!consequent || !alternate) return;
  768. res = new BasicEvaluatedExpression();
  769. if (consequent.isConditional()) {
  770. res.setOptions(consequent.options);
  771. } else {
  772. res.setOptions([consequent]);
  773. }
  774. if (alternate.isConditional()) {
  775. res.addOptions(alternate.options);
  776. } else {
  777. res.addOptions([alternate]);
  778. }
  779. } else {
  780. res = this.evaluateExpression(
  781. conditionValue ? expr.consequent : expr.alternate
  782. );
  783. }
  784. res.setRange(expr.range);
  785. return res;
  786. });
  787. this.hooks.evaluate.for("ArrayExpression").tap("Parser", expr => {
  788. const items = expr.elements.map(element => {
  789. return element !== null && this.evaluateExpression(element);
  790. });
  791. if (!items.every(Boolean)) return;
  792. return new BasicEvaluatedExpression()
  793. .setItems(items)
  794. .setRange(expr.range);
  795. });
  796. }
  797. getRenameIdentifier(expr) {
  798. const result = this.evaluateExpression(expr);
  799. if (result && result.isIdentifier()) {
  800. return result.identifier;
  801. }
  802. }
  803. walkClass(classy) {
  804. if (classy.superClass) this.walkExpression(classy.superClass);
  805. if (classy.body && classy.body.type === "ClassBody") {
  806. const wasTopLevel = this.scope.topLevelScope;
  807. this.scope.topLevelScope = false;
  808. for (const methodDefinition of classy.body.body) {
  809. if (methodDefinition.type === "MethodDefinition") {
  810. this.walkMethodDefinition(methodDefinition);
  811. }
  812. }
  813. this.scope.topLevelScope = wasTopLevel;
  814. }
  815. }
  816. walkMethodDefinition(methodDefinition) {
  817. if (methodDefinition.computed && methodDefinition.key) {
  818. this.walkExpression(methodDefinition.key);
  819. }
  820. if (methodDefinition.value) {
  821. this.walkExpression(methodDefinition.value);
  822. }
  823. }
  824. // Prewalking iterates the scope for variable declarations
  825. prewalkStatements(statements) {
  826. for (let index = 0, len = statements.length; index < len; index++) {
  827. const statement = statements[index];
  828. this.prewalkStatement(statement);
  829. }
  830. }
  831. // Block-Prewalking iterates the scope for block variable declarations
  832. blockPrewalkStatements(statements) {
  833. for (let index = 0, len = statements.length; index < len; index++) {
  834. const statement = statements[index];
  835. this.blockPrewalkStatement(statement);
  836. }
  837. }
  838. // Walking iterates the statements and expressions and processes them
  839. walkStatements(statements) {
  840. for (let index = 0, len = statements.length; index < len; index++) {
  841. const statement = statements[index];
  842. this.walkStatement(statement);
  843. }
  844. }
  845. prewalkStatement(statement) {
  846. switch (statement.type) {
  847. case "BlockStatement":
  848. this.prewalkBlockStatement(statement);
  849. break;
  850. case "DoWhileStatement":
  851. this.prewalkDoWhileStatement(statement);
  852. break;
  853. case "ExportAllDeclaration":
  854. this.prewalkExportAllDeclaration(statement);
  855. break;
  856. case "ExportDefaultDeclaration":
  857. this.prewalkExportDefaultDeclaration(statement);
  858. break;
  859. case "ExportNamedDeclaration":
  860. this.prewalkExportNamedDeclaration(statement);
  861. break;
  862. case "ForInStatement":
  863. this.prewalkForInStatement(statement);
  864. break;
  865. case "ForOfStatement":
  866. this.prewalkForOfStatement(statement);
  867. break;
  868. case "ForStatement":
  869. this.prewalkForStatement(statement);
  870. break;
  871. case "FunctionDeclaration":
  872. this.prewalkFunctionDeclaration(statement);
  873. break;
  874. case "IfStatement":
  875. this.prewalkIfStatement(statement);
  876. break;
  877. case "ImportDeclaration":
  878. this.prewalkImportDeclaration(statement);
  879. break;
  880. case "LabeledStatement":
  881. this.prewalkLabeledStatement(statement);
  882. break;
  883. case "SwitchStatement":
  884. this.prewalkSwitchStatement(statement);
  885. break;
  886. case "TryStatement":
  887. this.prewalkTryStatement(statement);
  888. break;
  889. case "VariableDeclaration":
  890. this.prewalkVariableDeclaration(statement);
  891. break;
  892. case "WhileStatement":
  893. this.prewalkWhileStatement(statement);
  894. break;
  895. case "WithStatement":
  896. this.prewalkWithStatement(statement);
  897. break;
  898. }
  899. }
  900. blockPrewalkStatement(statement) {
  901. switch (statement.type) {
  902. case "VariableDeclaration":
  903. this.blockPrewalkVariableDeclaration(statement);
  904. break;
  905. case "ExportDefaultDeclaration":
  906. this.blockPrewalkExportDefaultDeclaration(statement);
  907. break;
  908. case "ExportNamedDeclaration":
  909. this.blockPrewalkExportNamedDeclaration(statement);
  910. break;
  911. case "ClassDeclaration":
  912. this.blockPrewalkClassDeclaration(statement);
  913. break;
  914. }
  915. }
  916. walkStatement(statement) {
  917. if (this.hooks.statement.call(statement) !== undefined) return;
  918. switch (statement.type) {
  919. case "BlockStatement":
  920. this.walkBlockStatement(statement);
  921. break;
  922. case "ClassDeclaration":
  923. this.walkClassDeclaration(statement);
  924. break;
  925. case "DoWhileStatement":
  926. this.walkDoWhileStatement(statement);
  927. break;
  928. case "ExportDefaultDeclaration":
  929. this.walkExportDefaultDeclaration(statement);
  930. break;
  931. case "ExportNamedDeclaration":
  932. this.walkExportNamedDeclaration(statement);
  933. break;
  934. case "ExpressionStatement":
  935. this.walkExpressionStatement(statement);
  936. break;
  937. case "ForInStatement":
  938. this.walkForInStatement(statement);
  939. break;
  940. case "ForOfStatement":
  941. this.walkForOfStatement(statement);
  942. break;
  943. case "ForStatement":
  944. this.walkForStatement(statement);
  945. break;
  946. case "FunctionDeclaration":
  947. this.walkFunctionDeclaration(statement);
  948. break;
  949. case "IfStatement":
  950. this.walkIfStatement(statement);
  951. break;
  952. case "LabeledStatement":
  953. this.walkLabeledStatement(statement);
  954. break;
  955. case "ReturnStatement":
  956. this.walkReturnStatement(statement);
  957. break;
  958. case "SwitchStatement":
  959. this.walkSwitchStatement(statement);
  960. break;
  961. case "ThrowStatement":
  962. this.walkThrowStatement(statement);
  963. break;
  964. case "TryStatement":
  965. this.walkTryStatement(statement);
  966. break;
  967. case "VariableDeclaration":
  968. this.walkVariableDeclaration(statement);
  969. break;
  970. case "WhileStatement":
  971. this.walkWhileStatement(statement);
  972. break;
  973. case "WithStatement":
  974. this.walkWithStatement(statement);
  975. break;
  976. }
  977. }
  978. // Real Statements
  979. prewalkBlockStatement(statement) {
  980. this.prewalkStatements(statement.body);
  981. }
  982. walkBlockStatement(statement) {
  983. this.inBlockScope(() => {
  984. const body = statement.body;
  985. this.blockPrewalkStatements(body);
  986. this.walkStatements(body);
  987. });
  988. }
  989. walkExpressionStatement(statement) {
  990. this.walkExpression(statement.expression);
  991. }
  992. prewalkIfStatement(statement) {
  993. this.prewalkStatement(statement.consequent);
  994. if (statement.alternate) {
  995. this.prewalkStatement(statement.alternate);
  996. }
  997. }
  998. walkIfStatement(statement) {
  999. const result = this.hooks.statementIf.call(statement);
  1000. if (result === undefined) {
  1001. this.walkExpression(statement.test);
  1002. this.walkStatement(statement.consequent);
  1003. if (statement.alternate) {
  1004. this.walkStatement(statement.alternate);
  1005. }
  1006. } else {
  1007. if (result) {
  1008. this.walkStatement(statement.consequent);
  1009. } else if (statement.alternate) {
  1010. this.walkStatement(statement.alternate);
  1011. }
  1012. }
  1013. }
  1014. prewalkLabeledStatement(statement) {
  1015. this.prewalkStatement(statement.body);
  1016. }
  1017. walkLabeledStatement(statement) {
  1018. const hook = this.hooks.label.get(statement.label.name);
  1019. if (hook !== undefined) {
  1020. const result = hook.call(statement);
  1021. if (result === true) return;
  1022. }
  1023. this.walkStatement(statement.body);
  1024. }
  1025. prewalkWithStatement(statement) {
  1026. this.prewalkStatement(statement.body);
  1027. }
  1028. walkWithStatement(statement) {
  1029. this.walkExpression(statement.object);
  1030. this.walkStatement(statement.body);
  1031. }
  1032. prewalkSwitchStatement(statement) {
  1033. this.prewalkSwitchCases(statement.cases);
  1034. }
  1035. walkSwitchStatement(statement) {
  1036. this.walkExpression(statement.discriminant);
  1037. this.walkSwitchCases(statement.cases);
  1038. }
  1039. walkTerminatingStatement(statement) {
  1040. if (statement.argument) this.walkExpression(statement.argument);
  1041. }
  1042. walkReturnStatement(statement) {
  1043. this.walkTerminatingStatement(statement);
  1044. }
  1045. walkThrowStatement(statement) {
  1046. this.walkTerminatingStatement(statement);
  1047. }
  1048. prewalkTryStatement(statement) {
  1049. this.prewalkStatement(statement.block);
  1050. }
  1051. walkTryStatement(statement) {
  1052. if (this.scope.inTry) {
  1053. this.walkStatement(statement.block);
  1054. } else {
  1055. this.scope.inTry = true;
  1056. this.walkStatement(statement.block);
  1057. this.scope.inTry = false;
  1058. }
  1059. if (statement.handler) this.walkCatchClause(statement.handler);
  1060. if (statement.finalizer) this.walkStatement(statement.finalizer);
  1061. }
  1062. prewalkWhileStatement(statement) {
  1063. this.prewalkStatement(statement.body);
  1064. }
  1065. walkWhileStatement(statement) {
  1066. this.walkExpression(statement.test);
  1067. this.walkStatement(statement.body);
  1068. }
  1069. prewalkDoWhileStatement(statement) {
  1070. this.prewalkStatement(statement.body);
  1071. }
  1072. walkDoWhileStatement(statement) {
  1073. this.walkStatement(statement.body);
  1074. this.walkExpression(statement.test);
  1075. }
  1076. prewalkForStatement(statement) {
  1077. if (statement.init) {
  1078. if (statement.init.type === "VariableDeclaration") {
  1079. this.prewalkStatement(statement.init);
  1080. }
  1081. }
  1082. this.prewalkStatement(statement.body);
  1083. }
  1084. walkForStatement(statement) {
  1085. this.inBlockScope(() => {
  1086. if (statement.init) {
  1087. if (statement.init.type === "VariableDeclaration") {
  1088. this.blockPrewalkVariableDeclaration(statement.init);
  1089. this.walkStatement(statement.init);
  1090. } else {
  1091. this.walkExpression(statement.init);
  1092. }
  1093. }
  1094. if (statement.test) {
  1095. this.walkExpression(statement.test);
  1096. }
  1097. if (statement.update) {
  1098. this.walkExpression(statement.update);
  1099. }
  1100. const body = statement.body;
  1101. if (body.type === "BlockStatement") {
  1102. // no need to add additional scope
  1103. this.blockPrewalkStatements(body.body);
  1104. this.walkStatements(body.body);
  1105. } else {
  1106. this.walkStatement(body);
  1107. }
  1108. });
  1109. }
  1110. prewalkForInStatement(statement) {
  1111. if (statement.left.type === "VariableDeclaration") {
  1112. this.prewalkVariableDeclaration(statement.left);
  1113. }
  1114. this.prewalkStatement(statement.body);
  1115. }
  1116. walkForInStatement(statement) {
  1117. this.inBlockScope(() => {
  1118. if (statement.left.type === "VariableDeclaration") {
  1119. this.blockPrewalkVariableDeclaration(statement.left);
  1120. this.walkVariableDeclaration(statement.left);
  1121. } else {
  1122. this.walkPattern(statement.left);
  1123. }
  1124. this.walkExpression(statement.right);
  1125. const body = statement.body;
  1126. if (body.type === "BlockStatement") {
  1127. // no need to add additional scope
  1128. this.blockPrewalkStatements(body.body);
  1129. this.walkStatements(body.body);
  1130. } else {
  1131. this.walkStatement(body);
  1132. }
  1133. });
  1134. }
  1135. prewalkForOfStatement(statement) {
  1136. if (statement.left.type === "VariableDeclaration") {
  1137. this.prewalkVariableDeclaration(statement.left);
  1138. }
  1139. this.prewalkStatement(statement.body);
  1140. }
  1141. walkForOfStatement(statement) {
  1142. this.inBlockScope(() => {
  1143. if (statement.left.type === "VariableDeclaration") {
  1144. this.blockPrewalkVariableDeclaration(statement.left);
  1145. this.walkVariableDeclaration(statement.left);
  1146. } else {
  1147. this.walkPattern(statement.left);
  1148. }
  1149. this.walkExpression(statement.right);
  1150. const body = statement.body;
  1151. if (body.type === "BlockStatement") {
  1152. // no need to add additional scope
  1153. this.blockPrewalkStatements(body.body);
  1154. this.walkStatements(body.body);
  1155. } else {
  1156. this.walkStatement(body);
  1157. }
  1158. });
  1159. }
  1160. // Declarations
  1161. prewalkFunctionDeclaration(statement) {
  1162. if (statement.id) {
  1163. this.scope.renames.set(statement.id.name, null);
  1164. this.scope.definitions.add(statement.id.name);
  1165. }
  1166. }
  1167. walkFunctionDeclaration(statement) {
  1168. const wasTopLevel = this.scope.topLevelScope;
  1169. this.scope.topLevelScope = false;
  1170. this.inFunctionScope(true, statement.params, () => {
  1171. for (const param of statement.params) {
  1172. this.walkPattern(param);
  1173. }
  1174. if (statement.body.type === "BlockStatement") {
  1175. this.detectStrictMode(statement.body.body);
  1176. this.prewalkStatement(statement.body);
  1177. this.walkStatement(statement.body);
  1178. } else {
  1179. this.walkExpression(statement.body);
  1180. }
  1181. });
  1182. this.scope.topLevelScope = wasTopLevel;
  1183. }
  1184. prewalkImportDeclaration(statement) {
  1185. const source = statement.source.value;
  1186. this.hooks.import.call(statement, source);
  1187. for (const specifier of statement.specifiers) {
  1188. const name = specifier.local.name;
  1189. this.scope.renames.set(name, null);
  1190. this.scope.definitions.add(name);
  1191. switch (specifier.type) {
  1192. case "ImportDefaultSpecifier":
  1193. this.hooks.importSpecifier.call(statement, source, "default", name);
  1194. break;
  1195. case "ImportSpecifier":
  1196. this.hooks.importSpecifier.call(
  1197. statement,
  1198. source,
  1199. specifier.imported.name,
  1200. name
  1201. );
  1202. break;
  1203. case "ImportNamespaceSpecifier":
  1204. this.hooks.importSpecifier.call(statement, source, null, name);
  1205. break;
  1206. }
  1207. }
  1208. }
  1209. enterDeclaration(declaration, onIdent) {
  1210. switch (declaration.type) {
  1211. case "VariableDeclaration":
  1212. for (const declarator of declaration.declarations) {
  1213. switch (declarator.type) {
  1214. case "VariableDeclarator": {
  1215. this.enterPattern(declarator.id, onIdent);
  1216. break;
  1217. }
  1218. }
  1219. }
  1220. break;
  1221. case "FunctionDeclaration":
  1222. this.enterPattern(declaration.id, onIdent);
  1223. break;
  1224. case "ClassDeclaration":
  1225. this.enterPattern(declaration.id, onIdent);
  1226. break;
  1227. }
  1228. }
  1229. blockPrewalkExportNamedDeclaration(statement) {
  1230. if (statement.declaration) {
  1231. this.blockPrewalkStatement(statement.declaration);
  1232. }
  1233. }
  1234. prewalkExportNamedDeclaration(statement) {
  1235. let source;
  1236. if (statement.source) {
  1237. source = statement.source.value;
  1238. this.hooks.exportImport.call(statement, source);
  1239. } else {
  1240. this.hooks.export.call(statement);
  1241. }
  1242. if (statement.declaration) {
  1243. if (
  1244. !this.hooks.exportDeclaration.call(statement, statement.declaration)
  1245. ) {
  1246. this.prewalkStatement(statement.declaration);
  1247. let index = 0;
  1248. this.enterDeclaration(statement.declaration, def => {
  1249. this.hooks.exportSpecifier.call(statement, def, def, index++);
  1250. });
  1251. }
  1252. }
  1253. if (statement.specifiers) {
  1254. for (
  1255. let specifierIndex = 0;
  1256. specifierIndex < statement.specifiers.length;
  1257. specifierIndex++
  1258. ) {
  1259. const specifier = statement.specifiers[specifierIndex];
  1260. switch (specifier.type) {
  1261. case "ExportSpecifier": {
  1262. const name = specifier.exported.name;
  1263. if (source) {
  1264. this.hooks.exportImportSpecifier.call(
  1265. statement,
  1266. source,
  1267. specifier.local.name,
  1268. name,
  1269. specifierIndex
  1270. );
  1271. } else {
  1272. this.hooks.exportSpecifier.call(
  1273. statement,
  1274. specifier.local.name,
  1275. name,
  1276. specifierIndex
  1277. );
  1278. }
  1279. break;
  1280. }
  1281. }
  1282. }
  1283. }
  1284. }
  1285. walkExportNamedDeclaration(statement) {
  1286. if (statement.declaration) {
  1287. this.walkStatement(statement.declaration);
  1288. }
  1289. }
  1290. blockPrewalkExportDefaultDeclaration(statement) {
  1291. if (statement.declaration.type === "ClassDeclaration") {
  1292. this.blockPrewalkClassDeclaration(statement.declaration);
  1293. }
  1294. }
  1295. prewalkExportDefaultDeclaration(statement) {
  1296. this.prewalkStatement(statement.declaration);
  1297. if (
  1298. statement.declaration.id &&
  1299. statement.declaration.type !== "FunctionExpression" &&
  1300. statement.declaration.type !== "ClassExpression"
  1301. ) {
  1302. this.hooks.exportSpecifier.call(
  1303. statement,
  1304. statement.declaration.id.name,
  1305. "default"
  1306. );
  1307. }
  1308. }
  1309. walkExportDefaultDeclaration(statement) {
  1310. this.hooks.export.call(statement);
  1311. if (
  1312. statement.declaration.id &&
  1313. statement.declaration.type !== "FunctionExpression" &&
  1314. statement.declaration.type !== "ClassExpression"
  1315. ) {
  1316. if (
  1317. !this.hooks.exportDeclaration.call(statement, statement.declaration)
  1318. ) {
  1319. this.walkStatement(statement.declaration);
  1320. }
  1321. } else {
  1322. // Acorn parses `export default function() {}` as `FunctionDeclaration` and
  1323. // `export default class {}` as `ClassDeclaration`, both with `id = null`.
  1324. // These nodes must be treated as expressions.
  1325. if (statement.declaration.type === "FunctionDeclaration") {
  1326. this.walkFunctionDeclaration(statement.declaration);
  1327. } else if (statement.declaration.type === "ClassDeclaration") {
  1328. this.walkClassDeclaration(statement.declaration);
  1329. } else {
  1330. this.walkExpression(statement.declaration);
  1331. }
  1332. if (!this.hooks.exportExpression.call(statement, statement.declaration)) {
  1333. this.hooks.exportSpecifier.call(
  1334. statement,
  1335. statement.declaration,
  1336. "default"
  1337. );
  1338. }
  1339. }
  1340. }
  1341. prewalkExportAllDeclaration(statement) {
  1342. const source = statement.source.value;
  1343. this.hooks.exportImport.call(statement, source);
  1344. this.hooks.exportImportSpecifier.call(statement, source, null, null, 0);
  1345. }
  1346. prewalkVariableDeclaration(statement) {
  1347. if (statement.kind !== "var") return;
  1348. this._prewalkVariableDeclaration(statement, this.hooks.varDeclarationVar);
  1349. }
  1350. blockPrewalkVariableDeclaration(statement) {
  1351. if (statement.kind === "var") return;
  1352. const hookMap =
  1353. statement.kind === "const"
  1354. ? this.hooks.varDeclarationConst
  1355. : this.hooks.varDeclarationLet;
  1356. this._prewalkVariableDeclaration(statement, hookMap);
  1357. }
  1358. _prewalkVariableDeclaration(statement, hookMap) {
  1359. for (const declarator of statement.declarations) {
  1360. switch (declarator.type) {
  1361. case "VariableDeclarator": {
  1362. this.enterPattern(declarator.id, (name, decl) => {
  1363. let hook = hookMap.get(name);
  1364. if (hook === undefined || !hook.call(decl)) {
  1365. hook = this.hooks.varDeclaration.get(name);
  1366. if (hook === undefined || !hook.call(decl)) {
  1367. this.scope.renames.set(name, null);
  1368. this.scope.definitions.add(name);
  1369. }
  1370. }
  1371. });
  1372. break;
  1373. }
  1374. }
  1375. }
  1376. }
  1377. walkVariableDeclaration(statement) {
  1378. for (const declarator of statement.declarations) {
  1379. switch (declarator.type) {
  1380. case "VariableDeclarator": {
  1381. const renameIdentifier =
  1382. declarator.init && this.getRenameIdentifier(declarator.init);
  1383. if (renameIdentifier && declarator.id.type === "Identifier") {
  1384. const hook = this.hooks.canRename.get(renameIdentifier);
  1385. if (hook !== undefined && hook.call(declarator.init)) {
  1386. // renaming with "var a = b;"
  1387. const hook = this.hooks.rename.get(renameIdentifier);
  1388. if (hook === undefined || !hook.call(declarator.init)) {
  1389. this.scope.renames.set(
  1390. declarator.id.name,
  1391. this.scope.renames.get(renameIdentifier) || renameIdentifier
  1392. );
  1393. this.scope.definitions.delete(declarator.id.name);
  1394. }
  1395. break;
  1396. }
  1397. }
  1398. this.walkPattern(declarator.id);
  1399. if (declarator.init) this.walkExpression(declarator.init);
  1400. break;
  1401. }
  1402. }
  1403. }
  1404. }
  1405. blockPrewalkClassDeclaration(statement) {
  1406. if (statement.id) {
  1407. this.scope.renames.set(statement.id.name, null);
  1408. this.scope.definitions.add(statement.id.name);
  1409. }
  1410. }
  1411. walkClassDeclaration(statement) {
  1412. this.walkClass(statement);
  1413. }
  1414. prewalkSwitchCases(switchCases) {
  1415. for (let index = 0, len = switchCases.length; index < len; index++) {
  1416. const switchCase = switchCases[index];
  1417. this.prewalkStatements(switchCase.consequent);
  1418. }
  1419. }
  1420. walkSwitchCases(switchCases) {
  1421. for (let index = 0, len = switchCases.length; index < len; index++) {
  1422. const switchCase = switchCases[index];
  1423. if (switchCase.test) {
  1424. this.walkExpression(switchCase.test);
  1425. }
  1426. this.walkStatements(switchCase.consequent);
  1427. }
  1428. }
  1429. walkCatchClause(catchClause) {
  1430. this.inBlockScope(() => {
  1431. // Error binding is optional in catch clause since ECMAScript 2019
  1432. if (catchClause.param !== null) {
  1433. this.enterPattern(catchClause.param, ident => {
  1434. this.scope.renames.set(ident, null);
  1435. this.scope.definitions.add(ident);
  1436. });
  1437. this.walkPattern(catchClause.param);
  1438. }
  1439. this.prewalkStatement(catchClause.body);
  1440. this.walkStatement(catchClause.body);
  1441. });
  1442. }
  1443. walkPattern(pattern) {
  1444. switch (pattern.type) {
  1445. case "ArrayPattern":
  1446. this.walkArrayPattern(pattern);
  1447. break;
  1448. case "AssignmentPattern":
  1449. this.walkAssignmentPattern(pattern);
  1450. break;
  1451. case "MemberExpression":
  1452. this.walkMemberExpression(pattern);
  1453. break;
  1454. case "ObjectPattern":
  1455. this.walkObjectPattern(pattern);
  1456. break;
  1457. case "RestElement":
  1458. this.walkRestElement(pattern);
  1459. break;
  1460. }
  1461. }
  1462. walkAssignmentPattern(pattern) {
  1463. this.walkExpression(pattern.right);
  1464. this.walkPattern(pattern.left);
  1465. }
  1466. walkObjectPattern(pattern) {
  1467. for (let i = 0, len = pattern.properties.length; i < len; i++) {
  1468. const prop = pattern.properties[i];
  1469. if (prop) {
  1470. if (prop.computed) this.walkExpression(prop.key);
  1471. if (prop.value) this.walkPattern(prop.value);
  1472. }
  1473. }
  1474. }
  1475. walkArrayPattern(pattern) {
  1476. for (let i = 0, len = pattern.elements.length; i < len; i++) {
  1477. const element = pattern.elements[i];
  1478. if (element) this.walkPattern(element);
  1479. }
  1480. }
  1481. walkRestElement(pattern) {
  1482. this.walkPattern(pattern.argument);
  1483. }
  1484. walkExpressions(expressions) {
  1485. for (const expression of expressions) {
  1486. if (expression) {
  1487. this.walkExpression(expression);
  1488. }
  1489. }
  1490. }
  1491. walkExpression(expression) {
  1492. switch (expression.type) {
  1493. case "ArrayExpression":
  1494. this.walkArrayExpression(expression);
  1495. break;
  1496. case "ArrowFunctionExpression":
  1497. this.walkArrowFunctionExpression(expression);
  1498. break;
  1499. case "AssignmentExpression":
  1500. this.walkAssignmentExpression(expression);
  1501. break;
  1502. case "AwaitExpression":
  1503. this.walkAwaitExpression(expression);
  1504. break;
  1505. case "BinaryExpression":
  1506. this.walkBinaryExpression(expression);
  1507. break;
  1508. case "CallExpression":
  1509. this.walkCallExpression(expression);
  1510. break;
  1511. case "ClassExpression":
  1512. this.walkClassExpression(expression);
  1513. break;
  1514. case "ConditionalExpression":
  1515. this.walkConditionalExpression(expression);
  1516. break;
  1517. case "FunctionExpression":
  1518. this.walkFunctionExpression(expression);
  1519. break;
  1520. case "Identifier":
  1521. this.walkIdentifier(expression);
  1522. break;
  1523. case "LogicalExpression":
  1524. this.walkLogicalExpression(expression);
  1525. break;
  1526. case "MemberExpression":
  1527. this.walkMemberExpression(expression);
  1528. break;
  1529. case "NewExpression":
  1530. this.walkNewExpression(expression);
  1531. break;
  1532. case "ObjectExpression":
  1533. this.walkObjectExpression(expression);
  1534. break;
  1535. case "SequenceExpression":
  1536. this.walkSequenceExpression(expression);
  1537. break;
  1538. case "SpreadElement":
  1539. this.walkSpreadElement(expression);
  1540. break;
  1541. case "TaggedTemplateExpression":
  1542. this.walkTaggedTemplateExpression(expression);
  1543. break;
  1544. case "TemplateLiteral":
  1545. this.walkTemplateLiteral(expression);
  1546. break;
  1547. case "ThisExpression":
  1548. this.walkThisExpression(expression);
  1549. break;
  1550. case "UnaryExpression":
  1551. this.walkUnaryExpression(expression);
  1552. break;
  1553. case "UpdateExpression":
  1554. this.walkUpdateExpression(expression);
  1555. break;
  1556. case "YieldExpression":
  1557. this.walkYieldExpression(expression);
  1558. break;
  1559. }
  1560. }
  1561. walkAwaitExpression(expression) {
  1562. this.walkExpression(expression.argument);
  1563. }
  1564. walkArrayExpression(expression) {
  1565. if (expression.elements) {
  1566. this.walkExpressions(expression.elements);
  1567. }
  1568. }
  1569. walkSpreadElement(expression) {
  1570. if (expression.argument) {
  1571. this.walkExpression(expression.argument);
  1572. }
  1573. }
  1574. walkObjectExpression(expression) {
  1575. for (
  1576. let propIndex = 0, len = expression.properties.length;
  1577. propIndex < len;
  1578. propIndex++
  1579. ) {
  1580. const prop = expression.properties[propIndex];
  1581. if (prop.type === "SpreadElement") {
  1582. this.walkExpression(prop.argument);
  1583. continue;
  1584. }
  1585. if (prop.computed) {
  1586. this.walkExpression(prop.key);
  1587. }
  1588. if (prop.shorthand) {
  1589. this.scope.inShorthand = true;
  1590. }
  1591. this.walkExpression(prop.value);
  1592. if (prop.shorthand) {
  1593. this.scope.inShorthand = false;
  1594. }
  1595. }
  1596. }
  1597. walkFunctionExpression(expression) {
  1598. const wasTopLevel = this.scope.topLevelScope;
  1599. this.scope.topLevelScope = false;
  1600. const scopeParams = expression.params;
  1601. // Add function name in scope for recursive calls
  1602. if (expression.id) {
  1603. scopeParams.push(expression.id.name);
  1604. }
  1605. this.inFunctionScope(true, scopeParams, () => {
  1606. for (const param of expression.params) {
  1607. this.walkPattern(param);
  1608. }
  1609. if (expression.body.type === "BlockStatement") {
  1610. this.detectStrictMode(expression.body.body);
  1611. this.prewalkStatement(expression.body);
  1612. this.walkStatement(expression.body);
  1613. } else {
  1614. this.walkExpression(expression.body);
  1615. }
  1616. });
  1617. this.scope.topLevelScope = wasTopLevel;
  1618. }
  1619. walkArrowFunctionExpression(expression) {
  1620. this.inFunctionScope(false, expression.params, () => {
  1621. for (const param of expression.params) {
  1622. this.walkPattern(param);
  1623. }
  1624. if (expression.body.type === "BlockStatement") {
  1625. this.detectStrictMode(expression.body.body);
  1626. this.prewalkStatement(expression.body);
  1627. this.walkStatement(expression.body);
  1628. } else {
  1629. this.walkExpression(expression.body);
  1630. }
  1631. });
  1632. }
  1633. walkSequenceExpression(expression) {
  1634. if (expression.expressions) this.walkExpressions(expression.expressions);
  1635. }
  1636. walkUpdateExpression(expression) {
  1637. this.walkExpression(expression.argument);
  1638. }
  1639. walkUnaryExpression(expression) {
  1640. if (expression.operator === "typeof") {
  1641. const exprName = this.getNameForExpression(expression.argument);
  1642. if (exprName && exprName.free) {
  1643. const hook = this.hooks.typeof.get(exprName.name);
  1644. if (hook !== undefined) {
  1645. const result = hook.call(expression);
  1646. if (result === true) return;
  1647. }
  1648. }
  1649. }
  1650. this.walkExpression(expression.argument);
  1651. }
  1652. walkLeftRightExpression(expression) {
  1653. this.walkExpression(expression.left);
  1654. this.walkExpression(expression.right);
  1655. }
  1656. walkBinaryExpression(expression) {
  1657. this.walkLeftRightExpression(expression);
  1658. }
  1659. walkLogicalExpression(expression) {
  1660. const result = this.hooks.expressionLogicalOperator.call(expression);
  1661. if (result === undefined) {
  1662. this.walkLeftRightExpression(expression);
  1663. } else {
  1664. if (result) {
  1665. this.walkExpression(expression.right);
  1666. }
  1667. }
  1668. }
  1669. walkAssignmentExpression(expression) {
  1670. const renameIdentifier = this.getRenameIdentifier(expression.right);
  1671. if (expression.left.type === "Identifier" && renameIdentifier) {
  1672. const hook = this.hooks.canRename.get(renameIdentifier);
  1673. if (hook !== undefined && hook.call(expression.right)) {
  1674. // renaming "a = b;"
  1675. const hook = this.hooks.rename.get(renameIdentifier);
  1676. if (hook === undefined || !hook.call(expression.right)) {
  1677. this.scope.renames.set(expression.left.name, renameIdentifier);
  1678. this.scope.definitions.delete(expression.left.name);
  1679. }
  1680. return;
  1681. }
  1682. }
  1683. if (expression.left.type === "Identifier") {
  1684. const assignedHook = this.hooks.assigned.get(expression.left.name);
  1685. if (assignedHook === undefined || !assignedHook.call(expression)) {
  1686. this.walkExpression(expression.right);
  1687. }
  1688. this.scope.renames.set(expression.left.name, null);
  1689. const assignHook = this.hooks.assign.get(expression.left.name);
  1690. if (assignHook === undefined || !assignHook.call(expression)) {
  1691. this.walkExpression(expression.left);
  1692. }
  1693. return;
  1694. }
  1695. this.walkExpression(expression.right);
  1696. this.walkPattern(expression.left);
  1697. this.enterPattern(expression.left, (name, decl) => {
  1698. this.scope.renames.set(name, null);
  1699. });
  1700. }
  1701. walkConditionalExpression(expression) {
  1702. const result = this.hooks.expressionConditionalOperator.call(expression);
  1703. if (result === undefined) {
  1704. this.walkExpression(expression.test);
  1705. this.walkExpression(expression.consequent);
  1706. if (expression.alternate) {
  1707. this.walkExpression(expression.alternate);
  1708. }
  1709. } else {
  1710. if (result) {
  1711. this.walkExpression(expression.consequent);
  1712. } else if (expression.alternate) {
  1713. this.walkExpression(expression.alternate);
  1714. }
  1715. }
  1716. }
  1717. walkNewExpression(expression) {
  1718. const callee = this.evaluateExpression(expression.callee);
  1719. if (callee.isIdentifier()) {
  1720. const hook = this.hooks.new.get(callee.identifier);
  1721. if (hook !== undefined) {
  1722. const result = hook.call(expression);
  1723. if (result === true) {
  1724. return;
  1725. }
  1726. }
  1727. }
  1728. this.walkExpression(expression.callee);
  1729. if (expression.arguments) {
  1730. this.walkExpressions(expression.arguments);
  1731. }
  1732. }
  1733. walkYieldExpression(expression) {
  1734. if (expression.argument) {
  1735. this.walkExpression(expression.argument);
  1736. }
  1737. }
  1738. walkTemplateLiteral(expression) {
  1739. if (expression.expressions) {
  1740. this.walkExpressions(expression.expressions);
  1741. }
  1742. }
  1743. walkTaggedTemplateExpression(expression) {
  1744. if (expression.tag) {
  1745. this.walkExpression(expression.tag);
  1746. }
  1747. if (expression.quasi && expression.quasi.expressions) {
  1748. this.walkExpressions(expression.quasi.expressions);
  1749. }
  1750. }
  1751. walkClassExpression(expression) {
  1752. this.walkClass(expression);
  1753. }
  1754. _walkIIFE(functionExpression, options, currentThis) {
  1755. const renameArgOrThis = argOrThis => {
  1756. const renameIdentifier = this.getRenameIdentifier(argOrThis);
  1757. if (renameIdentifier) {
  1758. const hook = this.hooks.canRename.get(renameIdentifier);
  1759. if (hook !== undefined && hook.call(argOrThis)) {
  1760. const hook = this.hooks.rename.get(renameIdentifier);
  1761. if (hook === undefined || !hook.call(argOrThis)) {
  1762. return renameIdentifier;
  1763. }
  1764. }
  1765. }
  1766. this.walkExpression(argOrThis);
  1767. };
  1768. const params = functionExpression.params;
  1769. const renameThis = currentThis ? renameArgOrThis(currentThis) : null;
  1770. const args = options.map(renameArgOrThis);
  1771. const wasTopLevel = this.scope.topLevelScope;
  1772. this.scope.topLevelScope = false;
  1773. const scopeParams = params.filter((identifier, idx) => !args[idx]);
  1774. // Add function name in scope for recursive calls
  1775. if (functionExpression.id) {
  1776. scopeParams.push(functionExpression.id.name);
  1777. }
  1778. this.inFunctionScope(true, scopeParams, () => {
  1779. if (renameThis) {
  1780. this.scope.renames.set("this", renameThis);
  1781. }
  1782. for (let i = 0; i < args.length; i++) {
  1783. const param = args[i];
  1784. if (!param) continue;
  1785. if (!params[i] || params[i].type !== "Identifier") continue;
  1786. this.scope.renames.set(params[i].name, param);
  1787. }
  1788. if (functionExpression.body.type === "BlockStatement") {
  1789. this.prewalkStatement(functionExpression.body);
  1790. this.walkStatement(functionExpression.body);
  1791. } else {
  1792. this.walkExpression(functionExpression.body);
  1793. }
  1794. });
  1795. this.scope.topLevelScope = wasTopLevel;
  1796. }
  1797. walkCallExpression(expression) {
  1798. if (
  1799. expression.callee.type === "MemberExpression" &&
  1800. expression.callee.object.type === "FunctionExpression" &&
  1801. !expression.callee.computed &&
  1802. (expression.callee.property.name === "call" ||
  1803. expression.callee.property.name === "bind") &&
  1804. expression.arguments.length > 0
  1805. ) {
  1806. // (function(…) { }.call/bind(?, …))
  1807. this._walkIIFE(
  1808. expression.callee.object,
  1809. expression.arguments.slice(1),
  1810. expression.arguments[0]
  1811. );
  1812. } else if (expression.callee.type === "FunctionExpression") {
  1813. // (function(…) { }(…))
  1814. this._walkIIFE(expression.callee, expression.arguments, null);
  1815. } else if (expression.callee.type === "Import") {
  1816. let result = this.hooks.importCall.call(expression);
  1817. if (result === true) return;
  1818. if (expression.arguments) this.walkExpressions(expression.arguments);
  1819. } else {
  1820. const callee = this.evaluateExpression(expression.callee);
  1821. if (callee.isIdentifier()) {
  1822. const callHook = this.hooks.call.get(callee.identifier);
  1823. if (callHook !== undefined) {
  1824. let result = callHook.call(expression);
  1825. if (result === true) return;
  1826. }
  1827. let identifier = callee.identifier.replace(/\.[^.]+$/, "");
  1828. if (identifier !== callee.identifier) {
  1829. const callAnyHook = this.hooks.callAnyMember.get(identifier);
  1830. if (callAnyHook !== undefined) {
  1831. let result = callAnyHook.call(expression);
  1832. if (result === true) return;
  1833. }
  1834. }
  1835. }
  1836. if (expression.callee) this.walkExpression(expression.callee);
  1837. if (expression.arguments) this.walkExpressions(expression.arguments);
  1838. }
  1839. }
  1840. walkMemberExpression(expression) {
  1841. const exprName = this.getNameForExpression(expression);
  1842. if (exprName && exprName.free) {
  1843. const expressionHook = this.hooks.expression.get(exprName.name);
  1844. if (expressionHook !== undefined) {
  1845. const result = expressionHook.call(expression);
  1846. if (result === true) return;
  1847. }
  1848. const expressionAnyMemberHook = this.hooks.expressionAnyMember.get(
  1849. exprName.nameGeneral
  1850. );
  1851. if (expressionAnyMemberHook !== undefined) {
  1852. const result = expressionAnyMemberHook.call(expression);
  1853. if (result === true) return;
  1854. }
  1855. }
  1856. this.walkExpression(expression.object);
  1857. if (expression.computed === true) this.walkExpression(expression.property);
  1858. }
  1859. walkThisExpression(expression) {
  1860. const expressionHook = this.hooks.expression.get("this");
  1861. if (expressionHook !== undefined) {
  1862. expressionHook.call(expression);
  1863. }
  1864. }
  1865. walkIdentifier(expression) {
  1866. if (!this.scope.definitions.has(expression.name)) {
  1867. const hook = this.hooks.expression.get(
  1868. this.scope.renames.get(expression.name) || expression.name
  1869. );
  1870. if (hook !== undefined) {
  1871. const result = hook.call(expression);
  1872. if (result === true) return;
  1873. }
  1874. }
  1875. }
  1876. /**
  1877. * @deprecated
  1878. * @param {any} params scope params
  1879. * @param {function(): void} fn inner function
  1880. * @returns {void}
  1881. */
  1882. inScope(params, fn) {
  1883. const oldScope = this.scope;
  1884. this.scope = {
  1885. topLevelScope: oldScope.topLevelScope,
  1886. inTry: false,
  1887. inShorthand: false,
  1888. isStrict: oldScope.isStrict,
  1889. definitions: oldScope.definitions.createChild(),
  1890. renames: oldScope.renames.createChild()
  1891. };
  1892. this.scope.renames.set("this", null);
  1893. this.enterPatterns(params, ident => {
  1894. this.scope.renames.set(ident, null);
  1895. this.scope.definitions.add(ident);
  1896. });
  1897. fn();
  1898. this.scope = oldScope;
  1899. }
  1900. inFunctionScope(hasThis, params, fn) {
  1901. const oldScope = this.scope;
  1902. this.scope = {
  1903. topLevelScope: oldScope.topLevelScope,
  1904. inTry: false,
  1905. inShorthand: false,
  1906. isStrict: oldScope.isStrict,
  1907. definitions: oldScope.definitions.createChild(),
  1908. renames: oldScope.renames.createChild()
  1909. };
  1910. if (hasThis) {
  1911. this.scope.renames.set("this", null);
  1912. }
  1913. this.enterPatterns(params, ident => {
  1914. this.scope.renames.set(ident, null);
  1915. this.scope.definitions.add(ident);
  1916. });
  1917. fn();
  1918. this.scope = oldScope;
  1919. }
  1920. inBlockScope(fn) {
  1921. const oldScope = this.scope;
  1922. this.scope = {
  1923. topLevelScope: oldScope.topLevelScope,
  1924. inTry: oldScope.inTry,
  1925. inShorthand: false,
  1926. isStrict: oldScope.isStrict,
  1927. definitions: oldScope.definitions.createChild(),
  1928. renames: oldScope.renames.createChild()
  1929. };
  1930. fn();
  1931. this.scope = oldScope;
  1932. }
  1933. detectStrictMode(statements) {
  1934. const isStrict =
  1935. statements.length >= 1 &&
  1936. statements[0].type === "ExpressionStatement" &&
  1937. statements[0].expression.type === "Literal" &&
  1938. statements[0].expression.value === "use strict";
  1939. if (isStrict) {
  1940. this.scope.isStrict = true;
  1941. }
  1942. }
  1943. enterPatterns(patterns, onIdent) {
  1944. for (const pattern of patterns) {
  1945. if (typeof pattern !== "string") {
  1946. this.enterPattern(pattern, onIdent);
  1947. } else if (pattern) {
  1948. onIdent(pattern);
  1949. }
  1950. }
  1951. }
  1952. enterPattern(pattern, onIdent) {
  1953. if (!pattern) return;
  1954. switch (pattern.type) {
  1955. case "ArrayPattern":
  1956. this.enterArrayPattern(pattern, onIdent);
  1957. break;
  1958. case "AssignmentPattern":
  1959. this.enterAssignmentPattern(pattern, onIdent);
  1960. break;
  1961. case "Identifier":
  1962. this.enterIdentifier(pattern, onIdent);
  1963. break;
  1964. case "ObjectPattern":
  1965. this.enterObjectPattern(pattern, onIdent);
  1966. break;
  1967. case "RestElement":
  1968. this.enterRestElement(pattern, onIdent);
  1969. break;
  1970. case "Property":
  1971. this.enterPattern(pattern.value, onIdent);
  1972. break;
  1973. }
  1974. }
  1975. enterIdentifier(pattern, onIdent) {
  1976. onIdent(pattern.name, pattern);
  1977. }
  1978. enterObjectPattern(pattern, onIdent) {
  1979. for (
  1980. let propIndex = 0, len = pattern.properties.length;
  1981. propIndex < len;
  1982. propIndex++
  1983. ) {
  1984. const prop = pattern.properties[propIndex];
  1985. this.enterPattern(prop, onIdent);
  1986. }
  1987. }
  1988. enterArrayPattern(pattern, onIdent) {
  1989. for (
  1990. let elementIndex = 0, len = pattern.elements.length;
  1991. elementIndex < len;
  1992. elementIndex++
  1993. ) {
  1994. const element = pattern.elements[elementIndex];
  1995. this.enterPattern(element, onIdent);
  1996. }
  1997. }
  1998. enterRestElement(pattern, onIdent) {
  1999. this.enterPattern(pattern.argument, onIdent);
  2000. }
  2001. enterAssignmentPattern(pattern, onIdent) {
  2002. this.enterPattern(pattern.left, onIdent);
  2003. }
  2004. evaluateExpression(expression) {
  2005. try {
  2006. const hook = this.hooks.evaluate.get(expression.type);
  2007. if (hook !== undefined) {
  2008. const result = hook.call(expression);
  2009. if (result !== undefined) {
  2010. if (result) {
  2011. result.setExpression(expression);
  2012. }
  2013. return result;
  2014. }
  2015. }
  2016. } catch (e) {
  2017. console.warn(e);
  2018. // ignore error
  2019. }
  2020. return new BasicEvaluatedExpression()
  2021. .setRange(expression.range)
  2022. .setExpression(expression);
  2023. }
  2024. parseString(expression) {
  2025. switch (expression.type) {
  2026. case "BinaryExpression":
  2027. if (expression.operator === "+") {
  2028. return (
  2029. this.parseString(expression.left) +
  2030. this.parseString(expression.right)
  2031. );
  2032. }
  2033. break;
  2034. case "Literal":
  2035. return expression.value + "";
  2036. }
  2037. throw new Error(
  2038. expression.type + " is not supported as parameter for require"
  2039. );
  2040. }
  2041. parseCalculatedString(expression) {
  2042. switch (expression.type) {
  2043. case "BinaryExpression":
  2044. if (expression.operator === "+") {
  2045. const left = this.parseCalculatedString(expression.left);
  2046. const right = this.parseCalculatedString(expression.right);
  2047. if (left.code) {
  2048. return {
  2049. range: left.range,
  2050. value: left.value,
  2051. code: true,
  2052. conditional: false
  2053. };
  2054. } else if (right.code) {
  2055. return {
  2056. range: [
  2057. left.range[0],
  2058. right.range ? right.range[1] : left.range[1]
  2059. ],
  2060. value: left.value + right.value,
  2061. code: true,
  2062. conditional: false
  2063. };
  2064. } else {
  2065. return {
  2066. range: [left.range[0], right.range[1]],
  2067. value: left.value + right.value,
  2068. code: false,
  2069. conditional: false
  2070. };
  2071. }
  2072. }
  2073. break;
  2074. case "ConditionalExpression": {
  2075. const consequent = this.parseCalculatedString(expression.consequent);
  2076. const alternate = this.parseCalculatedString(expression.alternate);
  2077. const items = [];
  2078. if (consequent.conditional) {
  2079. items.push(...consequent.conditional);
  2080. } else if (!consequent.code) {
  2081. items.push(consequent);
  2082. } else {
  2083. break;
  2084. }
  2085. if (alternate.conditional) {
  2086. items.push(...alternate.conditional);
  2087. } else if (!alternate.code) {
  2088. items.push(alternate);
  2089. } else {
  2090. break;
  2091. }
  2092. return {
  2093. range: undefined,
  2094. value: "",
  2095. code: true,
  2096. conditional: items
  2097. };
  2098. }
  2099. case "Literal":
  2100. return {
  2101. range: expression.range,
  2102. value: expression.value + "",
  2103. code: false,
  2104. conditional: false
  2105. };
  2106. }
  2107. return {
  2108. range: undefined,
  2109. value: "",
  2110. code: true,
  2111. conditional: false
  2112. };
  2113. }
  2114. parse(source, initialState) {
  2115. let ast;
  2116. let comments;
  2117. if (typeof source === "object" && source !== null) {
  2118. ast = source;
  2119. comments = source.comments;
  2120. } else {
  2121. comments = [];
  2122. ast = Parser.parse(source, {
  2123. sourceType: this.sourceType,
  2124. onComment: comments
  2125. });
  2126. }
  2127. const oldScope = this.scope;
  2128. const oldState = this.state;
  2129. const oldComments = this.comments;
  2130. this.scope = {
  2131. topLevelScope: true,
  2132. inTry: false,
  2133. inShorthand: false,
  2134. isStrict: false,
  2135. definitions: new StackedSetMap(),
  2136. renames: new StackedSetMap()
  2137. };
  2138. const state = (this.state = initialState || {});
  2139. this.comments = comments;
  2140. if (this.hooks.program.call(ast, comments) === undefined) {
  2141. this.detectStrictMode(ast.body);
  2142. this.prewalkStatements(ast.body);
  2143. this.blockPrewalkStatements(ast.body);
  2144. this.walkStatements(ast.body);
  2145. }
  2146. this.scope = oldScope;
  2147. this.state = oldState;
  2148. this.comments = oldComments;
  2149. return state;
  2150. }
  2151. evaluate(source) {
  2152. const ast = Parser.parse("(" + source + ")", {
  2153. sourceType: this.sourceType,
  2154. locations: false
  2155. });
  2156. // TODO(https://github.com/acornjs/acorn/issues/741)
  2157. // @ts-ignore
  2158. if (ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement") {
  2159. throw new Error("evaluate: Source is not a expression");
  2160. }
  2161. // TODO(https://github.com/acornjs/acorn/issues/741)
  2162. // @ts-ignore
  2163. return this.evaluateExpression(ast.body[0].expression);
  2164. }
  2165. getComments(range) {
  2166. return this.comments.filter(
  2167. comment => comment.range[0] >= range[0] && comment.range[1] <= range[1]
  2168. );
  2169. }
  2170. parseCommentOptions(range) {
  2171. const comments = this.getComments(range);
  2172. if (comments.length === 0) {
  2173. return EMPTY_COMMENT_OPTIONS;
  2174. }
  2175. let options = {};
  2176. let errors = [];
  2177. for (const comment of comments) {
  2178. const { value } = comment;
  2179. if (value && webpackCommentRegExp.test(value)) {
  2180. // try compile only if webpack options comment is present
  2181. try {
  2182. const val = vm.runInNewContext(`(function(){return {${value}};})()`);
  2183. Object.assign(options, val);
  2184. } catch (e) {
  2185. e.comment = comment;
  2186. errors.push(e);
  2187. }
  2188. }
  2189. }
  2190. return { options, errors };
  2191. }
  2192. getNameForExpression(expression) {
  2193. let expr = expression;
  2194. const exprName = [];
  2195. while (
  2196. expr.type === "MemberExpression" &&
  2197. expr.property.type === (expr.computed ? "Literal" : "Identifier")
  2198. ) {
  2199. exprName.push(expr.computed ? expr.property.value : expr.property.name);
  2200. expr = expr.object;
  2201. }
  2202. let free;
  2203. if (expr.type === "Identifier") {
  2204. free = !this.scope.definitions.has(expr.name);
  2205. exprName.push(this.scope.renames.get(expr.name) || expr.name);
  2206. } else if (
  2207. expr.type === "ThisExpression" &&
  2208. this.scope.renames.get("this")
  2209. ) {
  2210. free = true;
  2211. exprName.push(this.scope.renames.get("this"));
  2212. } else if (expr.type === "ThisExpression") {
  2213. free = this.scope.topLevelScope;
  2214. exprName.push("this");
  2215. } else {
  2216. return null;
  2217. }
  2218. let prefix = "";
  2219. for (let i = exprName.length - 1; i >= 2; i--) {
  2220. prefix += exprName[i] + ".";
  2221. }
  2222. if (exprName.length > 1) {
  2223. prefix += exprName[1];
  2224. }
  2225. const name = prefix ? prefix + "." + exprName[0] : exprName[0];
  2226. const nameGeneral = prefix;
  2227. return {
  2228. name,
  2229. nameGeneral,
  2230. free
  2231. };
  2232. }
  2233. static parse(code, options) {
  2234. const type = options ? options.sourceType : "module";
  2235. const parserOptions = Object.assign(
  2236. Object.create(null),
  2237. defaultParserOptions,
  2238. options
  2239. );
  2240. if (type === "auto") {
  2241. parserOptions.sourceType = "module";
  2242. } else if (parserOptions.sourceType === "script") {
  2243. parserOptions.allowReturnOutsideFunction = true;
  2244. }
  2245. let ast;
  2246. let error;
  2247. let threw = false;
  2248. try {
  2249. ast = acornParser.parse(code, parserOptions);
  2250. } catch (e) {
  2251. error = e;
  2252. threw = true;
  2253. }
  2254. if (threw && type === "auto") {
  2255. parserOptions.sourceType = "script";
  2256. parserOptions.allowReturnOutsideFunction = true;
  2257. if (Array.isArray(parserOptions.onComment)) {
  2258. parserOptions.onComment.length = 0;
  2259. }
  2260. try {
  2261. ast = acornParser.parse(code, parserOptions);
  2262. threw = false;
  2263. } catch (e) {
  2264. threw = true;
  2265. }
  2266. }
  2267. if (threw) {
  2268. throw error;
  2269. }
  2270. return ast;
  2271. }
  2272. }
  2273. // TODO remove in webpack 5
  2274. Object.defineProperty(Parser.prototype, "getCommentOptions", {
  2275. configurable: false,
  2276. value: util.deprecate(
  2277. /**
  2278. * @deprecated
  2279. * @param {TODO} range Range
  2280. * @returns {void}
  2281. * @this {Parser}
  2282. */
  2283. function(range) {
  2284. return this.parseCommentOptions(range).options;
  2285. },
  2286. "Parser.getCommentOptions: Use Parser.parseCommentOptions(range) instead"
  2287. )
  2288. });
  2289. module.exports = Parser;