summaryrefslogtreecommitdiff
path: root/node_modules/webpack/lib/javascript/JavascriptParser.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/webpack/lib/javascript/JavascriptParser.js')
-rw-r--r--node_modules/webpack/lib/javascript/JavascriptParser.js4527
1 files changed, 4527 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/javascript/JavascriptParser.js b/node_modules/webpack/lib/javascript/JavascriptParser.js
new file mode 100644
index 00000000..827e9bd4
--- /dev/null
+++ b/node_modules/webpack/lib/javascript/JavascriptParser.js
@@ -0,0 +1,4527 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { Parser: AcornParser } = require("acorn");
+const { importAssertions } = require("acorn-import-assertions");
+const { SyncBailHook, HookMap } = require("tapable");
+const vm = require("vm");
+const Parser = require("../Parser");
+const StackedMap = require("../util/StackedMap");
+const binarySearchBounds = require("../util/binarySearchBounds");
+const memoize = require("../util/memoize");
+const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
+
+/** @typedef {import("acorn").Options} AcornOptions */
+/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */
+/** @typedef {import("estree").BinaryExpression} BinaryExpression */
+/** @typedef {import("estree").BlockStatement} BlockStatement */
+/** @typedef {import("estree").SequenceExpression} SequenceExpression */
+/** @typedef {import("estree").CallExpression} CallExpression */
+/** @typedef {import("estree").BaseCallExpression} BaseCallExpression */
+/** @typedef {import("estree").StaticBlock} StaticBlock */
+/** @typedef {import("estree").ImportExpression} ImportExpression */
+/** @typedef {import("estree").ClassDeclaration} ClassDeclaration */
+/** @typedef {import("estree").ForStatement} ForStatement */
+/** @typedef {import("estree").SwitchStatement} SwitchStatement */
+/** @typedef {import("estree").ExportNamedDeclaration} ExportNamedDeclaration */
+/** @typedef {import("estree").ClassExpression} ClassExpression */
+/** @typedef {import("estree").Comment} Comment */
+/** @typedef {import("estree").ConditionalExpression} ConditionalExpression */
+/** @typedef {import("estree").Declaration} Declaration */
+/** @typedef {import("estree").PrivateIdentifier} PrivateIdentifier */
+/** @typedef {import("estree").PropertyDefinition} PropertyDefinition */
+/** @typedef {import("estree").Expression} Expression */
+/** @typedef {import("estree").Identifier} Identifier */
+/** @typedef {import("estree").VariableDeclaration} VariableDeclaration */
+/** @typedef {import("estree").IfStatement} IfStatement */
+/** @typedef {import("estree").LabeledStatement} LabeledStatement */
+/** @typedef {import("estree").Literal} Literal */
+/** @typedef {import("estree").LogicalExpression} LogicalExpression */
+/** @typedef {import("estree").ChainExpression} ChainExpression */
+/** @typedef {import("estree").MemberExpression} MemberExpression */
+/** @typedef {import("estree").YieldExpression} YieldExpression */
+/** @typedef {import("estree").MetaProperty} MetaProperty */
+/** @typedef {import("estree").Property} Property */
+/** @typedef {import("estree").AssignmentPattern} AssignmentPattern */
+/** @typedef {import("estree").ChainElement} ChainElement */
+/** @typedef {import("estree").Pattern} Pattern */
+/** @typedef {import("estree").UpdateExpression} UpdateExpression */
+/** @typedef {import("estree").ObjectExpression} ObjectExpression */
+/** @typedef {import("estree").UnaryExpression} UnaryExpression */
+/** @typedef {import("estree").ArrayExpression} ArrayExpression */
+/** @typedef {import("estree").ArrayPattern} ArrayPattern */
+/** @typedef {import("estree").AwaitExpression} AwaitExpression */
+/** @typedef {import("estree").ThisExpression} ThisExpression */
+/** @typedef {import("estree").RestElement} RestElement */
+/** @typedef {import("estree").ObjectPattern} ObjectPattern */
+/** @typedef {import("estree").SwitchCase} SwitchCase */
+/** @typedef {import("estree").CatchClause} CatchClause */
+/** @typedef {import("estree").VariableDeclarator} VariableDeclarator */
+/** @typedef {import("estree").ForInStatement} ForInStatement */
+/** @typedef {import("estree").ForOfStatement} ForOfStatement */
+/** @typedef {import("estree").ReturnStatement} ReturnStatement */
+/** @typedef {import("estree").WithStatement} WithStatement */
+/** @typedef {import("estree").ThrowStatement} ThrowStatement */
+/** @typedef {import("estree").MethodDefinition} MethodDefinition */
+/** @typedef {import("estree").ModuleDeclaration} ModuleDeclaration */
+/** @typedef {import("estree").NewExpression} NewExpression */
+/** @typedef {import("estree").SpreadElement} SpreadElement */
+/** @typedef {import("estree").FunctionExpression} FunctionExpression */
+/** @typedef {import("estree").WhileStatement} WhileStatement */
+/** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */
+/** @typedef {import("estree").ExpressionStatement} ExpressionStatement */
+/** @typedef {import("estree").FunctionDeclaration} FunctionDeclaration */
+/** @typedef {import("estree").DoWhileStatement} DoWhileStatement */
+/** @typedef {import("estree").TryStatement} TryStatement */
+/** @typedef {import("estree").Node} AnyNode */
+/** @typedef {import("estree").Program} Program */
+/** @typedef {import("estree").Directive} Directive */
+/** @typedef {import("estree").Statement} Statement */
+/** @typedef {import("estree").ImportDeclaration} ImportDeclaration */
+/** @typedef {import("estree").ExportDefaultDeclaration} ExportDefaultDeclaration */
+/** @typedef {import("estree").ExportAllDeclaration} ExportAllDeclaration */
+/** @typedef {import("estree").Super} Super */
+/** @typedef {import("estree").TaggedTemplateExpression} TaggedTemplateExpression */
+/** @typedef {import("estree").TemplateLiteral} TemplateLiteral */
+/** @typedef {Record<string, any>} Assertions */
+/** @template T @typedef {import("tapable").AsArray<T>} AsArray<T> */
+/** @typedef {import("../Parser").ParserState} ParserState */
+/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
+/** @typedef {{declaredScope: ScopeInfo, freeName: string | true, tagInfo: TagInfo | undefined}} VariableInfoInterface */
+/** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[] }} GetInfoResult */
+
+const EMPTY_ARRAY = [];
+const ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = 0b01;
+const ALLOWED_MEMBER_TYPES_EXPRESSION = 0b10;
+const ALLOWED_MEMBER_TYPES_ALL = 0b11;
+
+// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
+
+const parser = AcornParser.extend(importAssertions);
+
+class VariableInfo {
+ /**
+ * @param {ScopeInfo} declaredScope scope in which the variable is declared
+ * @param {string | true | undefined} freeName which free name the variable aliases, or true when none
+ * @param {TagInfo | undefined} tagInfo info about tags
+ */
+ constructor(declaredScope, freeName, tagInfo) {
+ this.declaredScope = declaredScope;
+ this.freeName = freeName;
+ this.tagInfo = tagInfo;
+ }
+}
+
+/** @typedef {string | ScopeInfo | VariableInfo} ExportedVariableInfo */
+/** @typedef {Literal | string | null | undefined} ImportSource */
+/** @typedef {Omit<AcornOptions, "sourceType" | "ecmaVersion"> & { sourceType: "module" | "script" | "auto", ecmaVersion?: AcornOptions["ecmaVersion"] }} ParseOptions */
+
+/**
+ * @typedef {Object} TagInfo
+ * @property {any} tag
+ * @property {any} data
+ * @property {TagInfo | undefined} next
+ */
+
+/**
+ * @typedef {Object} ScopeInfo
+ * @property {StackedMap<string, VariableInfo | ScopeInfo>} definitions
+ * @property {boolean | "arrow"} topLevelScope
+ * @property {boolean | string} inShorthand
+ * @property {boolean} inTaggedTemplateTag
+ * @property {boolean} inTry
+ * @property {boolean} isStrict
+ * @property {boolean} isAsmJs
+ */
+
+/** @typedef {[number, number]} Range */
+
+/**
+ * Helper function for joining two ranges into a single range. This is useful
+ * when working with AST nodes, as it allows you to combine the ranges of child nodes
+ * to create the range of the _parent node_.
+ *
+ * @param {[number, number]} startRange start range to join
+ * @param {[number, number]} endRange end range to join
+ * @returns {[number, number]} joined range
+ *
+ * @example
+ * ```js
+ * const startRange = [0, 5];
+ * const endRange = [10, 15];
+ * const joinedRange = joinRanges(startRange, endRange);
+ * console.log(joinedRange); // [0, 15]
+ * ```
+ *
+ */
+const joinRanges = (startRange, endRange) => {
+ if (!endRange) return startRange;
+ if (!startRange) return endRange;
+ return [startRange[0], endRange[1]];
+};
+
+/**
+ * Helper function used to generate a string representation of a
+ * [member expression](https://github.com/estree/estree/blob/master/es5.md#memberexpression).
+ *
+ * @param {string} object object to name
+ * @param {string[]} membersReversed reversed list of members
+ * @returns {string} member expression as a string
+ * @example
+ * ```js
+ * const membersReversed = ["property1", "property2", "property3"]; // Members parsed from the AST
+ * const name = objectAndMembersToName("myObject", membersReversed);
+ *
+ * console.log(name); // "myObject.property1.property2.property3"
+ * ```
+ *
+ */
+const objectAndMembersToName = (object, membersReversed) => {
+ let name = object;
+ for (let i = membersReversed.length - 1; i >= 0; i--) {
+ name = name + "." + membersReversed[i];
+ }
+ return name;
+};
+
+/**
+ * Grabs the name of a given expression and returns it as a string or undefined. Has particular
+ * handling for [Identifiers](https://github.com/estree/estree/blob/master/es5.md#identifier),
+ * [ThisExpressions](https://github.com/estree/estree/blob/master/es5.md#identifier), and
+ * [MetaProperties](https://github.com/estree/estree/blob/master/es2015.md#metaproperty) which is
+ * specifically for handling the `new.target` meta property.
+ *
+ * @param {Expression | Super} expression expression
+ * @returns {string | "this" | undefined} name or variable info
+ */
+const getRootName = expression => {
+ switch (expression.type) {
+ case "Identifier":
+ return expression.name;
+ case "ThisExpression":
+ return "this";
+ case "MetaProperty":
+ return `${expression.meta.name}.${expression.property.name}`;
+ default:
+ return undefined;
+ }
+};
+
+/** @type {AcornOptions} */
+const defaultParserOptions = {
+ ranges: true,
+ locations: true,
+ ecmaVersion: "latest",
+ sourceType: "module",
+ // https://github.com/tc39/proposal-hashbang
+ allowHashBang: true,
+ onComment: null
+};
+
+// regexp to match at least one "magic comment"
+const webpackCommentRegExp = new RegExp(/(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/);
+
+const EMPTY_COMMENT_OPTIONS = {
+ options: null,
+ errors: null
+};
+
+class JavascriptParser extends Parser {
+ /**
+ * @param {"module" | "script" | "auto"} sourceType default source type
+ */
+ constructor(sourceType = "auto") {
+ super();
+ this.hooks = Object.freeze({
+ /** @type {HookMap<SyncBailHook<[UnaryExpression], BasicEvaluatedExpression | undefined | null>>} */
+ evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])),
+ /** @type {HookMap<SyncBailHook<[Expression], BasicEvaluatedExpression | undefined | null>>} */
+ evaluate: new HookMap(() => new SyncBailHook(["expression"])),
+ /** @type {HookMap<SyncBailHook<[Identifier | ThisExpression | MemberExpression | MetaProperty], BasicEvaluatedExpression | undefined | null>>} */
+ evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])),
+ /** @type {HookMap<SyncBailHook<[Identifier | ThisExpression | MemberExpression], BasicEvaluatedExpression | undefined | null>>} */
+ evaluateDefinedIdentifier: new HookMap(
+ () => new SyncBailHook(["expression"])
+ ),
+ /** @type {HookMap<SyncBailHook<[NewExpression], BasicEvaluatedExpression | undefined | null>>} */
+ evaluateNewExpression: new HookMap(
+ () => new SyncBailHook(["expression"])
+ ),
+ /** @type {HookMap<SyncBailHook<[CallExpression], BasicEvaluatedExpression | undefined | null>>} */
+ evaluateCallExpression: new HookMap(
+ () => new SyncBailHook(["expression"])
+ ),
+ /** @type {HookMap<SyncBailHook<[CallExpression, BasicEvaluatedExpression | undefined], BasicEvaluatedExpression | undefined | null>>} */
+ evaluateCallExpressionMember: new HookMap(
+ () => new SyncBailHook(["expression", "param"])
+ ),
+ /** @type {HookMap<SyncBailHook<[Expression | Declaration | PrivateIdentifier, number], boolean | void>>} */
+ isPure: new HookMap(
+ () => new SyncBailHook(["expression", "commentsStartPosition"])
+ ),
+ /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */
+ preStatement: new SyncBailHook(["statement"]),
+
+ /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */
+ blockPreStatement: new SyncBailHook(["declaration"]),
+ /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */
+ statement: new SyncBailHook(["statement"]),
+ /** @type {SyncBailHook<[IfStatement], boolean | void>} */
+ statementIf: new SyncBailHook(["statement"]),
+ /** @type {SyncBailHook<[Expression, ClassExpression | ClassDeclaration], boolean | void>} */
+ classExtendsExpression: new SyncBailHook([
+ "expression",
+ "classDefinition"
+ ]),
+ /** @type {SyncBailHook<[MethodDefinition | PropertyDefinition | StaticBlock, ClassExpression | ClassDeclaration], boolean | void>} */
+ classBodyElement: new SyncBailHook(["element", "classDefinition"]),
+ /** @type {SyncBailHook<[Expression, MethodDefinition | PropertyDefinition, ClassExpression | ClassDeclaration], boolean | void>} */
+ classBodyValue: new SyncBailHook([
+ "expression",
+ "element",
+ "classDefinition"
+ ]),
+ /** @type {HookMap<SyncBailHook<[LabeledStatement], boolean | void>>} */
+ label: new HookMap(() => new SyncBailHook(["statement"])),
+ /** @type {SyncBailHook<[ImportDeclaration, ImportSource], boolean | void>} */
+ import: new SyncBailHook(["statement", "source"]),
+ /** @type {SyncBailHook<[ImportDeclaration, ImportSource, string, string], boolean | void>} */
+ importSpecifier: new SyncBailHook([
+ "statement",
+ "source",
+ "exportName",
+ "identifierName"
+ ]),
+ /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration], boolean | void>} */
+ export: new SyncBailHook(["statement"]),
+ /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, ImportSource], boolean | void>} */
+ exportImport: new SyncBailHook(["statement", "source"]),
+ /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, Declaration], boolean | void>} */
+ exportDeclaration: new SyncBailHook(["statement", "declaration"]),
+ /** @type {SyncBailHook<[ExportDefaultDeclaration, Declaration], boolean | void>} */
+ exportExpression: new SyncBailHook(["statement", "declaration"]),
+ /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, string, string, number | undefined], boolean | void>} */
+ exportSpecifier: new SyncBailHook([
+ "statement",
+ "identifierName",
+ "exportName",
+ "index"
+ ]),
+ /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, ImportSource, string, string, number | undefined], boolean | void>} */
+ exportImportSpecifier: new SyncBailHook([
+ "statement",
+ "source",
+ "identifierName",
+ "exportName",
+ "index"
+ ]),
+ /** @type {SyncBailHook<[VariableDeclarator, Statement], boolean | void>} */
+ preDeclarator: new SyncBailHook(["declarator", "statement"]),
+ /** @type {SyncBailHook<[VariableDeclarator, Statement], boolean | void>} */
+ declarator: new SyncBailHook(["declarator", "statement"]),
+ /** @type {HookMap<SyncBailHook<[Declaration], boolean | void>>} */
+ varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])),
+ /** @type {HookMap<SyncBailHook<[Declaration], boolean | void>>} */
+ varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])),
+ /** @type {HookMap<SyncBailHook<[Declaration], boolean | void>>} */
+ varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])),
+ /** @type {HookMap<SyncBailHook<[Declaration], boolean | void>>} */
+ varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])),
+ /** @type {HookMap<SyncBailHook<[Identifier], boolean | void>>} */
+ pattern: new HookMap(() => new SyncBailHook(["pattern"])),
+ /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
+ canRename: new HookMap(() => new SyncBailHook(["initExpression"])),
+ /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
+ rename: new HookMap(() => new SyncBailHook(["initExpression"])),
+ /** @type {HookMap<SyncBailHook<[AssignmentExpression], boolean | void>>} */
+ assign: new HookMap(() => new SyncBailHook(["expression"])),
+ /** @type {HookMap<SyncBailHook<[AssignmentExpression, string[]], boolean | void>>} */
+ assignMemberChain: new HookMap(
+ () => new SyncBailHook(["expression", "members"])
+ ),
+ /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
+ typeof: new HookMap(() => new SyncBailHook(["expression"])),
+ /** @type {SyncBailHook<[ImportExpression], boolean | void>} */
+ importCall: new SyncBailHook(["expression"]),
+ /** @type {SyncBailHook<[Expression], boolean | void>} */
+ topLevelAwait: new SyncBailHook(["expression"]),
+ /** @type {HookMap<SyncBailHook<[CallExpression], boolean | void>>} */
+ call: new HookMap(() => new SyncBailHook(["expression"])),
+ /** Something like "a.b()" */
+ /** @type {HookMap<SyncBailHook<[CallExpression, string[], boolean[], Range[]], boolean | void>>} */
+ callMemberChain: new HookMap(
+ () =>
+ new SyncBailHook([
+ "expression",
+ "members",
+ "membersOptionals",
+ "memberRanges"
+ ])
+ ),
+ /** Something like "a.b().c.d" */
+ /** @type {HookMap<SyncBailHook<[Expression, string[], CallExpression, string[], Range[]], boolean | void>>} */
+ memberChainOfCallMemberChain: new HookMap(
+ () =>
+ new SyncBailHook([
+ "expression",
+ "calleeMembers",
+ "callExpression",
+ "members",
+ "memberRanges"
+ ])
+ ),
+ /** Something like "a.b().c.d()"" */
+ /** @type {HookMap<SyncBailHook<[CallExpression, string[], CallExpression, string[], Range[]], boolean | void>>} */
+ callMemberChainOfCallMemberChain: new HookMap(
+ () =>
+ new SyncBailHook([
+ "expression",
+ "calleeMembers",
+ "innerCallExpression",
+ "members",
+ "memberRanges"
+ ])
+ ),
+ /** @type {SyncBailHook<[ChainExpression], boolean | void>} */
+ optionalChaining: new SyncBailHook(["optionalChaining"]),
+ /** @type {HookMap<SyncBailHook<[NewExpression], boolean | void>>} */
+ new: new HookMap(() => new SyncBailHook(["expression"])),
+ /** @type {SyncBailHook<[BinaryExpression], boolean | void>} */
+ binaryExpression: new SyncBailHook(["binaryExpression"]),
+ /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
+ expression: new HookMap(() => new SyncBailHook(["expression"])),
+ /** @type {HookMap<SyncBailHook<[MemberExpression, string[], boolean[], Range[]], boolean | void>>} */
+ expressionMemberChain: new HookMap(
+ () =>
+ new SyncBailHook([
+ "expression",
+ "members",
+ "membersOptionals",
+ "memberRanges"
+ ])
+ ),
+ /** @type {HookMap<SyncBailHook<[MemberExpression, string[]], boolean | void>>} */
+ unhandledExpressionMemberChain: new HookMap(
+ () => new SyncBailHook(["expression", "members"])
+ ),
+ /** @type {SyncBailHook<[ConditionalExpression], boolean | void>} */
+ expressionConditionalOperator: new SyncBailHook(["expression"]),
+ /** @type {SyncBailHook<[LogicalExpression], boolean | void>} */
+ expressionLogicalOperator: new SyncBailHook(["expression"]),
+ /** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */
+ program: new SyncBailHook(["ast", "comments"]),
+ /** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */
+ finish: new SyncBailHook(["ast", "comments"])
+ });
+ this.sourceType = sourceType;
+ /** @type {ScopeInfo} */
+ this.scope = undefined;
+ /** @type {ParserState} */
+ this.state = undefined;
+ this.comments = undefined;
+ this.semicolons = undefined;
+ /** @type {(Statement | ModuleDeclaration | Expression)[]} */
+ this.statementPath = undefined;
+ /** @type {Statement | ModuleDeclaration | Expression | undefined} */
+ this.prevStatement = undefined;
+ /** @type {WeakMap<Expression, Set<string>>} */
+ this.destructuringAssignmentProperties = undefined;
+ this.currentTagData = undefined;
+ this._initializeEvaluating();
+ }
+
+ _initializeEvaluating() {
+ this.hooks.evaluate.for("Literal").tap("JavascriptParser", _expr => {
+ const expr = /** @type {Literal} */ (_expr);
+
+ switch (typeof expr.value) {
+ case "number":
+ return new BasicEvaluatedExpression()
+ .setNumber(expr.value)
+ .setRange(/** @type {Range} */ (expr.range));
+ case "bigint":
+ return new BasicEvaluatedExpression()
+ .setBigInt(expr.value)
+ .setRange(/** @type {Range} */ (expr.range));
+ case "string":
+ return new BasicEvaluatedExpression()
+ .setString(expr.value)
+ .setRange(/** @type {Range} */ (expr.range));
+ case "boolean":
+ return new BasicEvaluatedExpression()
+ .setBoolean(expr.value)
+ .setRange(/** @type {Range} */ (expr.range));
+ }
+ if (expr.value === null) {
+ return new BasicEvaluatedExpression()
+ .setNull()
+ .setRange(/** @type {Range} */ (expr.range));
+ }
+ if (expr.value instanceof RegExp) {
+ return new BasicEvaluatedExpression()
+ .setRegExp(expr.value)
+ .setRange(/** @type {Range} */ (expr.range));
+ }
+ });
+ this.hooks.evaluate.for("NewExpression").tap("JavascriptParser", _expr => {
+ const expr = /** @type {NewExpression} */ (_expr);
+ const callee = expr.callee;
+ if (callee.type !== "Identifier") return;
+ if (callee.name !== "RegExp") {
+ return this.callHooksForName(
+ this.hooks.evaluateNewExpression,
+ callee.name,
+ expr
+ );
+ } else if (
+ expr.arguments.length > 2 ||
+ this.getVariableInfo("RegExp") !== "RegExp"
+ )
+ return;
+
+ let regExp, flags;
+ const arg1 = expr.arguments[0];
+
+ if (arg1) {
+ if (arg1.type === "SpreadElement") return;
+
+ const evaluatedRegExp = this.evaluateExpression(arg1);
+
+ if (!evaluatedRegExp) return;
+
+ regExp = evaluatedRegExp.asString();
+
+ if (!regExp) return;
+ } else {
+ return new BasicEvaluatedExpression()
+ .setRegExp(new RegExp(""))
+ .setRange(/** @type {Range} */ (expr.range));
+ }
+
+ const arg2 = expr.arguments[1];
+
+ if (arg2) {
+ if (arg2.type === "SpreadElement") return;
+
+ const evaluatedFlags = this.evaluateExpression(arg2);
+
+ if (!evaluatedFlags) return;
+
+ if (!evaluatedFlags.isUndefined()) {
+ flags = evaluatedFlags.asString();
+
+ if (
+ flags === undefined ||
+ !BasicEvaluatedExpression.isValidRegExpFlags(flags)
+ )
+ return;
+ }
+ }
+
+ return new BasicEvaluatedExpression()
+ .setRegExp(flags ? new RegExp(regExp, flags) : new RegExp(regExp))
+ .setRange(/** @type {Range} */ (expr.range));
+ });
+ this.hooks.evaluate
+ .for("LogicalExpression")
+ .tap("JavascriptParser", _expr => {
+ const expr = /** @type {LogicalExpression} */ (_expr);
+
+ const left = this.evaluateExpression(expr.left);
+ let returnRight = false;
+ /** @type {boolean|undefined} */
+ let allowedRight;
+ if (expr.operator === "&&") {
+ const leftAsBool = left.asBool();
+ if (leftAsBool === false) return left.setRange(expr.range);
+ returnRight = leftAsBool === true;
+ allowedRight = false;
+ } else if (expr.operator === "||") {
+ const leftAsBool = left.asBool();
+ if (leftAsBool === true) return left.setRange(expr.range);
+ returnRight = leftAsBool === false;
+ allowedRight = true;
+ } else if (expr.operator === "??") {
+ const leftAsNullish = left.asNullish();
+ if (leftAsNullish === false) return left.setRange(expr.range);
+ if (leftAsNullish !== true) return;
+ returnRight = true;
+ } else return;
+ const right = this.evaluateExpression(expr.right);
+ if (returnRight) {
+ if (left.couldHaveSideEffects()) right.setSideEffects();
+ return right.setRange(expr.range);
+ }
+
+ const asBool = right.asBool();
+
+ if (allowedRight === true && asBool === true) {
+ return new BasicEvaluatedExpression()
+ .setRange(expr.range)
+ .setTruthy();
+ } else if (allowedRight === false && asBool === false) {
+ return new BasicEvaluatedExpression().setRange(expr.range).setFalsy();
+ }
+ });
+
+ /**
+ * In simple logical cases, we can use valueAsExpression to assist us in evaluating the expression on
+ * either side of a [BinaryExpression](https://github.com/estree/estree/blob/master/es5.md#binaryexpression).
+ * This supports scenarios in webpack like conditionally `import()`'ing modules based on some simple evaluation:
+ *
+ * ```js
+ * if (1 === 3) {
+ * import("./moduleA"); // webpack will auto evaluate this and not import the modules
+ * }
+ * ```
+ *
+ * Additional scenarios include evaluation of strings inside of dynamic import statements:
+ *
+ * ```js
+ * const foo = "foo";
+ * const bar = "bar";
+ *
+ * import("./" + foo + bar); // webpack will auto evaluate this into import("./foobar")
+ * ```
+ * @param {boolean | number | BigInt | string} value the value to convert to an expression
+ * @param {BinaryExpression | UnaryExpression} expr the expression being evaluated
+ * @param {boolean} sideEffects whether the expression has side effects
+ * @returns {BasicEvaluatedExpression | undefined} the evaluated expression
+ * @example
+ *
+ * ```js
+ * const binaryExpr = new BinaryExpression("+",
+ * { type: "Literal", value: 2 },
+ * { type: "Literal", value: 3 }
+ * );
+ *
+ * const leftValue = 2;
+ * const rightValue = 3;
+ *
+ * const leftExpr = valueAsExpression(leftValue, binaryExpr.left, false);
+ * const rightExpr = valueAsExpression(rightValue, binaryExpr.right, false);
+ * const result = new BasicEvaluatedExpression()
+ * .setNumber(leftExpr.number + rightExpr.number)
+ * .setRange(binaryExpr.range);
+ *
+ * console.log(result.number); // Output: 5
+ * ```
+ */
+ const valueAsExpression = (value, expr, sideEffects) => {
+ switch (typeof value) {
+ case "boolean":
+ return new BasicEvaluatedExpression()
+ .setBoolean(value)
+ .setSideEffects(sideEffects)
+ .setRange(/** @type {Range} */ (expr.range));
+ case "number":
+ return new BasicEvaluatedExpression()
+ .setNumber(value)
+ .setSideEffects(sideEffects)
+ .setRange(/** @type {Range} */ (expr.range));
+ case "bigint":
+ return new BasicEvaluatedExpression()
+ .setBigInt(value)
+ .setSideEffects(sideEffects)
+ .setRange(/** @type {Range} */ (expr.range));
+ case "string":
+ return new BasicEvaluatedExpression()
+ .setString(value)
+ .setSideEffects(sideEffects)
+ .setRange(/** @type {Range} */ (expr.range));
+ }
+ };
+
+ this.hooks.evaluate
+ .for("BinaryExpression")
+ .tap("JavascriptParser", _expr => {
+ const expr = /** @type {BinaryExpression} */ (_expr);
+
+ /**
+ * Evaluates a binary expression if and only if it is a const operation (e.g. 1 + 2, "a" + "b", etc.).
+ *
+ * @template T
+ * @param {(leftOperand: T, rightOperand: T) => boolean | number | BigInt | string} operandHandler the handler for the operation (e.g. (a, b) => a + b)
+ * @returns {BasicEvaluatedExpression | undefined} the evaluated expression
+ */
+ const handleConstOperation = operandHandler => {
+ const left = this.evaluateExpression(expr.left);
+ if (!left.isCompileTimeValue()) return;
+
+ const right = this.evaluateExpression(expr.right);
+ if (!right.isCompileTimeValue()) return;
+
+ const result = operandHandler(
+ left.asCompileTimeValue(),
+ right.asCompileTimeValue()
+ );
+ return valueAsExpression(
+ result,
+ expr,
+ left.couldHaveSideEffects() || right.couldHaveSideEffects()
+ );
+ };
+
+ /**
+ * Helper function to determine if two booleans are always different. This is used in `handleStrictEqualityComparison`
+ * to determine if an expressions boolean or nullish conversion is equal or not.
+ *
+ * @param {boolean} a first boolean to compare
+ * @param {boolean} b second boolean to compare
+ * @returns {boolean} true if the two booleans are always different, false otherwise
+ */
+ const isAlwaysDifferent = (a, b) =>
+ (a === true && b === false) || (a === false && b === true);
+
+ const handleTemplateStringCompare = (left, right, res, eql) => {
+ const getPrefix = parts => {
+ let value = "";
+ for (const p of parts) {
+ const v = p.asString();
+ if (v !== undefined) value += v;
+ else break;
+ }
+ return value;
+ };
+ const getSuffix = parts => {
+ let value = "";
+ for (let i = parts.length - 1; i >= 0; i--) {
+ const v = parts[i].asString();
+ if (v !== undefined) value = v + value;
+ else break;
+ }
+ return value;
+ };
+ const leftPrefix = getPrefix(left.parts);
+ const rightPrefix = getPrefix(right.parts);
+ const leftSuffix = getSuffix(left.parts);
+ const rightSuffix = getSuffix(right.parts);
+ const lenPrefix = Math.min(leftPrefix.length, rightPrefix.length);
+ const lenSuffix = Math.min(leftSuffix.length, rightSuffix.length);
+ const prefixMismatch =
+ lenPrefix > 0 &&
+ leftPrefix.slice(0, lenPrefix) !== rightPrefix.slice(0, lenPrefix);
+ const suffixMismatch =
+ lenSuffix > 0 &&
+ leftSuffix.slice(-lenSuffix) !== rightSuffix.slice(-lenSuffix);
+ if (prefixMismatch || suffixMismatch) {
+ return res
+ .setBoolean(!eql)
+ .setSideEffects(
+ left.couldHaveSideEffects() || right.couldHaveSideEffects()
+ );
+ }
+ };
+
+ /**
+ * Helper function to handle BinaryExpressions using strict equality comparisons (e.g. "===" and "!==").
+ * @param {boolean} eql true for "===" and false for "!=="
+ * @returns {BasicEvaluatedExpression | undefined} the evaluated expression
+ */
+ const handleStrictEqualityComparison = eql => {
+ const left = this.evaluateExpression(expr.left);
+ const right = this.evaluateExpression(expr.right);
+ const res = new BasicEvaluatedExpression();
+ res.setRange(/** @type {Range} */ (expr.range));
+
+ const leftConst = left.isCompileTimeValue();
+ const rightConst = right.isCompileTimeValue();
+
+ if (leftConst && rightConst) {
+ return res
+ .setBoolean(
+ eql ===
+ (left.asCompileTimeValue() === right.asCompileTimeValue())
+ )
+ .setSideEffects(
+ left.couldHaveSideEffects() || right.couldHaveSideEffects()
+ );
+ }
+
+ if (left.isArray() && right.isArray()) {
+ return res
+ .setBoolean(!eql)
+ .setSideEffects(
+ left.couldHaveSideEffects() || right.couldHaveSideEffects()
+ );
+ }
+ if (left.isTemplateString() && right.isTemplateString()) {
+ return handleTemplateStringCompare(left, right, res, eql);
+ }
+
+ const leftPrimitive = left.isPrimitiveType();
+ const rightPrimitive = right.isPrimitiveType();
+
+ if (
+ // Primitive !== Object or
+ // compile-time object types are never equal to something at runtime
+ (leftPrimitive === false &&
+ (leftConst || rightPrimitive === true)) ||
+ (rightPrimitive === false &&
+ (rightConst || leftPrimitive === true)) ||
+ // Different nullish or boolish status also means not equal
+ isAlwaysDifferent(
+ /** @type {boolean} */ (left.asBool()),
+ /** @type {boolean} */ (right.asBool())
+ ) ||
+ isAlwaysDifferent(
+ /** @type {boolean} */ (left.asNullish()),
+ /** @type {boolean} */ (right.asNullish())
+ )
+ ) {
+ return res
+ .setBoolean(!eql)
+ .setSideEffects(
+ left.couldHaveSideEffects() || right.couldHaveSideEffects()
+ );
+ }
+ };
+
+ /**
+ * Helper function to handle BinaryExpressions using abstract equality comparisons (e.g. "==" and "!=").
+ * @param {boolean} eql true for "==" and false for "!="
+ * @returns {BasicEvaluatedExpression | undefined} the evaluated expression
+ */
+ const handleAbstractEqualityComparison = eql => {
+ const left = this.evaluateExpression(expr.left);
+ const right = this.evaluateExpression(expr.right);
+ const res = new BasicEvaluatedExpression();
+ res.setRange(expr.range);
+
+ const leftConst = left.isCompileTimeValue();
+ const rightConst = right.isCompileTimeValue();
+
+ if (leftConst && rightConst) {
+ return res
+ .setBoolean(
+ eql ===
+ // eslint-disable-next-line eqeqeq
+ (left.asCompileTimeValue() == right.asCompileTimeValue())
+ )
+ .setSideEffects(
+ left.couldHaveSideEffects() || right.couldHaveSideEffects()
+ );
+ }
+
+ if (left.isArray() && right.isArray()) {
+ return res
+ .setBoolean(!eql)
+ .setSideEffects(
+ left.couldHaveSideEffects() || right.couldHaveSideEffects()
+ );
+ }
+ if (left.isTemplateString() && right.isTemplateString()) {
+ return handleTemplateStringCompare(left, right, res, eql);
+ }
+ };
+
+ if (expr.operator === "+") {
+ const left = this.evaluateExpression(expr.left);
+ const right = this.evaluateExpression(expr.right);
+ const res = new BasicEvaluatedExpression();
+ if (left.isString()) {
+ if (right.isString()) {
+ res.setString(left.string + right.string);
+ } else if (right.isNumber()) {
+ res.setString(left.string + right.number);
+ } else if (
+ right.isWrapped() &&
+ right.prefix &&
+ right.prefix.isString()
+ ) {
+ // "left" + ("prefix" + inner + "postfix")
+ // => ("leftPrefix" + inner + "postfix")
+ res.setWrapped(
+ new BasicEvaluatedExpression()
+ .setString(left.string + right.prefix.string)
+ .setRange(joinRanges(left.range, right.prefix.range)),
+ right.postfix,
+ right.wrappedInnerExpressions
+ );
+ } else if (right.isWrapped()) {
+ // "left" + ([null] + inner + "postfix")
+ // => ("left" + inner + "postfix")
+ res.setWrapped(
+ left,
+ right.postfix,
+ right.wrappedInnerExpressions
+ );
+ } else {
+ // "left" + expr
+ // => ("left" + expr + "")
+ res.setWrapped(left, null, [right]);
+ }
+ } else if (left.isNumber()) {
+ if (right.isString()) {
+ res.setString(left.number + right.string);
+ } else if (right.isNumber()) {
+ res.setNumber(left.number + right.number);
+ } else {
+ return;
+ }
+ } else if (left.isBigInt()) {
+ if (right.isBigInt()) {
+ res.setBigInt(left.bigint + right.bigint);
+ }
+ } else if (left.isWrapped()) {
+ if (left.postfix && left.postfix.isString() && right.isString()) {
+ // ("prefix" + inner + "postfix") + "right"
+ // => ("prefix" + inner + "postfixRight")
+ res.setWrapped(
+ left.prefix,
+ new BasicEvaluatedExpression()
+ .setString(left.postfix.string + right.string)
+ .setRange(joinRanges(left.postfix.range, right.range)),
+ left.wrappedInnerExpressions
+ );
+ } else if (
+ left.postfix &&
+ left.postfix.isString() &&
+ right.isNumber()
+ ) {
+ // ("prefix" + inner + "postfix") + 123
+ // => ("prefix" + inner + "postfix123")
+ res.setWrapped(
+ left.prefix,
+ new BasicEvaluatedExpression()
+ .setString(left.postfix.string + right.number)
+ .setRange(joinRanges(left.postfix.range, right.range)),
+ left.wrappedInnerExpressions
+ );
+ } else if (right.isString()) {
+ // ("prefix" + inner + [null]) + "right"
+ // => ("prefix" + inner + "right")
+ res.setWrapped(left.prefix, right, left.wrappedInnerExpressions);
+ } else if (right.isNumber()) {
+ // ("prefix" + inner + [null]) + 123
+ // => ("prefix" + inner + "123")
+ res.setWrapped(
+ left.prefix,
+ new BasicEvaluatedExpression()
+ .setString(right.number + "")
+ .setRange(right.range),
+ left.wrappedInnerExpressions
+ );
+ } else if (right.isWrapped()) {
+ // ("prefix1" + inner1 + "postfix1") + ("prefix2" + inner2 + "postfix2")
+ // ("prefix1" + inner1 + "postfix1" + "prefix2" + inner2 + "postfix2")
+ res.setWrapped(
+ left.prefix,
+ right.postfix,
+ left.wrappedInnerExpressions &&
+ right.wrappedInnerExpressions &&
+ left.wrappedInnerExpressions
+ .concat(left.postfix ? [left.postfix] : [])
+ .concat(right.prefix ? [right.prefix] : [])
+ .concat(right.wrappedInnerExpressions)
+ );
+ } else {
+ // ("prefix" + inner + postfix) + expr
+ // => ("prefix" + inner + postfix + expr + [null])
+ res.setWrapped(
+ left.prefix,
+ null,
+ left.wrappedInnerExpressions &&
+ left.wrappedInnerExpressions.concat(
+ left.postfix ? [left.postfix, right] : [right]
+ )
+ );
+ }
+ } else {
+ if (right.isString()) {
+ // left + "right"
+ // => ([null] + left + "right")
+ res.setWrapped(null, right, [left]);
+ } else if (right.isWrapped()) {
+ // left + (prefix + inner + "postfix")
+ // => ([null] + left + prefix + inner + "postfix")
+ res.setWrapped(
+ null,
+ right.postfix,
+ right.wrappedInnerExpressions &&
+ (right.prefix ? [left, right.prefix] : [left]).concat(
+ right.wrappedInnerExpressions
+ )
+ );
+ } else {
+ return;
+ }
+ }
+ if (left.couldHaveSideEffects() || right.couldHaveSideEffects())
+ res.setSideEffects();
+ res.setRange(expr.range);
+ return res;
+ } else if (expr.operator === "-") {
+ return handleConstOperation((l, r) => l - r);
+ } else if (expr.operator === "*") {
+ return handleConstOperation((l, r) => l * r);
+ } else if (expr.operator === "/") {
+ return handleConstOperation((l, r) => l / r);
+ } else if (expr.operator === "**") {
+ return handleConstOperation((l, r) => l ** r);
+ } else if (expr.operator === "===") {
+ return handleStrictEqualityComparison(true);
+ } else if (expr.operator === "==") {
+ return handleAbstractEqualityComparison(true);
+ } else if (expr.operator === "!==") {
+ return handleStrictEqualityComparison(false);
+ } else if (expr.operator === "!=") {
+ return handleAbstractEqualityComparison(false);
+ } else if (expr.operator === "&") {
+ return handleConstOperation((l, r) => l & r);
+ } else if (expr.operator === "|") {
+ return handleConstOperation((l, r) => l | r);
+ } else if (expr.operator === "^") {
+ return handleConstOperation((l, r) => l ^ r);
+ } else if (expr.operator === ">>>") {
+ return handleConstOperation((l, r) => l >>> r);
+ } else if (expr.operator === ">>") {
+ return handleConstOperation((l, r) => l >> r);
+ } else if (expr.operator === "<<") {
+ return handleConstOperation((l, r) => l << r);
+ } else if (expr.operator === "<") {
+ return handleConstOperation((l, r) => l < r);
+ } else if (expr.operator === ">") {
+ return handleConstOperation((l, r) => l > r);
+ } else if (expr.operator === "<=") {
+ return handleConstOperation((l, r) => l <= r);
+ } else if (expr.operator === ">=") {
+ return handleConstOperation((l, r) => l >= r);
+ }
+ });
+ this.hooks.evaluate
+ .for("UnaryExpression")
+ .tap("JavascriptParser", _expr => {
+ const expr = /** @type {UnaryExpression} */ (_expr);
+
+ /**
+ * Evaluates a UnaryExpression if and only if it is a basic const operator (e.g. +a, -a, ~a).
+ *
+ * @template T
+ * @param {(operand: T) => boolean | number | BigInt | string} operandHandler handler for the operand
+ * @returns {BasicEvaluatedExpression | undefined} evaluated expression
+ */
+ const handleConstOperation = operandHandler => {
+ const argument = this.evaluateExpression(expr.argument);
+ if (!argument.isCompileTimeValue()) return;
+ const result = operandHandler(argument.asCompileTimeValue());
+ return valueAsExpression(
+ result,
+ expr,
+ argument.couldHaveSideEffects()
+ );
+ };
+
+ if (expr.operator === "typeof") {
+ switch (expr.argument.type) {
+ case "Identifier": {
+ const res = this.callHooksForName(
+ this.hooks.evaluateTypeof,
+ expr.argument.name,
+ expr
+ );
+ if (res !== undefined) return res;
+ break;
+ }
+ case "MetaProperty": {
+ const res = this.callHooksForName(
+ this.hooks.evaluateTypeof,
+ getRootName(expr.argument),
+ expr
+ );
+ if (res !== undefined) return res;
+ break;
+ }
+ case "MemberExpression": {
+ const res = this.callHooksForExpression(
+ this.hooks.evaluateTypeof,
+ expr.argument,
+ expr
+ );
+ if (res !== undefined) return res;
+ break;
+ }
+ case "ChainExpression": {
+ const res = this.callHooksForExpression(
+ this.hooks.evaluateTypeof,
+ expr.argument.expression,
+ expr
+ );
+ if (res !== undefined) return res;
+ break;
+ }
+ case "FunctionExpression": {
+ return new BasicEvaluatedExpression()
+ .setString("function")
+ .setRange(expr.range);
+ }
+ }
+ const arg = this.evaluateExpression(expr.argument);
+ if (arg.isUnknown()) return;
+ if (arg.isString()) {
+ return new BasicEvaluatedExpression()
+ .setString("string")
+ .setRange(expr.range);
+ }
+ if (arg.isWrapped()) {
+ return new BasicEvaluatedExpression()
+ .setString("string")
+ .setSideEffects()
+ .setRange(expr.range);
+ }
+ if (arg.isUndefined()) {
+ return new BasicEvaluatedExpression()
+ .setString("undefined")
+ .setRange(expr.range);
+ }
+ if (arg.isNumber()) {
+ return new BasicEvaluatedExpression()
+ .setString("number")
+ .setRange(expr.range);
+ }
+ if (arg.isBigInt()) {
+ return new BasicEvaluatedExpression()
+ .setString("bigint")
+ .setRange(expr.range);
+ }
+ if (arg.isBoolean()) {
+ return new BasicEvaluatedExpression()
+ .setString("boolean")
+ .setRange(expr.range);
+ }
+ if (arg.isConstArray() || arg.isRegExp() || arg.isNull()) {
+ return new BasicEvaluatedExpression()
+ .setString("object")
+ .setRange(expr.range);
+ }
+ if (arg.isArray()) {
+ return new BasicEvaluatedExpression()
+ .setString("object")
+ .setSideEffects(arg.couldHaveSideEffects())
+ .setRange(expr.range);
+ }
+ } else if (expr.operator === "!") {
+ const argument = this.evaluateExpression(expr.argument);
+ const bool = argument.asBool();
+ if (typeof bool !== "boolean") return;
+ return new BasicEvaluatedExpression()
+ .setBoolean(!bool)
+ .setSideEffects(argument.couldHaveSideEffects())
+ .setRange(expr.range);
+ } else if (expr.operator === "~") {
+ return handleConstOperation(v => ~v);
+ } else if (expr.operator === "+") {
+ return handleConstOperation(v => +v);
+ } else if (expr.operator === "-") {
+ return handleConstOperation(v => -v);
+ }
+ });
+ this.hooks.evaluateTypeof.for("undefined").tap("JavascriptParser", expr => {
+ return new BasicEvaluatedExpression()
+ .setString("undefined")
+ .setRange(expr.range);
+ });
+ this.hooks.evaluate.for("Identifier").tap("JavascriptParser", expr => {
+ if (/** @type {Identifier} */ (expr).name === "undefined") {
+ return new BasicEvaluatedExpression()
+ .setUndefined()
+ .setRange(expr.range);
+ }
+ });
+ /**
+ * @param {string} exprType expression type name
+ * @param {function(Expression): GetInfoResult | undefined} getInfo get info
+ * @returns {void}
+ */
+ const tapEvaluateWithVariableInfo = (exprType, getInfo) => {
+ /** @type {Expression | undefined} */
+ let cachedExpression = undefined;
+ /** @type {GetInfoResult | undefined} */
+ let cachedInfo = undefined;
+ this.hooks.evaluate.for(exprType).tap("JavascriptParser", expr => {
+ const expression = /** @type {MemberExpression} */ (expr);
+
+ const info = getInfo(expr);
+ if (info !== undefined) {
+ return this.callHooksForInfoWithFallback(
+ this.hooks.evaluateIdentifier,
+ info.name,
+ name => {
+ cachedExpression = expression;
+ cachedInfo = info;
+ },
+ name => {
+ const hook = this.hooks.evaluateDefinedIdentifier.get(name);
+ if (hook !== undefined) {
+ return hook.call(expression);
+ }
+ },
+ expression
+ );
+ }
+ });
+ this.hooks.evaluate
+ .for(exprType)
+ .tap({ name: "JavascriptParser", stage: 100 }, expr => {
+ const info = cachedExpression === expr ? cachedInfo : getInfo(expr);
+ if (info !== undefined) {
+ return new BasicEvaluatedExpression()
+ .setIdentifier(
+ info.name,
+ info.rootInfo,
+ info.getMembers,
+ info.getMembersOptionals,
+ info.getMemberRanges
+ )
+ .setRange(expr.range);
+ }
+ });
+ this.hooks.finish.tap("JavascriptParser", () => {
+ // Cleanup for GC
+ cachedExpression = cachedInfo = undefined;
+ });
+ };
+ tapEvaluateWithVariableInfo("Identifier", expr => {
+ const info = this.getVariableInfo(/** @type {Identifier} */ (expr).name);
+ if (
+ typeof info === "string" ||
+ (info instanceof VariableInfo && typeof info.freeName === "string")
+ ) {
+ return {
+ name: info,
+ rootInfo: info,
+ getMembers: () => [],
+ getMembersOptionals: () => [],
+ getMemberRanges: () => []
+ };
+ }
+ });
+ tapEvaluateWithVariableInfo("ThisExpression", expr => {
+ const info = this.getVariableInfo("this");
+ if (
+ typeof info === "string" ||
+ (info instanceof VariableInfo && typeof info.freeName === "string")
+ ) {
+ return {
+ name: info,
+ rootInfo: info,
+ getMembers: () => [],
+ getMembersOptionals: () => [],
+ getMemberRanges: () => []
+ };
+ }
+ });
+ this.hooks.evaluate.for("MetaProperty").tap("JavascriptParser", expr => {
+ const metaProperty = /** @type {MetaProperty} */ (expr);
+
+ return this.callHooksForName(
+ this.hooks.evaluateIdentifier,
+ getRootName(expr),
+ metaProperty
+ );
+ });
+ tapEvaluateWithVariableInfo("MemberExpression", expr =>
+ this.getMemberExpressionInfo(
+ /** @type {MemberExpression} */ (expr),
+ ALLOWED_MEMBER_TYPES_EXPRESSION
+ )
+ );
+
+ this.hooks.evaluate.for("CallExpression").tap("JavascriptParser", _expr => {
+ const expr = /** @type {CallExpression} */ (_expr);
+ if (
+ expr.callee.type === "MemberExpression" &&
+ expr.callee.property.type ===
+ (expr.callee.computed ? "Literal" : "Identifier")
+ ) {
+ // type Super also possible here
+ const param = this.evaluateExpression(
+ /** @type {Expression} */ (expr.callee.object)
+ );
+ const property =
+ expr.callee.property.type === "Literal"
+ ? `${expr.callee.property.value}`
+ : expr.callee.property.name;
+ const hook = this.hooks.evaluateCallExpressionMember.get(property);
+ if (hook !== undefined) {
+ return hook.call(expr, param);
+ }
+ } else if (expr.callee.type === "Identifier") {
+ return this.callHooksForName(
+ this.hooks.evaluateCallExpression,
+ expr.callee.name,
+ expr
+ );
+ }
+ });
+ this.hooks.evaluateCallExpressionMember
+ .for("indexOf")
+ .tap("JavascriptParser", (expr, param) => {
+ if (!param.isString()) return;
+ if (expr.arguments.length === 0) return;
+ const [arg1, arg2] = expr.arguments;
+ if (arg1.type === "SpreadElement") return;
+ const arg1Eval = this.evaluateExpression(arg1);
+ if (!arg1Eval.isString()) return;
+ const arg1Value = arg1Eval.string;
+
+ let result;
+ if (arg2) {
+ if (arg2.type === "SpreadElement") return;
+ const arg2Eval = this.evaluateExpression(arg2);
+ if (!arg2Eval.isNumber()) return;
+ result = param.string.indexOf(arg1Value, arg2Eval.number);
+ } else {
+ result = param.string.indexOf(arg1Value);
+ }
+ return new BasicEvaluatedExpression()
+ .setNumber(result)
+ .setSideEffects(param.couldHaveSideEffects())
+ .setRange(expr.range);
+ });
+ this.hooks.evaluateCallExpressionMember
+ .for("replace")
+ .tap("JavascriptParser", (expr, param) => {
+ if (!param.isString()) return;
+ if (expr.arguments.length !== 2) return;
+ if (expr.arguments[0].type === "SpreadElement") return;
+ if (expr.arguments[1].type === "SpreadElement") return;
+ let arg1 = this.evaluateExpression(expr.arguments[0]);
+ let arg2 = this.evaluateExpression(expr.arguments[1]);
+ if (!arg1.isString() && !arg1.isRegExp()) return;
+ const arg1Value = arg1.regExp || arg1.string;
+ if (!arg2.isString()) return;
+ const arg2Value = arg2.string;
+ return new BasicEvaluatedExpression()
+ .setString(param.string.replace(arg1Value, arg2Value))
+ .setSideEffects(param.couldHaveSideEffects())
+ .setRange(expr.range);
+ });
+ ["substr", "substring", "slice"].forEach(fn => {
+ this.hooks.evaluateCallExpressionMember
+ .for(fn)
+ .tap("JavascriptParser", (expr, param) => {
+ if (!param.isString()) return;
+ let arg1;
+ let result,
+ str = param.string;
+ switch (expr.arguments.length) {
+ case 1:
+ if (expr.arguments[0].type === "SpreadElement") return;
+ arg1 = this.evaluateExpression(expr.arguments[0]);
+ if (!arg1.isNumber()) return;
+ result = str[fn](arg1.number);
+ break;
+ case 2: {
+ if (expr.arguments[0].type === "SpreadElement") return;
+ if (expr.arguments[1].type === "SpreadElement") return;
+ arg1 = this.evaluateExpression(expr.arguments[0]);
+ const arg2 = this.evaluateExpression(expr.arguments[1]);
+ if (!arg1.isNumber()) return;
+ if (!arg2.isNumber()) return;
+ result = str[fn](arg1.number, arg2.number);
+ break;
+ }
+ default:
+ return;
+ }
+ return new BasicEvaluatedExpression()
+ .setString(result)
+ .setSideEffects(param.couldHaveSideEffects())
+ .setRange(expr.range);
+ });
+ });
+
+ /**
+ * @param {"cooked" | "raw"} kind kind of values to get
+ * @param {TemplateLiteral} templateLiteralExpr TemplateLiteral expr
+ * @returns {{quasis: BasicEvaluatedExpression[], parts: BasicEvaluatedExpression[]}} Simplified template
+ */
+ const getSimplifiedTemplateResult = (kind, templateLiteralExpr) => {
+ /** @type {BasicEvaluatedExpression[]} */
+ const quasis = [];
+ /** @type {BasicEvaluatedExpression[]} */
+ const parts = [];
+
+ for (let i = 0; i < templateLiteralExpr.quasis.length; i++) {
+ const quasiExpr = templateLiteralExpr.quasis[i];
+ const quasi = quasiExpr.value[kind];
+
+ if (i > 0) {
+ const prevExpr = parts[parts.length - 1];
+ const expr = this.evaluateExpression(
+ templateLiteralExpr.expressions[i - 1]
+ );
+ const exprAsString = expr.asString();
+ if (
+ typeof exprAsString === "string" &&
+ !expr.couldHaveSideEffects()
+ ) {
+ // We can merge quasi + expr + quasi when expr
+ // is a const string
+
+ prevExpr.setString(prevExpr.string + exprAsString + quasi);
+ prevExpr.setRange([prevExpr.range[0], quasiExpr.range[1]]);
+ // We unset the expression as it doesn't match to a single expression
+ prevExpr.setExpression(undefined);
+ continue;
+ }
+ parts.push(expr);
+ }
+
+ const part = new BasicEvaluatedExpression()
+ .setString(quasi)
+ .setRange(/** @type {Range} */ (quasiExpr.range))
+ .setExpression(quasiExpr);
+ quasis.push(part);
+ parts.push(part);
+ }
+ return {
+ quasis,
+ parts
+ };
+ };
+
+ this.hooks.evaluate
+ .for("TemplateLiteral")
+ .tap("JavascriptParser", _node => {
+ const node = /** @type {TemplateLiteral} */ (_node);
+
+ const { quasis, parts } = getSimplifiedTemplateResult("cooked", node);
+ if (parts.length === 1) {
+ return parts[0].setRange(/** @type {Range} */ (node.range));
+ }
+ return new BasicEvaluatedExpression()
+ .setTemplateString(quasis, parts, "cooked")
+ .setRange(/** @type {Range} */ (node.range));
+ });
+ this.hooks.evaluate
+ .for("TaggedTemplateExpression")
+ .tap("JavascriptParser", _node => {
+ const node = /** @type {TaggedTemplateExpression} */ (_node);
+ const tag = this.evaluateExpression(node.tag);
+
+ if (tag.isIdentifier() && tag.identifier === "String.raw") {
+ const { quasis, parts } = getSimplifiedTemplateResult(
+ "raw",
+ node.quasi
+ );
+ return new BasicEvaluatedExpression()
+ .setTemplateString(quasis, parts, "raw")
+ .setRange(/** @type {Range} */ (node.range));
+ }
+ });
+
+ this.hooks.evaluateCallExpressionMember
+ .for("concat")
+ .tap("JavascriptParser", (expr, param) => {
+ if (!param.isString() && !param.isWrapped()) return;
+
+ let stringSuffix = null;
+ let hasUnknownParams = false;
+ const innerExpressions = [];
+ for (let i = expr.arguments.length - 1; i >= 0; i--) {
+ const arg = expr.arguments[i];
+ if (arg.type === "SpreadElement") return;
+ const argExpr = this.evaluateExpression(arg);
+ if (
+ hasUnknownParams ||
+ (!argExpr.isString() && !argExpr.isNumber())
+ ) {
+ hasUnknownParams = true;
+ innerExpressions.push(argExpr);
+ continue;
+ }
+
+ const value = argExpr.isString()
+ ? argExpr.string
+ : "" + argExpr.number;
+
+ const newString = value + (stringSuffix ? stringSuffix.string : "");
+ const newRange = /** @type {Range} */ ([
+ argExpr.range[0],
+ (stringSuffix || argExpr).range[1]
+ ]);
+ stringSuffix = new BasicEvaluatedExpression()
+ .setString(newString)
+ .setSideEffects(
+ (stringSuffix && stringSuffix.couldHaveSideEffects()) ||
+ argExpr.couldHaveSideEffects()
+ )
+ .setRange(newRange);
+ }
+
+ if (hasUnknownParams) {
+ const prefix = param.isString() ? param : param.prefix;
+ const inner =
+ param.isWrapped() && param.wrappedInnerExpressions
+ ? param.wrappedInnerExpressions.concat(innerExpressions.reverse())
+ : innerExpressions.reverse();
+ return new BasicEvaluatedExpression()
+ .setWrapped(prefix, stringSuffix, inner)
+ .setRange(expr.range);
+ } else if (param.isWrapped()) {
+ const postfix = stringSuffix || param.postfix;
+ const inner = param.wrappedInnerExpressions
+ ? param.wrappedInnerExpressions.concat(innerExpressions.reverse())
+ : innerExpressions.reverse();
+ return new BasicEvaluatedExpression()
+ .setWrapped(param.prefix, postfix, inner)
+ .setRange(expr.range);
+ } else {
+ const newString =
+ param.string + (stringSuffix ? stringSuffix.string : "");
+ return new BasicEvaluatedExpression()
+ .setString(newString)
+ .setSideEffects(
+ (stringSuffix && stringSuffix.couldHaveSideEffects()) ||
+ param.couldHaveSideEffects()
+ )
+ .setRange(expr.range);
+ }
+ });
+ this.hooks.evaluateCallExpressionMember
+ .for("split")
+ .tap("JavascriptParser", (expr, param) => {
+ if (!param.isString()) return;
+ if (expr.arguments.length !== 1) return;
+ if (expr.arguments[0].type === "SpreadElement") return;
+ let result;
+ const arg = this.evaluateExpression(expr.arguments[0]);
+ if (arg.isString()) {
+ result = param.string.split(arg.string);
+ } else if (arg.isRegExp()) {
+ result = param.string.split(arg.regExp);
+ } else {
+ return;
+ }
+ return new BasicEvaluatedExpression()
+ .setArray(result)
+ .setSideEffects(param.couldHaveSideEffects())
+ .setRange(/** @type {Range} */ (expr.range));
+ });
+ this.hooks.evaluate
+ .for("ConditionalExpression")
+ .tap("JavascriptParser", _expr => {
+ const expr = /** @type {ConditionalExpression} */ (_expr);
+
+ const condition = this.evaluateExpression(expr.test);
+ const conditionValue = condition.asBool();
+ let res;
+ if (conditionValue === undefined) {
+ const consequent = this.evaluateExpression(expr.consequent);
+ const alternate = this.evaluateExpression(expr.alternate);
+ res = new BasicEvaluatedExpression();
+ if (consequent.isConditional()) {
+ res.setOptions(
+ /** @type {BasicEvaluatedExpression[]} */ (consequent.options)
+ );
+ } else {
+ res.setOptions([consequent]);
+ }
+ if (alternate.isConditional()) {
+ res.addOptions(
+ /** @type {BasicEvaluatedExpression[]} */ (alternate.options)
+ );
+ } else {
+ res.addOptions([alternate]);
+ }
+ } else {
+ res = this.evaluateExpression(
+ conditionValue ? expr.consequent : expr.alternate
+ );
+ if (condition.couldHaveSideEffects()) res.setSideEffects();
+ }
+ res.setRange(/** @type {Range} */ (expr.range));
+ return res;
+ });
+ this.hooks.evaluate
+ .for("ArrayExpression")
+ .tap("JavascriptParser", _expr => {
+ const expr = /** @type {ArrayExpression} */ (_expr);
+
+ const items = expr.elements.map(element => {
+ return (
+ element !== null &&
+ element.type !== "SpreadElement" &&
+ this.evaluateExpression(element)
+ );
+ });
+ if (!items.every(Boolean)) return;
+ return new BasicEvaluatedExpression()
+ .setItems(items)
+ .setRange(/** @type {Range} */ (expr.range));
+ });
+ this.hooks.evaluate
+ .for("ChainExpression")
+ .tap("JavascriptParser", _expr => {
+ const expr = /** @type {ChainExpression} */ (_expr);
+ /** @type {Expression[]} */
+ const optionalExpressionsStack = [];
+ /** @type {Expression|Super} */
+ let next = expr.expression;
+
+ while (
+ next.type === "MemberExpression" ||
+ next.type === "CallExpression"
+ ) {
+ if (next.type === "MemberExpression") {
+ if (next.optional) {
+ // SuperNode can not be optional
+ optionalExpressionsStack.push(
+ /** @type {Expression} */ (next.object)
+ );
+ }
+ next = next.object;
+ } else {
+ if (next.optional) {
+ // SuperNode can not be optional
+ optionalExpressionsStack.push(
+ /** @type {Expression} */ (next.callee)
+ );
+ }
+ next = next.callee;
+ }
+ }
+
+ while (optionalExpressionsStack.length > 0) {
+ const expression =
+ /** @type {Expression} */
+ (optionalExpressionsStack.pop());
+ const evaluated = this.evaluateExpression(expression);
+
+ if (evaluated.asNullish()) {
+ return evaluated.setRange(/** @type {Range} */ (_expr.range));
+ }
+ }
+ return this.evaluateExpression(expr.expression);
+ });
+ }
+
+ /**
+ * @param {Expression} node node
+ * @returns {Set<string>|undefined} destructured identifiers
+ */
+ destructuringAssignmentPropertiesFor(node) {
+ if (!this.destructuringAssignmentProperties) return undefined;
+ return this.destructuringAssignmentProperties.get(node);
+ }
+
+ /**
+ * @param {Expression} expr expression
+ * @returns {string | VariableInfoInterface | undefined} identifier
+ */
+ getRenameIdentifier(expr) {
+ const result = this.evaluateExpression(expr);
+ if (result.isIdentifier()) {
+ return result.identifier;
+ }
+ }
+
+ /**
+ * @param {ClassExpression | ClassDeclaration} classy a class node
+ * @returns {void}
+ */
+ walkClass(classy) {
+ if (classy.superClass) {
+ if (!this.hooks.classExtendsExpression.call(classy.superClass, classy)) {
+ this.walkExpression(classy.superClass);
+ }
+ }
+ if (classy.body && classy.body.type === "ClassBody") {
+ const scopeParams = [];
+ // Add class name in scope for recursive calls
+ if (classy.id) {
+ scopeParams.push(classy.id);
+ }
+ this.inClassScope(true, scopeParams, () => {
+ for (const classElement of /** @type {TODO} */ (classy.body.body)) {
+ if (!this.hooks.classBodyElement.call(classElement, classy)) {
+ if (classElement.computed && classElement.key) {
+ this.walkExpression(classElement.key);
+ }
+ if (classElement.value) {
+ if (
+ !this.hooks.classBodyValue.call(
+ classElement.value,
+ classElement,
+ classy
+ )
+ ) {
+ const wasTopLevel = this.scope.topLevelScope;
+ this.scope.topLevelScope = false;
+ this.walkExpression(classElement.value);
+ this.scope.topLevelScope = wasTopLevel;
+ }
+ } else if (classElement.type === "StaticBlock") {
+ const wasTopLevel = this.scope.topLevelScope;
+ this.scope.topLevelScope = false;
+ this.walkBlockStatement(classElement);
+ this.scope.topLevelScope = wasTopLevel;
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Pre walking iterates the scope for variable declarations
+ *
+ * @param {(Statement | ModuleDeclaration)[]} statements statements
+ */
+ preWalkStatements(statements) {
+ for (let index = 0, len = statements.length; index < len; index++) {
+ const statement = statements[index];
+ this.preWalkStatement(statement);
+ }
+ }
+
+ /**
+ * Block pre walking iterates the scope for block variable declarations
+ *
+ * @param {(Statement | ModuleDeclaration)[]} statements statements
+ */
+ blockPreWalkStatements(statements) {
+ for (let index = 0, len = statements.length; index < len; index++) {
+ const statement = statements[index];
+ this.blockPreWalkStatement(statement);
+ }
+ }
+
+ /**
+ * Walking iterates the statements and expressions and processes them
+ *
+ * @param {(Statement | ModuleDeclaration)[]} statements statements
+ */
+ walkStatements(statements) {
+ for (let index = 0, len = statements.length; index < len; index++) {
+ const statement = statements[index];
+ this.walkStatement(statement);
+ }
+ }
+
+ /**
+ * Walking iterates the statements and expressions and processes them
+ *
+ * @param {Statement | ModuleDeclaration} statement statement
+ */
+ preWalkStatement(statement) {
+ this.statementPath.push(statement);
+ if (this.hooks.preStatement.call(statement)) {
+ this.prevStatement = this.statementPath.pop();
+ return;
+ }
+ switch (statement.type) {
+ case "BlockStatement":
+ this.preWalkBlockStatement(statement);
+ break;
+ case "DoWhileStatement":
+ this.preWalkDoWhileStatement(statement);
+ break;
+ case "ForInStatement":
+ this.preWalkForInStatement(statement);
+ break;
+ case "ForOfStatement":
+ this.preWalkForOfStatement(statement);
+ break;
+ case "ForStatement":
+ this.preWalkForStatement(statement);
+ break;
+ case "FunctionDeclaration":
+ this.preWalkFunctionDeclaration(statement);
+ break;
+ case "IfStatement":
+ this.preWalkIfStatement(statement);
+ break;
+ case "LabeledStatement":
+ this.preWalkLabeledStatement(statement);
+ break;
+ case "SwitchStatement":
+ this.preWalkSwitchStatement(statement);
+ break;
+ case "TryStatement":
+ this.preWalkTryStatement(statement);
+ break;
+ case "VariableDeclaration":
+ this.preWalkVariableDeclaration(statement);
+ break;
+ case "WhileStatement":
+ this.preWalkWhileStatement(statement);
+ break;
+ case "WithStatement":
+ this.preWalkWithStatement(statement);
+ break;
+ }
+ this.prevStatement = this.statementPath.pop();
+ }
+
+ /**
+ * @param {Statement | ModuleDeclaration} statement statement
+ */
+ blockPreWalkStatement(statement) {
+ this.statementPath.push(statement);
+ if (this.hooks.blockPreStatement.call(statement)) {
+ this.prevStatement = this.statementPath.pop();
+ return;
+ }
+ switch (statement.type) {
+ case "ImportDeclaration":
+ this.blockPreWalkImportDeclaration(statement);
+ break;
+ case "ExportAllDeclaration":
+ this.blockPreWalkExportAllDeclaration(statement);
+ break;
+ case "ExportDefaultDeclaration":
+ this.blockPreWalkExportDefaultDeclaration(statement);
+ break;
+ case "ExportNamedDeclaration":
+ this.blockPreWalkExportNamedDeclaration(statement);
+ break;
+ case "VariableDeclaration":
+ this.blockPreWalkVariableDeclaration(statement);
+ break;
+ case "ClassDeclaration":
+ this.blockPreWalkClassDeclaration(statement);
+ break;
+ case "ExpressionStatement":
+ this.blockPreWalkExpressionStatement(statement);
+ }
+ this.prevStatement = this.statementPath.pop();
+ }
+
+ /**
+ * @param {Statement | ModuleDeclaration} statement statement
+ */
+ walkStatement(statement) {
+ this.statementPath.push(statement);
+ if (this.hooks.statement.call(statement) !== undefined) {
+ this.prevStatement = this.statementPath.pop();
+ return;
+ }
+ switch (statement.type) {
+ case "BlockStatement":
+ this.walkBlockStatement(statement);
+ break;
+ case "ClassDeclaration":
+ this.walkClassDeclaration(statement);
+ break;
+ case "DoWhileStatement":
+ this.walkDoWhileStatement(statement);
+ break;
+ case "ExportDefaultDeclaration":
+ this.walkExportDefaultDeclaration(statement);
+ break;
+ case "ExportNamedDeclaration":
+ this.walkExportNamedDeclaration(statement);
+ break;
+ case "ExpressionStatement":
+ this.walkExpressionStatement(statement);
+ break;
+ case "ForInStatement":
+ this.walkForInStatement(statement);
+ break;
+ case "ForOfStatement":
+ this.walkForOfStatement(statement);
+ break;
+ case "ForStatement":
+ this.walkForStatement(statement);
+ break;
+ case "FunctionDeclaration":
+ this.walkFunctionDeclaration(statement);
+ break;
+ case "IfStatement":
+ this.walkIfStatement(statement);
+ break;
+ case "LabeledStatement":
+ this.walkLabeledStatement(statement);
+ break;
+ case "ReturnStatement":
+ this.walkReturnStatement(statement);
+ break;
+ case "SwitchStatement":
+ this.walkSwitchStatement(statement);
+ break;
+ case "ThrowStatement":
+ this.walkThrowStatement(statement);
+ break;
+ case "TryStatement":
+ this.walkTryStatement(statement);
+ break;
+ case "VariableDeclaration":
+ this.walkVariableDeclaration(statement);
+ break;
+ case "WhileStatement":
+ this.walkWhileStatement(statement);
+ break;
+ case "WithStatement":
+ this.walkWithStatement(statement);
+ break;
+ }
+ this.prevStatement = this.statementPath.pop();
+ }
+
+ /**
+ * Walks a statements that is nested within a parent statement
+ * and can potentially be a non-block statement.
+ * This enforces the nested statement to never be in ASI position.
+ *
+ * @param {Statement} statement the nested statement
+ */
+ walkNestedStatement(statement) {
+ this.prevStatement = undefined;
+ this.walkStatement(statement);
+ }
+
+ // Real Statements
+ /**
+ * @param {BlockStatement} statement block statement
+ */
+ preWalkBlockStatement(statement) {
+ this.preWalkStatements(statement.body);
+ }
+
+ /**
+ * @param {BlockStatement} statement block statement
+ */
+ walkBlockStatement(statement) {
+ this.inBlockScope(() => {
+ const body = statement.body;
+ const prev = this.prevStatement;
+ this.blockPreWalkStatements(body);
+ this.prevStatement = prev;
+ this.walkStatements(body);
+ });
+ }
+
+ /**
+ * @param {ExpressionStatement} statement expression statement
+ */
+ walkExpressionStatement(statement) {
+ this.walkExpression(statement.expression);
+ }
+
+ /**
+ * @param {IfStatement} statement if statement
+ */
+ preWalkIfStatement(statement) {
+ this.preWalkStatement(statement.consequent);
+ if (statement.alternate) {
+ this.preWalkStatement(statement.alternate);
+ }
+ }
+
+ /**
+ * @param {IfStatement} statement if statement
+ */
+ walkIfStatement(statement) {
+ const result = this.hooks.statementIf.call(statement);
+ if (result === undefined) {
+ this.walkExpression(statement.test);
+ this.walkNestedStatement(statement.consequent);
+ if (statement.alternate) {
+ this.walkNestedStatement(statement.alternate);
+ }
+ } else {
+ if (result) {
+ this.walkNestedStatement(statement.consequent);
+ } else if (statement.alternate) {
+ this.walkNestedStatement(statement.alternate);
+ }
+ }
+ }
+
+ /**
+ * @param {LabeledStatement} statement with statement
+ */
+ preWalkLabeledStatement(statement) {
+ this.preWalkStatement(statement.body);
+ }
+
+ /**
+ * @param {LabeledStatement} statement with statement
+ */
+ walkLabeledStatement(statement) {
+ const hook = this.hooks.label.get(statement.label.name);
+ if (hook !== undefined) {
+ const result = hook.call(statement);
+ if (result === true) return;
+ }
+ this.walkNestedStatement(statement.body);
+ }
+
+ /**
+ * @param {WithStatement} statement with statement
+ */
+ preWalkWithStatement(statement) {
+ this.preWalkStatement(statement.body);
+ }
+
+ /**
+ * @param {WithStatement} statement with statement
+ */
+ walkWithStatement(statement) {
+ this.walkExpression(statement.object);
+ this.walkNestedStatement(statement.body);
+ }
+
+ /**
+ * @param {SwitchStatement} statement switch statement
+ */
+ preWalkSwitchStatement(statement) {
+ this.preWalkSwitchCases(statement.cases);
+ }
+
+ /**
+ * @param {SwitchStatement} statement switch statement
+ */
+ walkSwitchStatement(statement) {
+ this.walkExpression(statement.discriminant);
+ this.walkSwitchCases(statement.cases);
+ }
+
+ /**
+ * @param {ReturnStatement | ThrowStatement} statement return or throw statement
+ */
+ walkTerminatingStatement(statement) {
+ if (statement.argument) this.walkExpression(statement.argument);
+ }
+
+ /**
+ * @param {ReturnStatement} statement return statement
+ */
+ walkReturnStatement(statement) {
+ this.walkTerminatingStatement(statement);
+ }
+
+ /**
+ * @param {ThrowStatement} statement return statement
+ */
+ walkThrowStatement(statement) {
+ this.walkTerminatingStatement(statement);
+ }
+
+ /**
+ * @param {TryStatement} statement try statement
+ */
+ preWalkTryStatement(statement) {
+ this.preWalkStatement(statement.block);
+ if (statement.handler) this.preWalkCatchClause(statement.handler);
+ if (statement.finalizer) this.preWalkStatement(statement.finalizer);
+ }
+
+ /**
+ * @param {TryStatement} statement try statement
+ */
+ walkTryStatement(statement) {
+ if (this.scope.inTry) {
+ this.walkStatement(statement.block);
+ } else {
+ this.scope.inTry = true;
+ this.walkStatement(statement.block);
+ this.scope.inTry = false;
+ }
+ if (statement.handler) this.walkCatchClause(statement.handler);
+ if (statement.finalizer) this.walkStatement(statement.finalizer);
+ }
+
+ /**
+ * @param {WhileStatement} statement while statement
+ */
+ preWalkWhileStatement(statement) {
+ this.preWalkStatement(statement.body);
+ }
+
+ /**
+ * @param {WhileStatement} statement while statement
+ */
+ walkWhileStatement(statement) {
+ this.walkExpression(statement.test);
+ this.walkNestedStatement(statement.body);
+ }
+
+ /**
+ * @param {DoWhileStatement} statement do while statement
+ */
+ preWalkDoWhileStatement(statement) {
+ this.preWalkStatement(statement.body);
+ }
+
+ /**
+ * @param {DoWhileStatement} statement do while statement
+ */
+ walkDoWhileStatement(statement) {
+ this.walkNestedStatement(statement.body);
+ this.walkExpression(statement.test);
+ }
+
+ /**
+ * @param {ForStatement} statement for statement
+ */
+ preWalkForStatement(statement) {
+ if (statement.init) {
+ if (statement.init.type === "VariableDeclaration") {
+ this.preWalkStatement(statement.init);
+ }
+ }
+ this.preWalkStatement(statement.body);
+ }
+
+ /**
+ * @param {ForStatement} statement for statement
+ */
+ walkForStatement(statement) {
+ this.inBlockScope(() => {
+ if (statement.init) {
+ if (statement.init.type === "VariableDeclaration") {
+ this.blockPreWalkVariableDeclaration(statement.init);
+ this.prevStatement = undefined;
+ this.walkStatement(statement.init);
+ } else {
+ this.walkExpression(statement.init);
+ }
+ }
+ if (statement.test) {
+ this.walkExpression(statement.test);
+ }
+ if (statement.update) {
+ this.walkExpression(statement.update);
+ }
+ const body = statement.body;
+ if (body.type === "BlockStatement") {
+ // no need to add additional scope
+ const prev = this.prevStatement;
+ this.blockPreWalkStatements(body.body);
+ this.prevStatement = prev;
+ this.walkStatements(body.body);
+ } else {
+ this.walkNestedStatement(body);
+ }
+ });
+ }
+
+ /**
+ * @param {ForInStatement} statement for statement
+ */
+ preWalkForInStatement(statement) {
+ if (statement.left.type === "VariableDeclaration") {
+ this.preWalkVariableDeclaration(statement.left);
+ }
+ this.preWalkStatement(statement.body);
+ }
+
+ /**
+ * @param {ForInStatement} statement for statement
+ */
+ walkForInStatement(statement) {
+ this.inBlockScope(() => {
+ if (statement.left.type === "VariableDeclaration") {
+ this.blockPreWalkVariableDeclaration(statement.left);
+ this.walkVariableDeclaration(statement.left);
+ } else {
+ this.walkPattern(statement.left);
+ }
+ this.walkExpression(statement.right);
+ const body = statement.body;
+ if (body.type === "BlockStatement") {
+ // no need to add additional scope
+ const prev = this.prevStatement;
+ this.blockPreWalkStatements(body.body);
+ this.prevStatement = prev;
+ this.walkStatements(body.body);
+ } else {
+ this.walkNestedStatement(body);
+ }
+ });
+ }
+
+ preWalkForOfStatement(statement) {
+ if (statement.await && this.scope.topLevelScope === true) {
+ this.hooks.topLevelAwait.call(statement);
+ }
+ if (statement.left.type === "VariableDeclaration") {
+ this.preWalkVariableDeclaration(statement.left);
+ }
+ this.preWalkStatement(statement.body);
+ }
+
+ /**
+ * @param {ForOfStatement} statement for statement
+ */
+ walkForOfStatement(statement) {
+ this.inBlockScope(() => {
+ if (statement.left.type === "VariableDeclaration") {
+ this.blockPreWalkVariableDeclaration(statement.left);
+ this.walkVariableDeclaration(statement.left);
+ } else {
+ this.walkPattern(statement.left);
+ }
+ this.walkExpression(statement.right);
+ const body = statement.body;
+ if (body.type === "BlockStatement") {
+ // no need to add additional scope
+ const prev = this.prevStatement;
+ this.blockPreWalkStatements(body.body);
+ this.prevStatement = prev;
+ this.walkStatements(body.body);
+ } else {
+ this.walkNestedStatement(body);
+ }
+ });
+ }
+
+ /**
+ * @param {FunctionDeclaration} statement function declaration
+ */
+ preWalkFunctionDeclaration(statement) {
+ if (statement.id) {
+ this.defineVariable(statement.id.name);
+ }
+ }
+
+ /**
+ * @param {FunctionDeclaration} statement function declaration
+ */
+ walkFunctionDeclaration(statement) {
+ const wasTopLevel = this.scope.topLevelScope;
+ this.scope.topLevelScope = false;
+ this.inFunctionScope(true, statement.params, () => {
+ for (const param of statement.params) {
+ this.walkPattern(param);
+ }
+ if (statement.body.type === "BlockStatement") {
+ this.detectMode(statement.body.body);
+ const prev = this.prevStatement;
+ this.preWalkStatement(statement.body);
+ this.prevStatement = prev;
+ this.walkStatement(statement.body);
+ } else {
+ this.walkExpression(statement.body);
+ }
+ });
+ this.scope.topLevelScope = wasTopLevel;
+ }
+
+ /**
+ * @param {ExpressionStatement} statement expression statement
+ */
+ blockPreWalkExpressionStatement(statement) {
+ const expression = statement.expression;
+ switch (expression.type) {
+ case "AssignmentExpression":
+ this.preWalkAssignmentExpression(expression);
+ }
+ }
+
+ /**
+ * @param {AssignmentExpression} expression assignment expression
+ */
+ preWalkAssignmentExpression(expression) {
+ if (
+ expression.left.type !== "ObjectPattern" ||
+ !this.destructuringAssignmentProperties
+ )
+ return;
+ const keys = this._preWalkObjectPattern(expression.left);
+ if (!keys) return;
+
+ // check multiple assignments
+ if (this.destructuringAssignmentProperties.has(expression)) {
+ const set = this.destructuringAssignmentProperties.get(expression);
+ this.destructuringAssignmentProperties.delete(expression);
+ for (const id of set) keys.add(id);
+ }
+
+ this.destructuringAssignmentProperties.set(
+ expression.right.type === "AwaitExpression"
+ ? expression.right.argument
+ : expression.right,
+ keys
+ );
+
+ if (expression.right.type === "AssignmentExpression") {
+ this.preWalkAssignmentExpression(expression.right);
+ }
+ }
+
+ blockPreWalkImportDeclaration(statement) {
+ const source = statement.source.value;
+ this.hooks.import.call(statement, source);
+ for (const specifier of statement.specifiers) {
+ const name = specifier.local.name;
+ switch (specifier.type) {
+ case "ImportDefaultSpecifier":
+ if (
+ !this.hooks.importSpecifier.call(statement, source, "default", name)
+ ) {
+ this.defineVariable(name);
+ }
+ break;
+ case "ImportSpecifier":
+ if (
+ !this.hooks.importSpecifier.call(
+ statement,
+ source,
+ specifier.imported.name || specifier.imported.value,
+ name
+ )
+ ) {
+ this.defineVariable(name);
+ }
+ break;
+ case "ImportNamespaceSpecifier":
+ if (!this.hooks.importSpecifier.call(statement, source, null, name)) {
+ this.defineVariable(name);
+ }
+ break;
+ default:
+ this.defineVariable(name);
+ }
+ }
+ }
+
+ enterDeclaration(declaration, onIdent) {
+ switch (declaration.type) {
+ case "VariableDeclaration":
+ for (const declarator of declaration.declarations) {
+ switch (declarator.type) {
+ case "VariableDeclarator": {
+ this.enterPattern(declarator.id, onIdent);
+ break;
+ }
+ }
+ }
+ break;
+ case "FunctionDeclaration":
+ this.enterPattern(declaration.id, onIdent);
+ break;
+ case "ClassDeclaration":
+ this.enterPattern(declaration.id, onIdent);
+ break;
+ }
+ }
+
+ blockPreWalkExportNamedDeclaration(statement) {
+ let source;
+ if (statement.source) {
+ source = statement.source.value;
+ this.hooks.exportImport.call(statement, source);
+ } else {
+ this.hooks.export.call(statement);
+ }
+ if (statement.declaration) {
+ if (
+ !this.hooks.exportDeclaration.call(statement, statement.declaration)
+ ) {
+ const prev = this.prevStatement;
+ this.preWalkStatement(statement.declaration);
+ this.prevStatement = prev;
+ this.blockPreWalkStatement(statement.declaration);
+ let index = 0;
+ this.enterDeclaration(statement.declaration, def => {
+ this.hooks.exportSpecifier.call(statement, def, def, index++);
+ });
+ }
+ }
+ if (statement.specifiers) {
+ for (
+ let specifierIndex = 0;
+ specifierIndex < statement.specifiers.length;
+ specifierIndex++
+ ) {
+ const specifier = statement.specifiers[specifierIndex];
+ switch (specifier.type) {
+ case "ExportSpecifier": {
+ const name = specifier.exported.name || specifier.exported.value;
+ if (source) {
+ this.hooks.exportImportSpecifier.call(
+ statement,
+ source,
+ specifier.local.name,
+ name,
+ specifierIndex
+ );
+ } else {
+ this.hooks.exportSpecifier.call(
+ statement,
+ specifier.local.name,
+ name,
+ specifierIndex
+ );
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @param {ExportNamedDeclaration} statement the statement
+ */
+ walkExportNamedDeclaration(statement) {
+ if (statement.declaration) {
+ this.walkStatement(statement.declaration);
+ }
+ }
+
+ blockPreWalkExportDefaultDeclaration(statement) {
+ const prev = this.prevStatement;
+ this.preWalkStatement(statement.declaration);
+ this.prevStatement = prev;
+ this.blockPreWalkStatement(statement.declaration);
+ if (
+ statement.declaration.id &&
+ statement.declaration.type !== "FunctionExpression" &&
+ statement.declaration.type !== "ClassExpression"
+ ) {
+ this.hooks.exportSpecifier.call(
+ statement,
+ statement.declaration.id.name,
+ "default",
+ undefined
+ );
+ }
+ }
+
+ walkExportDefaultDeclaration(statement) {
+ this.hooks.export.call(statement);
+ if (
+ statement.declaration.id &&
+ statement.declaration.type !== "FunctionExpression" &&
+ statement.declaration.type !== "ClassExpression"
+ ) {
+ if (
+ !this.hooks.exportDeclaration.call(statement, statement.declaration)
+ ) {
+ this.walkStatement(statement.declaration);
+ }
+ } else {
+ // Acorn parses `export default function() {}` as `FunctionDeclaration` and
+ // `export default class {}` as `ClassDeclaration`, both with `id = null`.
+ // These nodes must be treated as expressions.
+ if (
+ statement.declaration.type === "FunctionDeclaration" ||
+ statement.declaration.type === "ClassDeclaration"
+ ) {
+ this.walkStatement(statement.declaration);
+ } else {
+ this.walkExpression(statement.declaration);
+ }
+ if (!this.hooks.exportExpression.call(statement, statement.declaration)) {
+ this.hooks.exportSpecifier.call(
+ statement,
+ statement.declaration,
+ "default",
+ undefined
+ );
+ }
+ }
+ }
+
+ blockPreWalkExportAllDeclaration(statement) {
+ const source = statement.source.value;
+ const name = statement.exported ? statement.exported.name : null;
+ this.hooks.exportImport.call(statement, source);
+ this.hooks.exportImportSpecifier.call(statement, source, null, name, 0);
+ }
+
+ /**
+ * @param {VariableDeclaration} statement variable declaration
+ */
+ preWalkVariableDeclaration(statement) {
+ if (statement.kind !== "var") return;
+ this._preWalkVariableDeclaration(statement, this.hooks.varDeclarationVar);
+ }
+
+ /**
+ * @param {VariableDeclaration} statement variable declaration
+ */
+ blockPreWalkVariableDeclaration(statement) {
+ if (statement.kind === "var") return;
+ const hookMap =
+ statement.kind === "const"
+ ? this.hooks.varDeclarationConst
+ : this.hooks.varDeclarationLet;
+ this._preWalkVariableDeclaration(statement, hookMap);
+ }
+
+ /**
+ * @param {VariableDeclaration} statement variable declaration
+ * @param {TODO} hookMap map of hooks
+ */
+ _preWalkVariableDeclaration(statement, hookMap) {
+ for (const declarator of statement.declarations) {
+ switch (declarator.type) {
+ case "VariableDeclarator": {
+ this.preWalkVariableDeclarator(declarator);
+ if (!this.hooks.preDeclarator.call(declarator, statement)) {
+ this.enterPattern(declarator.id, (name, decl) => {
+ let hook = hookMap.get(name);
+ if (hook === undefined || !hook.call(decl)) {
+ hook = this.hooks.varDeclaration.get(name);
+ if (hook === undefined || !hook.call(decl)) {
+ this.defineVariable(name);
+ }
+ }
+ });
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param {ObjectPattern} objectPattern object pattern
+ * @returns {Set<string> | undefined} set of names or undefined if not all keys are identifiers
+ */
+ _preWalkObjectPattern(objectPattern) {
+ const ids = new Set();
+ const properties = objectPattern.properties;
+ for (let i = 0; i < properties.length; i++) {
+ const property = properties[i];
+ if (property.type !== "Property") return;
+ const key = property.key;
+ if (key.type === "Identifier") {
+ ids.add(key.name);
+ } else {
+ const id = this.evaluateExpression(key);
+ const str = id.asString();
+ if (str) {
+ ids.add(str);
+ } else {
+ // could not evaluate key
+ return;
+ }
+ }
+ }
+
+ return ids;
+ }
+
+ /**
+ * @param {VariableDeclarator} declarator variable declarator
+ */
+ preWalkVariableDeclarator(declarator) {
+ if (
+ !declarator.init ||
+ declarator.id.type !== "ObjectPattern" ||
+ !this.destructuringAssignmentProperties
+ )
+ return;
+ const keys = this._preWalkObjectPattern(declarator.id);
+
+ if (!keys) return;
+ this.destructuringAssignmentProperties.set(
+ declarator.init.type === "AwaitExpression"
+ ? declarator.init.argument
+ : declarator.init,
+ keys
+ );
+
+ if (declarator.init.type === "AssignmentExpression") {
+ this.preWalkAssignmentExpression(declarator.init);
+ }
+ }
+
+ /**
+ * @param {VariableDeclaration} statement variable declaration
+ */
+ walkVariableDeclaration(statement) {
+ for (const declarator of statement.declarations) {
+ switch (declarator.type) {
+ case "VariableDeclarator": {
+ const renameIdentifier =
+ declarator.init && this.getRenameIdentifier(declarator.init);
+ if (renameIdentifier && declarator.id.type === "Identifier") {
+ const hook = this.hooks.canRename.get(renameIdentifier);
+ if (hook !== undefined && hook.call(declarator.init)) {
+ // renaming with "var a = b;"
+ const hook = this.hooks.rename.get(renameIdentifier);
+ if (hook === undefined || !hook.call(declarator.init)) {
+ this.setVariable(declarator.id.name, renameIdentifier);
+ }
+ break;
+ }
+ }
+ if (!this.hooks.declarator.call(declarator, statement)) {
+ this.walkPattern(declarator.id);
+ if (declarator.init) this.walkExpression(declarator.init);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param {ClassDeclaration} statement class declaration
+ */
+ blockPreWalkClassDeclaration(statement) {
+ if (statement.id) {
+ this.defineVariable(statement.id.name);
+ }
+ }
+
+ /**
+ * @param {ClassDeclaration} statement class declaration
+ */
+ walkClassDeclaration(statement) {
+ this.walkClass(statement);
+ }
+
+ /**
+ * @param {SwitchCase[]} switchCases switch statement
+ */
+ preWalkSwitchCases(switchCases) {
+ for (let index = 0, len = switchCases.length; index < len; index++) {
+ const switchCase = switchCases[index];
+ this.preWalkStatements(switchCase.consequent);
+ }
+ }
+
+ /**
+ * @param {SwitchCase[]} switchCases switch statement
+ */
+ walkSwitchCases(switchCases) {
+ this.inBlockScope(() => {
+ const len = switchCases.length;
+
+ // we need to pre walk all statements first since we can have invalid code
+ // import A from "module";
+ // switch(1) {
+ // case 1:
+ // console.log(A); // should fail at runtime
+ // case 2:
+ // const A = 1;
+ // }
+ for (let index = 0; index < len; index++) {
+ const switchCase = switchCases[index];
+
+ if (switchCase.consequent.length > 0) {
+ const prev = this.prevStatement;
+ this.blockPreWalkStatements(switchCase.consequent);
+ this.prevStatement = prev;
+ }
+ }
+
+ for (let index = 0; index < len; index++) {
+ const switchCase = switchCases[index];
+
+ if (switchCase.test) {
+ this.walkExpression(switchCase.test);
+ }
+ if (switchCase.consequent.length > 0) {
+ this.walkStatements(switchCase.consequent);
+ }
+ }
+ });
+ }
+
+ /**
+ * @param {CatchClause} catchClause catch clause
+ */
+ preWalkCatchClause(catchClause) {
+ this.preWalkStatement(catchClause.body);
+ }
+
+ /**
+ * @param {CatchClause} catchClause catch clause
+ */
+ walkCatchClause(catchClause) {
+ this.inBlockScope(() => {
+ // Error binding is optional in catch clause since ECMAScript 2019
+ if (catchClause.param !== null) {
+ this.enterPattern(catchClause.param, ident => {
+ this.defineVariable(ident);
+ });
+ this.walkPattern(catchClause.param);
+ }
+ const prev = this.prevStatement;
+ this.blockPreWalkStatement(catchClause.body);
+ this.prevStatement = prev;
+ this.walkStatement(catchClause.body);
+ });
+ }
+
+ /**
+ * @param {Pattern} pattern pattern
+ */
+ walkPattern(pattern) {
+ switch (pattern.type) {
+ case "ArrayPattern":
+ this.walkArrayPattern(pattern);
+ break;
+ case "AssignmentPattern":
+ this.walkAssignmentPattern(pattern);
+ break;
+ case "MemberExpression":
+ this.walkMemberExpression(pattern);
+ break;
+ case "ObjectPattern":
+ this.walkObjectPattern(pattern);
+ break;
+ case "RestElement":
+ this.walkRestElement(pattern);
+ break;
+ }
+ }
+
+ /**
+ * @param {AssignmentPattern} pattern assignment pattern
+ */
+ walkAssignmentPattern(pattern) {
+ this.walkExpression(pattern.right);
+ this.walkPattern(pattern.left);
+ }
+
+ walkObjectPattern(pattern) {
+ for (let i = 0, len = pattern.properties.length; i < len; i++) {
+ const prop = pattern.properties[i];
+ if (prop) {
+ if (prop.computed) this.walkExpression(prop.key);
+ if (prop.value) this.walkPattern(prop.value);
+ }
+ }
+ }
+
+ /**
+ * @param {ArrayPattern} pattern array pattern
+ */
+ walkArrayPattern(pattern) {
+ for (let i = 0, len = pattern.elements.length; i < len; i++) {
+ const element = pattern.elements[i];
+ if (element) this.walkPattern(element);
+ }
+ }
+
+ /**
+ * @param {RestElement} pattern rest element
+ */
+ walkRestElement(pattern) {
+ this.walkPattern(pattern.argument);
+ }
+
+ /**
+ * @param {(Expression | SpreadElement | null)[]} expressions expressions
+ */
+ walkExpressions(expressions) {
+ for (const expression of expressions) {
+ if (expression) {
+ this.walkExpression(expression);
+ }
+ }
+ }
+
+ /**
+ * @param {TODO} expression expression
+ */
+ walkExpression(expression) {
+ switch (expression.type) {
+ case "ArrayExpression":
+ this.walkArrayExpression(expression);
+ break;
+ case "ArrowFunctionExpression":
+ this.walkArrowFunctionExpression(expression);
+ break;
+ case "AssignmentExpression":
+ this.walkAssignmentExpression(expression);
+ break;
+ case "AwaitExpression":
+ this.walkAwaitExpression(expression);
+ break;
+ case "BinaryExpression":
+ this.walkBinaryExpression(expression);
+ break;
+ case "CallExpression":
+ this.walkCallExpression(expression);
+ break;
+ case "ChainExpression":
+ this.walkChainExpression(expression);
+ break;
+ case "ClassExpression":
+ this.walkClassExpression(expression);
+ break;
+ case "ConditionalExpression":
+ this.walkConditionalExpression(expression);
+ break;
+ case "FunctionExpression":
+ this.walkFunctionExpression(expression);
+ break;
+ case "Identifier":
+ this.walkIdentifier(expression);
+ break;
+ case "ImportExpression":
+ this.walkImportExpression(expression);
+ break;
+ case "LogicalExpression":
+ this.walkLogicalExpression(expression);
+ break;
+ case "MetaProperty":
+ this.walkMetaProperty(expression);
+ break;
+ case "MemberExpression":
+ this.walkMemberExpression(expression);
+ break;
+ case "NewExpression":
+ this.walkNewExpression(expression);
+ break;
+ case "ObjectExpression":
+ this.walkObjectExpression(expression);
+ break;
+ case "SequenceExpression":
+ this.walkSequenceExpression(expression);
+ break;
+ case "SpreadElement":
+ this.walkSpreadElement(expression);
+ break;
+ case "TaggedTemplateExpression":
+ this.walkTaggedTemplateExpression(expression);
+ break;
+ case "TemplateLiteral":
+ this.walkTemplateLiteral(expression);
+ break;
+ case "ThisExpression":
+ this.walkThisExpression(expression);
+ break;
+ case "UnaryExpression":
+ this.walkUnaryExpression(expression);
+ break;
+ case "UpdateExpression":
+ this.walkUpdateExpression(expression);
+ break;
+ case "YieldExpression":
+ this.walkYieldExpression(expression);
+ break;
+ }
+ }
+
+ /**
+ * @param {AwaitExpression} expression await expression
+ */
+ walkAwaitExpression(expression) {
+ if (this.scope.topLevelScope === true)
+ this.hooks.topLevelAwait.call(expression);
+ this.walkExpression(expression.argument);
+ }
+
+ /**
+ * @param {ArrayExpression} expression array expression
+ */
+ walkArrayExpression(expression) {
+ if (expression.elements) {
+ this.walkExpressions(expression.elements);
+ }
+ }
+
+ /**
+ * @param {SpreadElement} expression spread element
+ */
+ walkSpreadElement(expression) {
+ if (expression.argument) {
+ this.walkExpression(expression.argument);
+ }
+ }
+
+ /**
+ * @param {ObjectExpression} expression object expression
+ */
+ walkObjectExpression(expression) {
+ for (
+ let propIndex = 0, len = expression.properties.length;
+ propIndex < len;
+ propIndex++
+ ) {
+ const prop = expression.properties[propIndex];
+ this.walkProperty(prop);
+ }
+ }
+
+ /**
+ * @param {Property | SpreadElement} prop property or spread element
+ */
+ walkProperty(prop) {
+ if (prop.type === "SpreadElement") {
+ this.walkExpression(prop.argument);
+ return;
+ }
+ if (prop.computed) {
+ this.walkExpression(prop.key);
+ }
+ if (prop.shorthand && prop.value && prop.value.type === "Identifier") {
+ this.scope.inShorthand = prop.value.name;
+ this.walkIdentifier(prop.value);
+ this.scope.inShorthand = false;
+ } else {
+ this.walkExpression(prop.value);
+ }
+ }
+
+ /**
+ * @param {FunctionExpression} expression arrow function expression
+ */
+ walkFunctionExpression(expression) {
+ const wasTopLevel = this.scope.topLevelScope;
+ this.scope.topLevelScope = false;
+ const scopeParams = [...expression.params];
+
+ // Add function name in scope for recursive calls
+ if (expression.id) {
+ scopeParams.push(expression.id);
+ }
+
+ this.inFunctionScope(true, scopeParams, () => {
+ for (const param of expression.params) {
+ this.walkPattern(param);
+ }
+ if (expression.body.type === "BlockStatement") {
+ this.detectMode(expression.body.body);
+ const prev = this.prevStatement;
+ this.preWalkStatement(expression.body);
+ this.prevStatement = prev;
+ this.walkStatement(expression.body);
+ } else {
+ this.walkExpression(expression.body);
+ }
+ });
+ this.scope.topLevelScope = wasTopLevel;
+ }
+
+ /**
+ * @param {ArrowFunctionExpression} expression arrow function expression
+ */
+ walkArrowFunctionExpression(expression) {
+ const wasTopLevel = this.scope.topLevelScope;
+ this.scope.topLevelScope = wasTopLevel ? "arrow" : false;
+ this.inFunctionScope(false, expression.params, () => {
+ for (const param of expression.params) {
+ this.walkPattern(param);
+ }
+ if (expression.body.type === "BlockStatement") {
+ this.detectMode(expression.body.body);
+ const prev = this.prevStatement;
+ this.preWalkStatement(expression.body);
+ this.prevStatement = prev;
+ this.walkStatement(expression.body);
+ } else {
+ this.walkExpression(expression.body);
+ }
+ });
+ this.scope.topLevelScope = wasTopLevel;
+ }
+
+ /**
+ * @param {SequenceExpression} expression the sequence
+ */
+ walkSequenceExpression(expression) {
+ if (!expression.expressions) return;
+ // We treat sequence expressions like statements when they are one statement level
+ // This has some benefits for optimizations that only work on statement level
+ const currentStatement = this.statementPath[this.statementPath.length - 1];
+ if (
+ currentStatement === expression ||
+ (currentStatement.type === "ExpressionStatement" &&
+ currentStatement.expression === expression)
+ ) {
+ const old = this.statementPath.pop();
+ for (const expr of expression.expressions) {
+ this.statementPath.push(expr);
+ this.walkExpression(expr);
+ this.statementPath.pop();
+ }
+ this.statementPath.push(old);
+ } else {
+ this.walkExpressions(expression.expressions);
+ }
+ }
+
+ /**
+ * @param {UpdateExpression} expression the update expression
+ */
+ walkUpdateExpression(expression) {
+ this.walkExpression(expression.argument);
+ }
+
+ /**
+ * @param {UnaryExpression} expression the unary expression
+ */
+ walkUnaryExpression(expression) {
+ if (expression.operator === "typeof") {
+ const result = this.callHooksForExpression(
+ this.hooks.typeof,
+ expression.argument,
+ expression
+ );
+ if (result === true) return;
+ if (expression.argument.type === "ChainExpression") {
+ const result = this.callHooksForExpression(
+ this.hooks.typeof,
+ expression.argument.expression,
+ expression
+ );
+ if (result === true) return;
+ }
+ }
+ this.walkExpression(expression.argument);
+ }
+
+ /**
+ * @param {LogicalExpression | BinaryExpression} expression the expression
+ */
+ walkLeftRightExpression(expression) {
+ this.walkExpression(expression.left);
+ this.walkExpression(expression.right);
+ }
+
+ /**
+ * @param {BinaryExpression} expression the binary expression
+ */
+ walkBinaryExpression(expression) {
+ if (this.hooks.binaryExpression.call(expression) === undefined) {
+ this.walkLeftRightExpression(expression);
+ }
+ }
+
+ /**
+ * @param {LogicalExpression} expression the logical expression
+ */
+ walkLogicalExpression(expression) {
+ const result = this.hooks.expressionLogicalOperator.call(expression);
+ if (result === undefined) {
+ this.walkLeftRightExpression(expression);
+ } else {
+ if (result) {
+ this.walkExpression(expression.right);
+ }
+ }
+ }
+
+ /**
+ * @param {AssignmentExpression} expression assignment expression
+ */
+ walkAssignmentExpression(expression) {
+ if (expression.left.type === "Identifier") {
+ const renameIdentifier = this.getRenameIdentifier(expression.right);
+ if (renameIdentifier) {
+ if (
+ this.callHooksForInfo(
+ this.hooks.canRename,
+ renameIdentifier,
+ expression.right
+ )
+ ) {
+ // renaming "a = b;"
+ if (
+ !this.callHooksForInfo(
+ this.hooks.rename,
+ renameIdentifier,
+ expression.right
+ )
+ ) {
+ this.setVariable(
+ expression.left.name,
+ typeof renameIdentifier === "string"
+ ? this.getVariableInfo(renameIdentifier)
+ : renameIdentifier
+ );
+ }
+ return;
+ }
+ }
+ this.walkExpression(expression.right);
+ this.enterPattern(expression.left, (name, decl) => {
+ if (!this.callHooksForName(this.hooks.assign, name, expression)) {
+ this.walkExpression(expression.left);
+ }
+ });
+ return;
+ }
+ if (expression.left.type.endsWith("Pattern")) {
+ this.walkExpression(expression.right);
+ this.enterPattern(expression.left, (name, decl) => {
+ if (!this.callHooksForName(this.hooks.assign, name, expression)) {
+ this.defineVariable(name);
+ }
+ });
+ this.walkPattern(expression.left);
+ } else if (expression.left.type === "MemberExpression") {
+ const exprName = this.getMemberExpressionInfo(
+ expression.left,
+ ALLOWED_MEMBER_TYPES_EXPRESSION
+ );
+ if (exprName) {
+ if (
+ this.callHooksForInfo(
+ this.hooks.assignMemberChain,
+ exprName.rootInfo,
+ expression,
+ exprName.getMembers()
+ )
+ ) {
+ return;
+ }
+ }
+ this.walkExpression(expression.right);
+ this.walkExpression(expression.left);
+ } else {
+ this.walkExpression(expression.right);
+ this.walkExpression(expression.left);
+ }
+ }
+
+ /**
+ * @param {ConditionalExpression} expression conditional expression
+ */
+ walkConditionalExpression(expression) {
+ const result = this.hooks.expressionConditionalOperator.call(expression);
+ if (result === undefined) {
+ this.walkExpression(expression.test);
+ this.walkExpression(expression.consequent);
+ if (expression.alternate) {
+ this.walkExpression(expression.alternate);
+ }
+ } else {
+ if (result) {
+ this.walkExpression(expression.consequent);
+ } else if (expression.alternate) {
+ this.walkExpression(expression.alternate);
+ }
+ }
+ }
+
+ /**
+ * @param {NewExpression} expression new expression
+ */
+ walkNewExpression(expression) {
+ const result = this.callHooksForExpression(
+ this.hooks.new,
+ expression.callee,
+ expression
+ );
+ if (result === true) return;
+ this.walkExpression(expression.callee);
+ if (expression.arguments) {
+ this.walkExpressions(expression.arguments);
+ }
+ }
+
+ /**
+ * @param {YieldExpression} expression yield expression
+ */
+ walkYieldExpression(expression) {
+ if (expression.argument) {
+ this.walkExpression(expression.argument);
+ }
+ }
+
+ /**
+ * @param {TemplateLiteral} expression template literal
+ */
+ walkTemplateLiteral(expression) {
+ if (expression.expressions) {
+ this.walkExpressions(expression.expressions);
+ }
+ }
+
+ /**
+ * @param {TaggedTemplateExpression} expression tagged template expression
+ */
+ walkTaggedTemplateExpression(expression) {
+ if (expression.tag) {
+ this.scope.inTaggedTemplateTag = true;
+ this.walkExpression(expression.tag);
+ this.scope.inTaggedTemplateTag = false;
+ }
+ if (expression.quasi && expression.quasi.expressions) {
+ this.walkExpressions(expression.quasi.expressions);
+ }
+ }
+
+ /**
+ * @param {ClassExpression} expression the class expression
+ */
+ walkClassExpression(expression) {
+ this.walkClass(expression);
+ }
+
+ /**
+ * @param {ChainExpression} expression expression
+ */
+ walkChainExpression(expression) {
+ const result = this.hooks.optionalChaining.call(expression);
+
+ if (result === undefined) {
+ if (expression.expression.type === "CallExpression") {
+ this.walkCallExpression(expression.expression);
+ } else {
+ this.walkMemberExpression(expression.expression);
+ }
+ }
+ }
+
+ _walkIIFE(functionExpression, options, currentThis) {
+ const getVarInfo = argOrThis => {
+ const renameIdentifier = this.getRenameIdentifier(argOrThis);
+ if (renameIdentifier) {
+ if (
+ this.callHooksForInfo(
+ this.hooks.canRename,
+ renameIdentifier,
+ argOrThis
+ )
+ ) {
+ if (
+ !this.callHooksForInfo(
+ this.hooks.rename,
+ renameIdentifier,
+ argOrThis
+ )
+ ) {
+ return typeof renameIdentifier === "string"
+ ? this.getVariableInfo(renameIdentifier)
+ : renameIdentifier;
+ }
+ }
+ }
+ this.walkExpression(argOrThis);
+ };
+ const { params, type } = functionExpression;
+ const arrow = type === "ArrowFunctionExpression";
+ const renameThis = currentThis ? getVarInfo(currentThis) : null;
+ const varInfoForArgs = options.map(getVarInfo);
+ const wasTopLevel = this.scope.topLevelScope;
+ this.scope.topLevelScope = wasTopLevel && arrow ? "arrow" : false;
+ const scopeParams = params.filter(
+ (identifier, idx) => !varInfoForArgs[idx]
+ );
+
+ // Add function name in scope for recursive calls
+ if (functionExpression.id) {
+ scopeParams.push(functionExpression.id.name);
+ }
+
+ this.inFunctionScope(true, scopeParams, () => {
+ if (renameThis && !arrow) {
+ this.setVariable("this", renameThis);
+ }
+ for (let i = 0; i < varInfoForArgs.length; i++) {
+ const varInfo = varInfoForArgs[i];
+ if (!varInfo) continue;
+ if (!params[i] || params[i].type !== "Identifier") continue;
+ this.setVariable(params[i].name, varInfo);
+ }
+ if (functionExpression.body.type === "BlockStatement") {
+ this.detectMode(functionExpression.body.body);
+ const prev = this.prevStatement;
+ this.preWalkStatement(functionExpression.body);
+ this.prevStatement = prev;
+ this.walkStatement(functionExpression.body);
+ } else {
+ this.walkExpression(functionExpression.body);
+ }
+ });
+ this.scope.topLevelScope = wasTopLevel;
+ }
+
+ /**
+ * @param {ImportExpression} expression import expression
+ */
+ walkImportExpression(expression) {
+ let result = this.hooks.importCall.call(expression);
+ if (result === true) return;
+
+ this.walkExpression(expression.source);
+ }
+
+ walkCallExpression(expression) {
+ const isSimpleFunction = fn => {
+ return fn.params.every(p => p.type === "Identifier");
+ };
+ if (
+ expression.callee.type === "MemberExpression" &&
+ expression.callee.object.type.endsWith("FunctionExpression") &&
+ !expression.callee.computed &&
+ (expression.callee.property.name === "call" ||
+ expression.callee.property.name === "bind") &&
+ expression.arguments.length > 0 &&
+ isSimpleFunction(expression.callee.object)
+ ) {
+ // (function(…) { }.call/bind(?, …))
+ this._walkIIFE(
+ expression.callee.object,
+ expression.arguments.slice(1),
+ expression.arguments[0]
+ );
+ } else if (
+ expression.callee.type.endsWith("FunctionExpression") &&
+ isSimpleFunction(expression.callee)
+ ) {
+ // (function(…) { }(…))
+ this._walkIIFE(expression.callee, expression.arguments, null);
+ } else {
+ if (expression.callee.type === "MemberExpression") {
+ const exprInfo = this.getMemberExpressionInfo(
+ expression.callee,
+ ALLOWED_MEMBER_TYPES_CALL_EXPRESSION
+ );
+ if (exprInfo && exprInfo.type === "call") {
+ const result = this.callHooksForInfo(
+ this.hooks.callMemberChainOfCallMemberChain,
+ exprInfo.rootInfo,
+ expression,
+ exprInfo.getCalleeMembers(),
+ exprInfo.call,
+ exprInfo.getMembers(),
+ exprInfo.getMemberRanges()
+ );
+ if (result === true) return;
+ }
+ }
+ const callee = this.evaluateExpression(expression.callee);
+ if (callee.isIdentifier()) {
+ const result1 = this.callHooksForInfo(
+ this.hooks.callMemberChain,
+ callee.rootInfo,
+ expression,
+ callee.getMembers(),
+ callee.getMembersOptionals
+ ? callee.getMembersOptionals()
+ : callee.getMembers().map(() => false),
+ callee.getMemberRanges ? callee.getMemberRanges() : []
+ );
+ if (result1 === true) return;
+ const result2 = this.callHooksForInfo(
+ this.hooks.call,
+ callee.identifier,
+ expression
+ );
+ if (result2 === true) return;
+ }
+
+ if (expression.callee) {
+ if (expression.callee.type === "MemberExpression") {
+ // because of call context we need to walk the call context as expression
+ this.walkExpression(expression.callee.object);
+ if (expression.callee.computed === true)
+ this.walkExpression(expression.callee.property);
+ } else {
+ this.walkExpression(expression.callee);
+ }
+ }
+ if (expression.arguments) this.walkExpressions(expression.arguments);
+ }
+ }
+
+ /**
+ * @param {MemberExpression} expression member expression
+ */
+ walkMemberExpression(expression) {
+ const exprInfo = this.getMemberExpressionInfo(
+ expression,
+ ALLOWED_MEMBER_TYPES_ALL
+ );
+ if (exprInfo) {
+ switch (exprInfo.type) {
+ case "expression": {
+ const result1 = this.callHooksForInfo(
+ this.hooks.expression,
+ exprInfo.name,
+ expression
+ );
+ if (result1 === true) return;
+ const members = exprInfo.getMembers();
+ const membersOptionals = exprInfo.getMembersOptionals();
+ const memberRanges = exprInfo.getMemberRanges();
+ const result2 = this.callHooksForInfo(
+ this.hooks.expressionMemberChain,
+ exprInfo.rootInfo,
+ expression,
+ members,
+ membersOptionals,
+ memberRanges
+ );
+ if (result2 === true) return;
+ this.walkMemberExpressionWithExpressionName(
+ expression,
+ exprInfo.name,
+ exprInfo.rootInfo,
+ members.slice(),
+ () =>
+ this.callHooksForInfo(
+ this.hooks.unhandledExpressionMemberChain,
+ exprInfo.rootInfo,
+ expression,
+ members
+ )
+ );
+ return;
+ }
+ case "call": {
+ const result = this.callHooksForInfo(
+ this.hooks.memberChainOfCallMemberChain,
+ exprInfo.rootInfo,
+ expression,
+ exprInfo.getCalleeMembers(),
+ exprInfo.call,
+ exprInfo.getMembers(),
+ exprInfo.getMemberRanges()
+ );
+ if (result === true) return;
+ // Fast skip over the member chain as we already called memberChainOfCallMemberChain
+ // and call computed property are literals anyway
+ this.walkExpression(exprInfo.call);
+ return;
+ }
+ }
+ }
+ this.walkExpression(expression.object);
+ if (expression.computed === true) this.walkExpression(expression.property);
+ }
+
+ walkMemberExpressionWithExpressionName(
+ expression,
+ name,
+ rootInfo,
+ members,
+ onUnhandled
+ ) {
+ if (expression.object.type === "MemberExpression") {
+ // optimize the case where expression.object is a MemberExpression too.
+ // we can keep info here when calling walkMemberExpression directly
+ const property =
+ expression.property.name || `${expression.property.value}`;
+ name = name.slice(0, -property.length - 1);
+ members.pop();
+ const result = this.callHooksForInfo(
+ this.hooks.expression,
+ name,
+ expression.object
+ );
+ if (result === true) return;
+ this.walkMemberExpressionWithExpressionName(
+ expression.object,
+ name,
+ rootInfo,
+ members,
+ onUnhandled
+ );
+ } else if (!onUnhandled || !onUnhandled()) {
+ this.walkExpression(expression.object);
+ }
+ if (expression.computed === true) this.walkExpression(expression.property);
+ }
+
+ /**
+ * @param {ThisExpression} expression this expression
+ */
+ walkThisExpression(expression) {
+ this.callHooksForName(this.hooks.expression, "this", expression);
+ }
+
+ /**
+ * @param {Identifier} expression identifier
+ */
+ walkIdentifier(expression) {
+ this.callHooksForName(this.hooks.expression, expression.name, expression);
+ }
+
+ /**
+ * @param {MetaProperty} metaProperty meta property
+ */
+ walkMetaProperty(metaProperty) {
+ this.hooks.expression.for(getRootName(metaProperty)).call(metaProperty);
+ }
+
+ callHooksForExpression(hookMap, expr, ...args) {
+ return this.callHooksForExpressionWithFallback(
+ hookMap,
+ expr,
+ undefined,
+ undefined,
+ ...args
+ );
+ }
+
+ /**
+ * @template T
+ * @template R
+ * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
+ * @param {MemberExpression} expr expression info
+ * @param {(function(string, string | ScopeInfo | VariableInfo, function(): string[]): any) | undefined} fallback callback when variable in not handled by hooks
+ * @param {(function(string): any) | undefined} defined callback when variable is defined
+ * @param {AsArray<T>} args args for the hook
+ * @returns {R | undefined} result of hook
+ */
+ callHooksForExpressionWithFallback(
+ hookMap,
+ expr,
+ fallback,
+ defined,
+ ...args
+ ) {
+ const exprName = this.getMemberExpressionInfo(
+ expr,
+ ALLOWED_MEMBER_TYPES_EXPRESSION
+ );
+ if (exprName !== undefined) {
+ const members = exprName.getMembers();
+ return this.callHooksForInfoWithFallback(
+ hookMap,
+ members.length === 0 ? exprName.rootInfo : exprName.name,
+ fallback &&
+ (name => fallback(name, exprName.rootInfo, exprName.getMembers)),
+ defined && (() => defined(exprName.name)),
+ ...args
+ );
+ }
+ }
+
+ /**
+ * @template T
+ * @template R
+ * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
+ * @param {string} name key in map
+ * @param {AsArray<T>} args args for the hook
+ * @returns {R | undefined} result of hook
+ */
+ callHooksForName(hookMap, name, ...args) {
+ return this.callHooksForNameWithFallback(
+ hookMap,
+ name,
+ undefined,
+ undefined,
+ ...args
+ );
+ }
+
+ /**
+ * @template T
+ * @template R
+ * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks that should be called
+ * @param {ExportedVariableInfo} info variable info
+ * @param {AsArray<T>} args args for the hook
+ * @returns {R | undefined} result of hook
+ */
+ callHooksForInfo(hookMap, info, ...args) {
+ return this.callHooksForInfoWithFallback(
+ hookMap,
+ info,
+ undefined,
+ undefined,
+ ...args
+ );
+ }
+
+ /**
+ * @template T
+ * @template R
+ * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
+ * @param {ExportedVariableInfo} info variable info
+ * @param {(function(string): any) | undefined} fallback callback when variable in not handled by hooks
+ * @param {(function(): any) | undefined} defined callback when variable is defined
+ * @param {AsArray<T>} args args for the hook
+ * @returns {R | undefined} result of hook
+ */
+ callHooksForInfoWithFallback(hookMap, info, fallback, defined, ...args) {
+ let name;
+ if (typeof info === "string") {
+ name = info;
+ } else {
+ if (!(info instanceof VariableInfo)) {
+ if (defined !== undefined) {
+ return defined();
+ }
+ return;
+ }
+ let tagInfo = info.tagInfo;
+ while (tagInfo !== undefined) {
+ const hook = hookMap.get(tagInfo.tag);
+ if (hook !== undefined) {
+ this.currentTagData = tagInfo.data;
+ const result = hook.call(...args);
+ this.currentTagData = undefined;
+ if (result !== undefined) return result;
+ }
+ tagInfo = tagInfo.next;
+ }
+ if (info.freeName === true) {
+ if (defined !== undefined) {
+ return defined();
+ }
+ return;
+ }
+ name = info.freeName;
+ }
+ const hook = hookMap.get(name);
+ if (hook !== undefined) {
+ const result = hook.call(...args);
+ if (result !== undefined) return result;
+ }
+ if (fallback !== undefined) {
+ return fallback(name);
+ }
+ }
+
+ /**
+ * @template T
+ * @template R
+ * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
+ * @param {string} name key in map
+ * @param {(function(string): any) | undefined} fallback callback when variable in not handled by hooks
+ * @param {(function(): any) | undefined} defined callback when variable is defined
+ * @param {AsArray<T>} args args for the hook
+ * @returns {R | undefined} result of hook
+ */
+ callHooksForNameWithFallback(hookMap, name, fallback, defined, ...args) {
+ return this.callHooksForInfoWithFallback(
+ hookMap,
+ this.getVariableInfo(name),
+ fallback,
+ defined,
+ ...args
+ );
+ }
+
+ /**
+ * @deprecated
+ * @param {any} params scope params
+ * @param {function(): void} fn inner function
+ * @returns {void}
+ */
+ inScope(params, fn) {
+ const oldScope = this.scope;
+ this.scope = {
+ topLevelScope: oldScope.topLevelScope,
+ inTry: false,
+ inShorthand: false,
+ inTaggedTemplateTag: false,
+ isStrict: oldScope.isStrict,
+ isAsmJs: oldScope.isAsmJs,
+ definitions: oldScope.definitions.createChild()
+ };
+
+ this.undefineVariable("this");
+
+ this.enterPatterns(params, (ident, pattern) => {
+ this.defineVariable(ident);
+ });
+
+ fn();
+
+ this.scope = oldScope;
+ }
+
+ /**
+ * @param {boolean} hasThis true, when this is defined
+ * @param {any} params scope params
+ * @param {function(): void} fn inner function
+ * @returns {void}
+ */
+ inClassScope(hasThis, params, fn) {
+ const oldScope = this.scope;
+ this.scope = {
+ topLevelScope: oldScope.topLevelScope,
+ inTry: false,
+ inShorthand: false,
+ inTaggedTemplateTag: false,
+ isStrict: oldScope.isStrict,
+ isAsmJs: oldScope.isAsmJs,
+ definitions: oldScope.definitions.createChild()
+ };
+
+ if (hasThis) {
+ this.undefineVariable("this");
+ }
+
+ this.enterPatterns(params, (ident, pattern) => {
+ this.defineVariable(ident);
+ });
+
+ fn();
+
+ this.scope = oldScope;
+ }
+
+ /**
+ * @param {boolean} hasThis true, when this is defined
+ * @param {any} params scope params
+ * @param {function(): void} fn inner function
+ * @returns {void}
+ */
+ inFunctionScope(hasThis, params, fn) {
+ const oldScope = this.scope;
+ this.scope = {
+ topLevelScope: oldScope.topLevelScope,
+ inTry: false,
+ inShorthand: false,
+ inTaggedTemplateTag: false,
+ isStrict: oldScope.isStrict,
+ isAsmJs: oldScope.isAsmJs,
+ definitions: oldScope.definitions.createChild()
+ };
+
+ if (hasThis) {
+ this.undefineVariable("this");
+ }
+
+ this.enterPatterns(params, (ident, pattern) => {
+ this.defineVariable(ident);
+ });
+
+ fn();
+
+ this.scope = oldScope;
+ }
+
+ /**
+ * @param {function(): void} fn inner function
+ * @returns {void}
+ */
+ inBlockScope(fn) {
+ const oldScope = this.scope;
+ this.scope = {
+ topLevelScope: oldScope.topLevelScope,
+ inTry: oldScope.inTry,
+ inShorthand: false,
+ inTaggedTemplateTag: false,
+ isStrict: oldScope.isStrict,
+ isAsmJs: oldScope.isAsmJs,
+ definitions: oldScope.definitions.createChild()
+ };
+
+ fn();
+
+ this.scope = oldScope;
+ }
+
+ /**
+ * @param {Array<Directive | Statement | ModuleDeclaration>} statements statements
+ */
+ detectMode(statements) {
+ const isLiteral =
+ statements.length >= 1 &&
+ statements[0].type === "ExpressionStatement" &&
+ statements[0].expression.type === "Literal";
+ if (
+ isLiteral &&
+ /** @type {Literal} */
+ (/** @type {ExpressionStatement} */ (statements[0]).expression).value ===
+ "use strict"
+ ) {
+ this.scope.isStrict = true;
+ }
+ if (
+ isLiteral &&
+ /** @type {Literal} */
+ (/** @type {ExpressionStatement} */ (statements[0]).expression).value ===
+ "use asm"
+ ) {
+ this.scope.isAsmJs = true;
+ }
+ }
+
+ enterPatterns(patterns, onIdent) {
+ for (const pattern of patterns) {
+ if (typeof pattern !== "string") {
+ this.enterPattern(pattern, onIdent);
+ } else if (pattern) {
+ onIdent(pattern);
+ }
+ }
+ }
+
+ enterPattern(pattern, onIdent) {
+ if (!pattern) return;
+ switch (pattern.type) {
+ case "ArrayPattern":
+ this.enterArrayPattern(pattern, onIdent);
+ break;
+ case "AssignmentPattern":
+ this.enterAssignmentPattern(pattern, onIdent);
+ break;
+ case "Identifier":
+ this.enterIdentifier(pattern, onIdent);
+ break;
+ case "ObjectPattern":
+ this.enterObjectPattern(pattern, onIdent);
+ break;
+ case "RestElement":
+ this.enterRestElement(pattern, onIdent);
+ break;
+ case "Property":
+ if (pattern.shorthand && pattern.value.type === "Identifier") {
+ this.scope.inShorthand = pattern.value.name;
+ this.enterIdentifier(pattern.value, onIdent);
+ this.scope.inShorthand = false;
+ } else {
+ this.enterPattern(pattern.value, onIdent);
+ }
+ break;
+ }
+ }
+
+ /**
+ * @param {Identifier} pattern identifier pattern
+ * @param {TODO} onIdent callback
+ */
+ enterIdentifier(pattern, onIdent) {
+ if (!this.callHooksForName(this.hooks.pattern, pattern.name, pattern)) {
+ onIdent(pattern.name, pattern);
+ }
+ }
+
+ /**
+ * @param {ObjectPattern} pattern object pattern
+ * @param {TODO} onIdent callback
+ */
+ enterObjectPattern(pattern, onIdent) {
+ for (
+ let propIndex = 0, len = pattern.properties.length;
+ propIndex < len;
+ propIndex++
+ ) {
+ const prop = pattern.properties[propIndex];
+ this.enterPattern(prop, onIdent);
+ }
+ }
+
+ /**
+ * @param {ArrayPattern} pattern object pattern
+ * @param {TODO} onIdent callback
+ */
+ enterArrayPattern(pattern, onIdent) {
+ for (
+ let elementIndex = 0, len = pattern.elements.length;
+ elementIndex < len;
+ elementIndex++
+ ) {
+ const element = pattern.elements[elementIndex];
+ // TODO check on `null`?
+ this.enterPattern(element, onIdent);
+ }
+ }
+
+ /**
+ * @param {RestElement} pattern object pattern
+ * @param {TODO} onIdent callback
+ */
+ enterRestElement(pattern, onIdent) {
+ this.enterPattern(pattern.argument, onIdent);
+ }
+
+ /**
+ * @param {AssignmentPattern} pattern object pattern
+ * @param {TODO} onIdent callback
+ */
+ enterAssignmentPattern(pattern, onIdent) {
+ this.enterPattern(pattern.left, onIdent);
+ }
+
+ /**
+ * @param {TODO} expression expression node
+ * @returns {BasicEvaluatedExpression} evaluation result
+ */
+ evaluateExpression(expression) {
+ try {
+ const hook = this.hooks.evaluate.get(expression.type);
+ if (hook !== undefined) {
+ const result = hook.call(expression);
+ if (result !== undefined && result !== null) {
+ result.setExpression(expression);
+ return result;
+ }
+ }
+ } catch (e) {
+ console.warn(e);
+ // ignore error
+ }
+ return new BasicEvaluatedExpression()
+ .setRange(/** @type {Range} */ (expression.range))
+ .setExpression(expression);
+ }
+
+ /**
+ * @param {Expression} expression expression
+ * @returns {string} parsed string
+ */
+ parseString(expression) {
+ switch (expression.type) {
+ case "BinaryExpression":
+ if (expression.operator === "+") {
+ return (
+ this.parseString(expression.left) +
+ this.parseString(expression.right)
+ );
+ }
+ break;
+ case "Literal":
+ return expression.value + "";
+ }
+ throw new Error(
+ expression.type + " is not supported as parameter for require"
+ );
+ }
+
+ parseCalculatedString(expression) {
+ switch (expression.type) {
+ case "BinaryExpression":
+ if (expression.operator === "+") {
+ const left = this.parseCalculatedString(expression.left);
+ const right = this.parseCalculatedString(expression.right);
+ if (left.code) {
+ return {
+ range: left.range,
+ value: left.value,
+ code: true,
+ conditional: false
+ };
+ } else if (right.code) {
+ return {
+ range: [
+ left.range[0],
+ right.range ? right.range[1] : left.range[1]
+ ],
+ value: left.value + right.value,
+ code: true,
+ conditional: false
+ };
+ } else {
+ return {
+ range: [left.range[0], right.range[1]],
+ value: left.value + right.value,
+ code: false,
+ conditional: false
+ };
+ }
+ }
+ break;
+ case "ConditionalExpression": {
+ const consequent = this.parseCalculatedString(expression.consequent);
+ const alternate = this.parseCalculatedString(expression.alternate);
+ const items = [];
+ if (consequent.conditional) {
+ items.push(...consequent.conditional);
+ } else if (!consequent.code) {
+ items.push(consequent);
+ } else {
+ break;
+ }
+ if (alternate.conditional) {
+ items.push(...alternate.conditional);
+ } else if (!alternate.code) {
+ items.push(alternate);
+ } else {
+ break;
+ }
+ return {
+ range: undefined,
+ value: "",
+ code: true,
+ conditional: items
+ };
+ }
+ case "Literal":
+ return {
+ range: expression.range,
+ value: expression.value + "",
+ code: false,
+ conditional: false
+ };
+ }
+ return {
+ range: undefined,
+ value: "",
+ code: true,
+ conditional: false
+ };
+ }
+
+ /**
+ * @param {string | Buffer | PreparsedAst} source the source to parse
+ * @param {ParserState} state the parser state
+ * @returns {ParserState} the parser state
+ */
+ parse(source, state) {
+ let ast;
+ let comments;
+ const semicolons = new Set();
+ if (source === null) {
+ throw new Error("source must not be null");
+ }
+ if (Buffer.isBuffer(source)) {
+ source = source.toString("utf-8");
+ }
+ if (typeof source === "object") {
+ ast = /** @type {Program} */ (source);
+ comments = source.comments;
+ } else {
+ comments = [];
+ ast = JavascriptParser._parse(source, {
+ sourceType: this.sourceType,
+ onComment: comments,
+ onInsertedSemicolon: pos => semicolons.add(pos)
+ });
+ }
+
+ const oldScope = this.scope;
+ const oldState = this.state;
+ const oldComments = this.comments;
+ const oldSemicolons = this.semicolons;
+ const oldStatementPath = this.statementPath;
+ const oldPrevStatement = this.prevStatement;
+ this.scope = {
+ topLevelScope: true,
+ inTry: false,
+ inShorthand: false,
+ inTaggedTemplateTag: false,
+ isStrict: false,
+ isAsmJs: false,
+ definitions: new StackedMap()
+ };
+ /** @type {ParserState} */
+ this.state = state;
+ this.comments = comments;
+ this.semicolons = semicolons;
+ this.statementPath = [];
+ this.prevStatement = undefined;
+ if (this.hooks.program.call(ast, comments) === undefined) {
+ this.destructuringAssignmentProperties = new WeakMap();
+ this.detectMode(ast.body);
+ this.preWalkStatements(ast.body);
+ this.prevStatement = undefined;
+ this.blockPreWalkStatements(ast.body);
+ this.prevStatement = undefined;
+ this.walkStatements(ast.body);
+ this.destructuringAssignmentProperties = undefined;
+ }
+ this.hooks.finish.call(ast, comments);
+ this.scope = oldScope;
+ /** @type {ParserState} */
+ this.state = oldState;
+ this.comments = oldComments;
+ this.semicolons = oldSemicolons;
+ this.statementPath = oldStatementPath;
+ this.prevStatement = oldPrevStatement;
+ return state;
+ }
+
+ /**
+ * @param {string} source source code
+ * @returns {BasicEvaluatedExpression} evaluation result
+ */
+ evaluate(source) {
+ const ast = JavascriptParser._parse("(" + source + ")", {
+ sourceType: this.sourceType,
+ locations: false
+ });
+ if (ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement") {
+ throw new Error("evaluate: Source is not a expression");
+ }
+ return this.evaluateExpression(ast.body[0].expression);
+ }
+
+ /**
+ * @param {Expression | Declaration | PrivateIdentifier | null | undefined} expr an expression
+ * @param {number} commentsStartPos source position from which annotation comments are checked
+ * @returns {boolean} true, when the expression is pure
+ */
+ isPure(expr, commentsStartPos) {
+ if (!expr) return true;
+ const result = this.hooks.isPure
+ .for(expr.type)
+ .call(expr, commentsStartPos);
+ if (typeof result === "boolean") return result;
+ switch (expr.type) {
+ // TODO handle more cases
+ case "ClassDeclaration":
+ case "ClassExpression": {
+ if (expr.body.type !== "ClassBody") return false;
+ if (expr.superClass && !this.isPure(expr.superClass, expr.range[0])) {
+ return false;
+ }
+ const items =
+ /** @type {TODO[]} */
+ (expr.body.body);
+ return items.every(item => {
+ if (
+ item.computed &&
+ item.key &&
+ !this.isPure(item.key, item.range[0])
+ ) {
+ return false;
+ }
+
+ if (
+ item.static &&
+ item.value &&
+ !this.isPure(
+ item.value,
+ item.key ? item.key.range[1] : item.range[0]
+ )
+ ) {
+ return false;
+ }
+
+ if (item.type === "StaticBlock") {
+ return false;
+ }
+
+ return true;
+ });
+ }
+
+ case "FunctionDeclaration":
+ case "FunctionExpression":
+ case "ArrowFunctionExpression":
+ case "ThisExpression":
+ case "Literal":
+ case "TemplateLiteral":
+ case "Identifier":
+ case "PrivateIdentifier":
+ return true;
+
+ case "VariableDeclaration":
+ return expr.declarations.every(decl =>
+ this.isPure(decl.init, /** @type {Range} */ (decl.range)[0])
+ );
+
+ case "ConditionalExpression":
+ return (
+ this.isPure(expr.test, commentsStartPos) &&
+ this.isPure(
+ expr.consequent,
+ /** @type {Range} */ (expr.test.range)[1]
+ ) &&
+ this.isPure(
+ expr.alternate,
+ /** @type {Range} */ (expr.consequent.range)[1]
+ )
+ );
+
+ case "LogicalExpression":
+ return (
+ this.isPure(expr.left, commentsStartPos) &&
+ this.isPure(expr.right, /** @type {Range} */ (expr.left.range)[1])
+ );
+
+ case "SequenceExpression":
+ return expr.expressions.every(expr => {
+ const pureFlag = this.isPure(expr, commentsStartPos);
+ commentsStartPos = /** @type {Range} */ (expr.range)[1];
+ return pureFlag;
+ });
+
+ case "CallExpression": {
+ const pureFlag =
+ /** @type {Range} */ (expr.range)[0] - commentsStartPos > 12 &&
+ this.getComments([
+ commentsStartPos,
+ /** @type {Range} */ (expr.range)[0]
+ ]).some(
+ comment =>
+ comment.type === "Block" &&
+ /^\s*(#|@)__PURE__\s*$/.test(comment.value)
+ );
+ if (!pureFlag) return false;
+ commentsStartPos = /** @type {Range} */ (expr.callee.range)[1];
+ return expr.arguments.every(arg => {
+ if (arg.type === "SpreadElement") return false;
+ const pureFlag = this.isPure(arg, commentsStartPos);
+ commentsStartPos = /** @type {Range} */ (arg.range)[1];
+ return pureFlag;
+ });
+ }
+ }
+ const evaluated = this.evaluateExpression(expr);
+ return !evaluated.couldHaveSideEffects();
+ }
+
+ /**
+ * @param {Range} range range
+ * @returns {TODO[]} comments in the range
+ */
+ getComments(range) {
+ const [rangeStart, rangeEnd] = range;
+ const compare = (comment, needle) => comment.range[0] - needle;
+ let idx = binarySearchBounds.ge(this.comments, rangeStart, compare);
+ let commentsInRange = [];
+ while (this.comments[idx] && this.comments[idx].range[1] <= rangeEnd) {
+ commentsInRange.push(this.comments[idx]);
+ idx++;
+ }
+
+ return commentsInRange;
+ }
+
+ /**
+ * @param {number} pos source code position
+ * @returns {boolean} true when a semicolon has been inserted before this position, false if not
+ */
+ isAsiPosition(pos) {
+ const currentStatement = this.statementPath[this.statementPath.length - 1];
+ if (currentStatement === undefined) throw new Error("Not in statement");
+ return (
+ // Either asking directly for the end position of the current statement
+ (currentStatement.range[1] === pos && this.semicolons.has(pos)) ||
+ // Or asking for the start position of the current statement,
+ // here we have to check multiple things
+ (currentStatement.range[0] === pos &&
+ // is there a previous statement which might be relevant?
+ this.prevStatement !== undefined &&
+ // is the end position of the previous statement an ASI position?
+ this.semicolons.has(this.prevStatement.range[1]))
+ );
+ }
+
+ /**
+ * @param {number} pos source code position
+ * @returns {void}
+ */
+ unsetAsiPosition(pos) {
+ this.semicolons.delete(pos);
+ }
+
+ /**
+ * @param {Expression} expr expression
+ * @returns {boolean} true, when the expression is a statement level expression
+ */
+ isStatementLevelExpression(expr) {
+ const currentStatement = this.statementPath[this.statementPath.length - 1];
+ return (
+ expr === currentStatement ||
+ (currentStatement.type === "ExpressionStatement" &&
+ currentStatement.expression === expr)
+ );
+ }
+
+ getTagData(name, tag) {
+ const info = this.scope.definitions.get(name);
+ if (info instanceof VariableInfo) {
+ let tagInfo = info.tagInfo;
+ while (tagInfo !== undefined) {
+ if (tagInfo.tag === tag) return tagInfo.data;
+ tagInfo = tagInfo.next;
+ }
+ }
+ }
+
+ tagVariable(name, tag, data) {
+ const oldInfo = this.scope.definitions.get(name);
+ /** @type {VariableInfo} */
+ let newInfo;
+ if (oldInfo === undefined) {
+ newInfo = new VariableInfo(this.scope, name, {
+ tag,
+ data,
+ next: undefined
+ });
+ } else if (oldInfo instanceof VariableInfo) {
+ newInfo = new VariableInfo(oldInfo.declaredScope, oldInfo.freeName, {
+ tag,
+ data,
+ next: oldInfo.tagInfo
+ });
+ } else {
+ newInfo = new VariableInfo(oldInfo, true, {
+ tag,
+ data,
+ next: undefined
+ });
+ }
+ this.scope.definitions.set(name, newInfo);
+ }
+
+ /**
+ * @param {string} name variable name
+ */
+ defineVariable(name) {
+ const oldInfo = this.scope.definitions.get(name);
+ // Don't redefine variable in same scope to keep existing tags
+ if (oldInfo instanceof VariableInfo && oldInfo.declaredScope === this.scope)
+ return;
+ this.scope.definitions.set(name, this.scope);
+ }
+
+ /**
+ * @param {string} name variable name
+ */
+ undefineVariable(name) {
+ this.scope.definitions.delete(name);
+ }
+
+ /**
+ * @param {string} name variable name
+ * @returns {boolean} true, when variable is defined
+ */
+ isVariableDefined(name) {
+ const info = this.scope.definitions.get(name);
+ if (info === undefined) return false;
+ if (info instanceof VariableInfo) {
+ return info.freeName === true;
+ }
+ return true;
+ }
+
+ /**
+ * @param {string} name variable name
+ * @returns {ExportedVariableInfo} info for this variable
+ */
+ getVariableInfo(name) {
+ const value = this.scope.definitions.get(name);
+ if (value === undefined) {
+ return name;
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * @param {string} name variable name
+ * @param {ExportedVariableInfo} variableInfo new info for this variable
+ * @returns {void}
+ */
+ setVariable(name, variableInfo) {
+ if (typeof variableInfo === "string") {
+ if (variableInfo === name) {
+ this.scope.definitions.delete(name);
+ } else {
+ this.scope.definitions.set(
+ name,
+ new VariableInfo(this.scope, variableInfo, undefined)
+ );
+ }
+ } else {
+ this.scope.definitions.set(name, variableInfo);
+ }
+ }
+
+ /**
+ * @param {TagInfo} tagInfo tag info
+ * @returns {VariableInfo} variable info
+ */
+ evaluatedVariable(tagInfo) {
+ return new VariableInfo(this.scope, undefined, tagInfo);
+ }
+
+ /**
+ * @param {Range} range range of the comment
+ * @returns {TODO} TODO
+ */
+ parseCommentOptions(range) {
+ const comments = this.getComments(range);
+ if (comments.length === 0) {
+ return EMPTY_COMMENT_OPTIONS;
+ }
+ let options = {};
+ /** @type {unknown[]} */
+ let errors = [];
+ for (const comment of comments) {
+ const { value } = comment;
+ if (value && webpackCommentRegExp.test(value)) {
+ // try compile only if webpack options comment is present
+ try {
+ for (let [key, val] of Object.entries(
+ vm.runInNewContext(`(function(){return {${value}};})()`)
+ )) {
+ if (typeof val === "object" && val !== null) {
+ if (val.constructor.name === "RegExp") val = new RegExp(val);
+ else val = JSON.parse(JSON.stringify(val));
+ }
+ options[key] = val;
+ }
+ } catch (e) {
+ const newErr = new Error(String(e.message));
+ newErr.stack = String(e.stack);
+ Object.assign(newErr, { comment });
+ errors.push(newErr);
+ }
+ }
+ }
+ return { options, errors };
+ }
+
+ /**
+ * @param {MemberExpression} expression a member expression
+ * @returns {{ members: string[], object: Expression | Super, membersOptionals: boolean[], memberRanges: Range[] }} member names (reverse order) and remaining object
+ */
+ extractMemberExpressionChain(expression) {
+ /** @type {AnyNode} */
+ let expr = expression;
+ const members = [];
+ const membersOptionals = [];
+ const memberRanges = [];
+ while (expr.type === "MemberExpression") {
+ if (expr.computed) {
+ if (expr.property.type !== "Literal") break;
+ members.push(`${expr.property.value}`); // the literal
+ memberRanges.push(expr.object.range); // the range of the expression fragment before the literal
+ } else {
+ if (expr.property.type !== "Identifier") break;
+ members.push(expr.property.name); // the identifier
+ memberRanges.push(expr.object.range); // the range of the expression fragment before the identifier
+ }
+ membersOptionals.push(expr.optional);
+ expr = expr.object;
+ }
+
+ return {
+ members,
+ membersOptionals,
+ memberRanges,
+ object: expr
+ };
+ }
+
+ /**
+ * @param {string} varName variable name
+ * @returns {{name: string, info: VariableInfo | string} | undefined} name of the free variable and variable info for that
+ */
+ getFreeInfoFromVariable(varName) {
+ const info = this.getVariableInfo(varName);
+ let name;
+ if (info instanceof VariableInfo) {
+ name = info.freeName;
+ if (typeof name !== "string") return undefined;
+ } else if (typeof info !== "string") {
+ return undefined;
+ } else {
+ name = info;
+ }
+ return { info, name };
+ }
+
+ /** @typedef {{ type: "call", call: CallExpression, calleeName: string, rootInfo: string | VariableInfo, getCalleeMembers: () => string[], name: string, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[]}} CallExpressionInfo */
+ /** @typedef {{ type: "expression", rootInfo: string | VariableInfo, name: string, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[]}} ExpressionExpressionInfo */
+
+ /**
+ * @param {MemberExpression} expression a member expression
+ * @param {number} allowedTypes which types should be returned, presented in bit mask
+ * @returns {CallExpressionInfo | ExpressionExpressionInfo | undefined} expression info
+ */
+ getMemberExpressionInfo(expression, allowedTypes) {
+ const { object, members, membersOptionals, memberRanges } =
+ this.extractMemberExpressionChain(expression);
+ switch (object.type) {
+ case "CallExpression": {
+ if ((allowedTypes & ALLOWED_MEMBER_TYPES_CALL_EXPRESSION) === 0)
+ return undefined;
+ let callee = object.callee;
+ let rootMembers = EMPTY_ARRAY;
+ if (callee.type === "MemberExpression") {
+ ({ object: callee, members: rootMembers } =
+ this.extractMemberExpressionChain(callee));
+ }
+ const rootName = getRootName(callee);
+ if (!rootName) return undefined;
+ const result = this.getFreeInfoFromVariable(rootName);
+ if (!result) return undefined;
+ const { info: rootInfo, name: resolvedRoot } = result;
+ const calleeName = objectAndMembersToName(resolvedRoot, rootMembers);
+ return {
+ type: "call",
+ call: object,
+ calleeName,
+ rootInfo,
+ getCalleeMembers: memoize(() => rootMembers.reverse()),
+ name: objectAndMembersToName(`${calleeName}()`, members),
+ getMembers: memoize(() => members.reverse()),
+ getMembersOptionals: memoize(() => membersOptionals.reverse()),
+ getMemberRanges: memoize(() => memberRanges.reverse())
+ };
+ }
+ case "Identifier":
+ case "MetaProperty":
+ case "ThisExpression": {
+ if ((allowedTypes & ALLOWED_MEMBER_TYPES_EXPRESSION) === 0)
+ return undefined;
+ const rootName = getRootName(object);
+ if (!rootName) return undefined;
+
+ const result = this.getFreeInfoFromVariable(rootName);
+ if (!result) return undefined;
+ const { info: rootInfo, name: resolvedRoot } = result;
+ return {
+ type: "expression",
+ name: objectAndMembersToName(resolvedRoot, members),
+ rootInfo,
+ getMembers: memoize(() => members.reverse()),
+ getMembersOptionals: memoize(() => membersOptionals.reverse()),
+ getMemberRanges: memoize(() => memberRanges.reverse())
+ };
+ }
+ }
+ }
+
+ /**
+ * @param {MemberExpression} expression an expression
+ * @returns {{ name: string, rootInfo: ExportedVariableInfo, getMembers: () => string[]} | undefined} name info
+ */
+ getNameForExpression(expression) {
+ return this.getMemberExpressionInfo(
+ expression,
+ ALLOWED_MEMBER_TYPES_EXPRESSION
+ );
+ }
+
+ /**
+ * @param {string} code source code
+ * @param {ParseOptions} options parsing options
+ * @returns {Program} parsed ast
+ */
+ static _parse(code, options) {
+ const type = options ? options.sourceType : "module";
+ /** @type {AcornOptions} */
+ const parserOptions = {
+ ...defaultParserOptions,
+ allowReturnOutsideFunction: type === "script",
+ ...options,
+ sourceType: type === "auto" ? "module" : type
+ };
+
+ /** @type {AnyNode | undefined} */
+ let ast;
+ let error;
+ let threw = false;
+ try {
+ ast = /** @type {AnyNode} */ (parser.parse(code, parserOptions));
+ } catch (e) {
+ error = e;
+ threw = true;
+ }
+
+ if (threw && type === "auto") {
+ parserOptions.sourceType = "script";
+ if (!("allowReturnOutsideFunction" in options)) {
+ parserOptions.allowReturnOutsideFunction = true;
+ }
+ if (Array.isArray(parserOptions.onComment)) {
+ parserOptions.onComment.length = 0;
+ }
+ try {
+ ast = /** @type {AnyNode} */ (parser.parse(code, parserOptions));
+ threw = false;
+ } catch (e) {
+ // we use the error from first parse try
+ // so nothing to do here
+ }
+ }
+
+ if (threw) {
+ throw error;
+ }
+
+ return /** @type {Program} */ (ast);
+ }
+}
+
+module.exports = JavascriptParser;
+module.exports.ALLOWED_MEMBER_TYPES_ALL = ALLOWED_MEMBER_TYPES_ALL;
+module.exports.ALLOWED_MEMBER_TYPES_EXPRESSION =
+ ALLOWED_MEMBER_TYPES_EXPRESSION;
+module.exports.ALLOWED_MEMBER_TYPES_CALL_EXPRESSION =
+ ALLOWED_MEMBER_TYPES_CALL_EXPRESSION;