source: imaps-frontend/node_modules/webpack/lib/javascript/JavascriptParser.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 147.9 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { Parser: AcornParser, tokTypes } = require("acorn");
9const { SyncBailHook, HookMap } = require("tapable");
10const vm = require("vm");
11const Parser = require("../Parser");
12const StackedMap = require("../util/StackedMap");
13const binarySearchBounds = require("../util/binarySearchBounds");
14const {
15 webpackCommentRegExp,
16 createMagicCommentContext
17} = require("../util/magicComment");
18const memoize = require("../util/memoize");
19const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
20
21/** @typedef {import("acorn").Options} AcornOptions */
22/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */
23/** @typedef {import("estree").BinaryExpression} BinaryExpression */
24/** @typedef {import("estree").BlockStatement} BlockStatement */
25/** @typedef {import("estree").SequenceExpression} SequenceExpression */
26/** @typedef {import("estree").CallExpression} CallExpression */
27/** @typedef {import("estree").BaseCallExpression} BaseCallExpression */
28/** @typedef {import("estree").StaticBlock} StaticBlock */
29/** @typedef {import("estree").ClassDeclaration} ClassDeclaration */
30/** @typedef {import("estree").ForStatement} ForStatement */
31/** @typedef {import("estree").SwitchStatement} SwitchStatement */
32/** @typedef {import("estree").ClassExpression} ClassExpression */
33/** @typedef {import("estree").Comment} Comment */
34/** @typedef {import("estree").ConditionalExpression} ConditionalExpression */
35/** @typedef {import("estree").Declaration} Declaration */
36/** @typedef {import("estree").PrivateIdentifier} PrivateIdentifier */
37/** @typedef {import("estree").PropertyDefinition} PropertyDefinition */
38/** @typedef {import("estree").Expression} Expression */
39/** @typedef {import("estree").Identifier} Identifier */
40/** @typedef {import("estree").VariableDeclaration} VariableDeclaration */
41/** @typedef {import("estree").IfStatement} IfStatement */
42/** @typedef {import("estree").LabeledStatement} LabeledStatement */
43/** @typedef {import("estree").Literal} Literal */
44/** @typedef {import("estree").LogicalExpression} LogicalExpression */
45/** @typedef {import("estree").ChainExpression} ChainExpression */
46/** @typedef {import("estree").MemberExpression} MemberExpression */
47/** @typedef {import("estree").YieldExpression} YieldExpression */
48/** @typedef {import("estree").MetaProperty} MetaProperty */
49/** @typedef {import("estree").Property} Property */
50/** @typedef {import("estree").AssignmentPattern} AssignmentPattern */
51/** @typedef {import("estree").ChainElement} ChainElement */
52/** @typedef {import("estree").Pattern} Pattern */
53/** @typedef {import("estree").UpdateExpression} UpdateExpression */
54/** @typedef {import("estree").ObjectExpression} ObjectExpression */
55/** @typedef {import("estree").UnaryExpression} UnaryExpression */
56/** @typedef {import("estree").ArrayExpression} ArrayExpression */
57/** @typedef {import("estree").ArrayPattern} ArrayPattern */
58/** @typedef {import("estree").AwaitExpression} AwaitExpression */
59/** @typedef {import("estree").ThisExpression} ThisExpression */
60/** @typedef {import("estree").RestElement} RestElement */
61/** @typedef {import("estree").ObjectPattern} ObjectPattern */
62/** @typedef {import("estree").SwitchCase} SwitchCase */
63/** @typedef {import("estree").CatchClause} CatchClause */
64/** @typedef {import("estree").VariableDeclarator} VariableDeclarator */
65/** @typedef {import("estree").ForInStatement} ForInStatement */
66/** @typedef {import("estree").ForOfStatement} ForOfStatement */
67/** @typedef {import("estree").ReturnStatement} ReturnStatement */
68/** @typedef {import("estree").WithStatement} WithStatement */
69/** @typedef {import("estree").ThrowStatement} ThrowStatement */
70/** @typedef {import("estree").MethodDefinition} MethodDefinition */
71/** @typedef {import("estree").NewExpression} NewExpression */
72/** @typedef {import("estree").SpreadElement} SpreadElement */
73/** @typedef {import("estree").FunctionExpression} FunctionExpression */
74/** @typedef {import("estree").WhileStatement} WhileStatement */
75/** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */
76/** @typedef {import("estree").ExpressionStatement} ExpressionStatement */
77/** @typedef {import("estree").FunctionDeclaration} FunctionDeclaration */
78/** @typedef {import("estree").DoWhileStatement} DoWhileStatement */
79/** @typedef {import("estree").TryStatement} TryStatement */
80/** @typedef {import("estree").Node} Node */
81/** @typedef {import("estree").Program} Program */
82/** @typedef {import("estree").Directive} Directive */
83/** @typedef {import("estree").Statement} Statement */
84/** @typedef {import("estree").ExportDefaultDeclaration} ExportDefaultDeclaration */
85/** @typedef {import("estree").Super} Super */
86/** @typedef {import("estree").TaggedTemplateExpression} TaggedTemplateExpression */
87/** @typedef {import("estree").TemplateLiteral} TemplateLiteral */
88/** @typedef {import("estree").AssignmentProperty} AssignmentProperty */
89/**
90 * @template T
91 * @typedef {import("tapable").AsArray<T>} AsArray<T>
92 */
93/** @typedef {import("../Parser").ParserState} ParserState */
94/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
95/** @typedef {{declaredScope: ScopeInfo, freeName: string | true | undefined, tagInfo: TagInfo | undefined}} VariableInfoInterface */
96/** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[] }} GetInfoResult */
97/** @typedef {Statement | ModuleDeclaration | Expression} StatementPathItem */
98/** @typedef {function(string): void} OnIdentString */
99/** @typedef {function(string, Identifier): void} OnIdent */
100/** @typedef {StatementPathItem[]} StatementPath */
101
102// TODO remove cast when @types/estree has been updated to import assertions
103/** @typedef {import("estree").BaseNode & { type: "ImportAttribute", key: Identifier | Literal, value: Literal }} ImportAttribute */
104/** @typedef {import("estree").ImportDeclaration & { attributes?: Array<ImportAttribute> }} ImportDeclaration */
105/** @typedef {import("estree").ExportNamedDeclaration & { attributes?: Array<ImportAttribute> }} ExportNamedDeclaration */
106/** @typedef {import("estree").ExportAllDeclaration & { attributes?: Array<ImportAttribute> }} ExportAllDeclaration */
107/** @typedef {import("estree").ImportExpression & { options?: Expression | null }} ImportExpression */
108/** @typedef {ImportDeclaration | ExportNamedDeclaration | ExportDefaultDeclaration | ExportAllDeclaration} ModuleDeclaration */
109
110/** @type {string[]} */
111const EMPTY_ARRAY = [];
112const ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = 0b01;
113const ALLOWED_MEMBER_TYPES_EXPRESSION = 0b10;
114const ALLOWED_MEMBER_TYPES_ALL = 0b11;
115
116const LEGACY_ASSERT_ATTRIBUTES = Symbol("assert");
117
118/**
119 * @param {any} Parser parser
120 * @returns {typeof AcornParser} extender acorn parser
121 */
122const importAssertions = Parser =>
123 /** @type {typeof AcornParser} */ (
124 /** @type {unknown} */ (
125 class extends Parser {
126 parseWithClause() {
127 const nodes = [];
128
129 const isAssertLegacy = this.value === "assert";
130
131 if (isAssertLegacy) {
132 if (!this.eat(tokTypes.name)) {
133 return nodes;
134 }
135 } else if (!this.eat(tokTypes._with)) {
136 return nodes;
137 }
138
139 this.expect(tokTypes.braceL);
140
141 const attributeKeys = {};
142 let first = true;
143
144 while (!this.eat(tokTypes.braceR)) {
145 if (!first) {
146 this.expect(tokTypes.comma);
147 if (this.afterTrailingComma(tokTypes.braceR)) {
148 break;
149 }
150 } else {
151 first = false;
152 }
153
154 const attr = this.parseImportAttribute();
155 const keyName =
156 attr.key.type === "Identifier" ? attr.key.name : attr.key.value;
157
158 if (Object.prototype.hasOwnProperty.call(attributeKeys, keyName)) {
159 this.raiseRecoverable(
160 attr.key.start,
161 `Duplicate attribute key '${keyName}'`
162 );
163 }
164
165 attributeKeys[keyName] = true;
166 nodes.push(attr);
167 }
168
169 if (isAssertLegacy) {
170 nodes[LEGACY_ASSERT_ATTRIBUTES] = true;
171 }
172
173 return nodes;
174 }
175 }
176 )
177 );
178
179// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
180const parser = AcornParser.extend(importAssertions);
181
182/** @typedef {Record<string, string> & { _isLegacyAssert?: boolean }} ImportAttributes */
183
184/**
185 * @param {ImportDeclaration | ExportNamedDeclaration | ExportAllDeclaration | ImportExpression} node node with assertions
186 * @returns {ImportAttributes | undefined} import attributes
187 */
188const getImportAttributes = node => {
189 if (node.type === "ImportExpression") {
190 if (
191 node.options &&
192 node.options.type === "ObjectExpression" &&
193 node.options.properties[0] &&
194 node.options.properties[0].type === "Property" &&
195 node.options.properties[0].key.type === "Identifier" &&
196 (node.options.properties[0].key.name === "with" ||
197 node.options.properties[0].key.name === "assert") &&
198 node.options.properties[0].value.type === "ObjectExpression" &&
199 node.options.properties[0].value.properties.length > 0
200 ) {
201 const properties =
202 /** @type {Property[]} */
203 (node.options.properties[0].value.properties);
204 const result = /** @type {ImportAttributes} */ ({});
205 for (const property of properties) {
206 const key =
207 /** @type {string} */
208 (
209 property.key.type === "Identifier"
210 ? property.key.name
211 : /** @type {Literal} */ (property.key).value
212 );
213 result[key] =
214 /** @type {string} */
215 (/** @type {Literal} */ (property.value).value);
216 }
217 const key =
218 node.options.properties[0].key.type === "Identifier"
219 ? node.options.properties[0].key.name
220 : /** @type {Literal} */ (node.options.properties[0].key).value;
221
222 if (key === "assert") {
223 result._isLegacyAssert = true;
224 }
225
226 return result;
227 }
228
229 return;
230 }
231
232 if (node.attributes === undefined || node.attributes.length === 0) {
233 return;
234 }
235
236 const result = /** @type {ImportAttributes} */ ({});
237
238 for (const attribute of node.attributes) {
239 const key =
240 /** @type {string} */
241 (
242 attribute.key.type === "Identifier"
243 ? attribute.key.name
244 : attribute.key.value
245 );
246
247 result[key] = /** @type {string} */ (attribute.value.value);
248 }
249
250 if (node.attributes[LEGACY_ASSERT_ATTRIBUTES]) {
251 result._isLegacyAssert = true;
252 }
253
254 return result;
255};
256
257class VariableInfo {
258 /**
259 * @param {ScopeInfo} declaredScope scope in which the variable is declared
260 * @param {string | true | undefined} freeName which free name the variable aliases, or true when none
261 * @param {TagInfo | undefined} tagInfo info about tags
262 */
263 constructor(declaredScope, freeName, tagInfo) {
264 this.declaredScope = declaredScope;
265 this.freeName = freeName;
266 this.tagInfo = tagInfo;
267 }
268}
269
270/** @typedef {string | ScopeInfo | VariableInfo} ExportedVariableInfo */
271/** @typedef {Literal | string | null | undefined} ImportSource */
272/** @typedef {Omit<AcornOptions, "sourceType" | "ecmaVersion"> & { sourceType: "module" | "script" | "auto", ecmaVersion?: AcornOptions["ecmaVersion"] }} ParseOptions */
273
274/**
275 * @typedef {object} TagInfo
276 * @property {any} tag
277 * @property {any} data
278 * @property {TagInfo | undefined} next
279 */
280
281/**
282 * @typedef {object} ScopeInfo
283 * @property {StackedMap<string, VariableInfo | ScopeInfo>} definitions
284 * @property {boolean | "arrow"} topLevelScope
285 * @property {boolean | string} inShorthand
286 * @property {boolean} inTaggedTemplateTag
287 * @property {boolean} inTry
288 * @property {boolean} isStrict
289 * @property {boolean} isAsmJs
290 */
291
292/** @typedef {[number, number]} Range */
293
294/**
295 * @typedef {object} DestructuringAssignmentProperty
296 * @property {string} id
297 * @property {Range | undefined=} range
298 * @property {boolean | string} shorthand
299 */
300
301/**
302 * Helper function for joining two ranges into a single range. This is useful
303 * when working with AST nodes, as it allows you to combine the ranges of child nodes
304 * to create the range of the _parent node_.
305 * @param {[number, number]} startRange start range to join
306 * @param {[number, number]} endRange end range to join
307 * @returns {[number, number]} joined range
308 * @example
309 * ```js
310 * const startRange = [0, 5];
311 * const endRange = [10, 15];
312 * const joinedRange = joinRanges(startRange, endRange);
313 * console.log(joinedRange); // [0, 15]
314 * ```
315 */
316const joinRanges = (startRange, endRange) => {
317 if (!endRange) return startRange;
318 if (!startRange) return endRange;
319 return [startRange[0], endRange[1]];
320};
321
322/**
323 * Helper function used to generate a string representation of a
324 * [member expression](https://github.com/estree/estree/blob/master/es5.md#memberexpression).
325 * @param {string} object object to name
326 * @param {string[]} membersReversed reversed list of members
327 * @returns {string} member expression as a string
328 * @example
329 * ```js
330 * const membersReversed = ["property1", "property2", "property3"]; // Members parsed from the AST
331 * const name = objectAndMembersToName("myObject", membersReversed);
332 *
333 * console.log(name); // "myObject.property1.property2.property3"
334 * ```
335 */
336const objectAndMembersToName = (object, membersReversed) => {
337 let name = object;
338 for (let i = membersReversed.length - 1; i >= 0; i--) {
339 name = `${name}.${membersReversed[i]}`;
340 }
341 return name;
342};
343
344/**
345 * Grabs the name of a given expression and returns it as a string or undefined. Has particular
346 * handling for [Identifiers](https://github.com/estree/estree/blob/master/es5.md#identifier),
347 * [ThisExpressions](https://github.com/estree/estree/blob/master/es5.md#identifier), and
348 * [MetaProperties](https://github.com/estree/estree/blob/master/es2015.md#metaproperty) which is
349 * specifically for handling the `new.target` meta property.
350 * @param {Expression | SpreadElement | Super} expression expression
351 * @returns {string | "this" | undefined} name or variable info
352 */
353const getRootName = expression => {
354 switch (expression.type) {
355 case "Identifier":
356 return expression.name;
357 case "ThisExpression":
358 return "this";
359 case "MetaProperty":
360 return `${expression.meta.name}.${expression.property.name}`;
361 default:
362 return undefined;
363 }
364};
365
366/** @type {AcornOptions} */
367const defaultParserOptions = {
368 ranges: true,
369 locations: true,
370 ecmaVersion: "latest",
371 sourceType: "module",
372 // https://github.com/tc39/proposal-hashbang
373 allowHashBang: true,
374 onComment: undefined
375};
376
377const EMPTY_COMMENT_OPTIONS = {
378 options: null,
379 errors: null
380};
381
382class JavascriptParser extends Parser {
383 /**
384 * @param {"module" | "script" | "auto"} sourceType default source type
385 */
386 constructor(sourceType = "auto") {
387 super();
388 this.hooks = Object.freeze({
389 /** @type {HookMap<SyncBailHook<[UnaryExpression], BasicEvaluatedExpression | null | undefined>>} */
390 evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])),
391 /** @type {HookMap<SyncBailHook<[Expression | SpreadElement | PrivateIdentifier], BasicEvaluatedExpression | null | undefined>>} */
392 evaluate: new HookMap(() => new SyncBailHook(["expression"])),
393 /** @type {HookMap<SyncBailHook<[Identifier | ThisExpression | MemberExpression | MetaProperty], BasicEvaluatedExpression | null | undefined>>} */
394 evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])),
395 /** @type {HookMap<SyncBailHook<[Identifier | ThisExpression | MemberExpression], BasicEvaluatedExpression | null | undefined>>} */
396 evaluateDefinedIdentifier: new HookMap(
397 () => new SyncBailHook(["expression"])
398 ),
399 /** @type {HookMap<SyncBailHook<[NewExpression], BasicEvaluatedExpression | null | undefined>>} */
400 evaluateNewExpression: new HookMap(
401 () => new SyncBailHook(["expression"])
402 ),
403 /** @type {HookMap<SyncBailHook<[CallExpression], BasicEvaluatedExpression | null | undefined>>} */
404 evaluateCallExpression: new HookMap(
405 () => new SyncBailHook(["expression"])
406 ),
407 /** @type {HookMap<SyncBailHook<[CallExpression, BasicEvaluatedExpression], BasicEvaluatedExpression | null | undefined>>} */
408 evaluateCallExpressionMember: new HookMap(
409 () => new SyncBailHook(["expression", "param"])
410 ),
411 /** @type {HookMap<SyncBailHook<[Expression | Declaration | PrivateIdentifier, number], boolean | void>>} */
412 isPure: new HookMap(
413 () => new SyncBailHook(["expression", "commentsStartPosition"])
414 ),
415 /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */
416 preStatement: new SyncBailHook(["statement"]),
417
418 /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */
419 blockPreStatement: new SyncBailHook(["declaration"]),
420 /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */
421 statement: new SyncBailHook(["statement"]),
422 /** @type {SyncBailHook<[IfStatement], boolean | void>} */
423 statementIf: new SyncBailHook(["statement"]),
424 /** @type {SyncBailHook<[Expression, ClassExpression | ClassDeclaration], boolean | void>} */
425 classExtendsExpression: new SyncBailHook([
426 "expression",
427 "classDefinition"
428 ]),
429 /** @type {SyncBailHook<[MethodDefinition | PropertyDefinition | StaticBlock, ClassExpression | ClassDeclaration], boolean | void>} */
430 classBodyElement: new SyncBailHook(["element", "classDefinition"]),
431 /** @type {SyncBailHook<[Expression, MethodDefinition | PropertyDefinition, ClassExpression | ClassDeclaration], boolean | void>} */
432 classBodyValue: new SyncBailHook([
433 "expression",
434 "element",
435 "classDefinition"
436 ]),
437 /** @type {HookMap<SyncBailHook<[LabeledStatement], boolean | void>>} */
438 label: new HookMap(() => new SyncBailHook(["statement"])),
439 /** @type {SyncBailHook<[ImportDeclaration, ImportSource], boolean | void>} */
440 import: new SyncBailHook(["statement", "source"]),
441 /** @type {SyncBailHook<[ImportDeclaration, ImportSource, string | null, string], boolean | void>} */
442 importSpecifier: new SyncBailHook([
443 "statement",
444 "source",
445 "exportName",
446 "identifierName"
447 ]),
448 /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration], boolean | void>} */
449 export: new SyncBailHook(["statement"]),
450 /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, ImportSource], boolean | void>} */
451 exportImport: new SyncBailHook(["statement", "source"]),
452 /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, Declaration], boolean | void>} */
453 exportDeclaration: new SyncBailHook(["statement", "declaration"]),
454 /** @type {SyncBailHook<[ExportDefaultDeclaration, FunctionDeclaration | ClassDeclaration], boolean | void>} */
455 exportExpression: new SyncBailHook(["statement", "declaration"]),
456 /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, string, string, number | undefined], boolean | void>} */
457 exportSpecifier: new SyncBailHook([
458 "statement",
459 "identifierName",
460 "exportName",
461 "index"
462 ]),
463 /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, ImportSource, string | null, string | null, number | undefined], boolean | void>} */
464 exportImportSpecifier: new SyncBailHook([
465 "statement",
466 "source",
467 "identifierName",
468 "exportName",
469 "index"
470 ]),
471 /** @type {SyncBailHook<[VariableDeclarator, Statement], boolean | void>} */
472 preDeclarator: new SyncBailHook(["declarator", "statement"]),
473 /** @type {SyncBailHook<[VariableDeclarator, Statement], boolean | void>} */
474 declarator: new SyncBailHook(["declarator", "statement"]),
475 /** @type {HookMap<SyncBailHook<[Declaration], boolean | void>>} */
476 varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])),
477 /** @type {HookMap<SyncBailHook<[Declaration], boolean | void>>} */
478 varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])),
479 /** @type {HookMap<SyncBailHook<[Declaration], boolean | void>>} */
480 varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])),
481 /** @type {HookMap<SyncBailHook<[Declaration], boolean | void>>} */
482 varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])),
483 /** @type {HookMap<SyncBailHook<[Identifier], boolean | void>>} */
484 pattern: new HookMap(() => new SyncBailHook(["pattern"])),
485 /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
486 canRename: new HookMap(() => new SyncBailHook(["initExpression"])),
487 /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
488 rename: new HookMap(() => new SyncBailHook(["initExpression"])),
489 /** @type {HookMap<SyncBailHook<[AssignmentExpression], boolean | void>>} */
490 assign: new HookMap(() => new SyncBailHook(["expression"])),
491 /** @type {HookMap<SyncBailHook<[AssignmentExpression, string[]], boolean | void>>} */
492 assignMemberChain: new HookMap(
493 () => new SyncBailHook(["expression", "members"])
494 ),
495 /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
496 typeof: new HookMap(() => new SyncBailHook(["expression"])),
497 /** @type {SyncBailHook<[ImportExpression], boolean | void>} */
498 importCall: new SyncBailHook(["expression"]),
499 /** @type {SyncBailHook<[Expression | ForOfStatement], boolean | void>} */
500 topLevelAwait: new SyncBailHook(["expression"]),
501 /** @type {HookMap<SyncBailHook<[CallExpression], boolean | void>>} */
502 call: new HookMap(() => new SyncBailHook(["expression"])),
503 /** Something like "a.b()" */
504 /** @type {HookMap<SyncBailHook<[CallExpression, string[], boolean[], Range[]], boolean | void>>} */
505 callMemberChain: new HookMap(
506 () =>
507 new SyncBailHook([
508 "expression",
509 "members",
510 "membersOptionals",
511 "memberRanges"
512 ])
513 ),
514 /** Something like "a.b().c.d" */
515 /** @type {HookMap<SyncBailHook<[Expression, string[], CallExpression, string[], Range[]], boolean | void>>} */
516 memberChainOfCallMemberChain: new HookMap(
517 () =>
518 new SyncBailHook([
519 "expression",
520 "calleeMembers",
521 "callExpression",
522 "members",
523 "memberRanges"
524 ])
525 ),
526 /** Something like "a.b().c.d()"" */
527 /** @type {HookMap<SyncBailHook<[CallExpression, string[], CallExpression, string[], Range[]], boolean | void>>} */
528 callMemberChainOfCallMemberChain: new HookMap(
529 () =>
530 new SyncBailHook([
531 "expression",
532 "calleeMembers",
533 "innerCallExpression",
534 "members",
535 "memberRanges"
536 ])
537 ),
538 /** @type {SyncBailHook<[ChainExpression], boolean | void>} */
539 optionalChaining: new SyncBailHook(["optionalChaining"]),
540 /** @type {HookMap<SyncBailHook<[NewExpression], boolean | void>>} */
541 new: new HookMap(() => new SyncBailHook(["expression"])),
542 /** @type {SyncBailHook<[BinaryExpression], boolean | void>} */
543 binaryExpression: new SyncBailHook(["binaryExpression"]),
544 /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
545 expression: new HookMap(() => new SyncBailHook(["expression"])),
546 /** @type {HookMap<SyncBailHook<[MemberExpression, string[], boolean[], Range[]], boolean | void>>} */
547 expressionMemberChain: new HookMap(
548 () =>
549 new SyncBailHook([
550 "expression",
551 "members",
552 "membersOptionals",
553 "memberRanges"
554 ])
555 ),
556 /** @type {HookMap<SyncBailHook<[MemberExpression, string[]], boolean | void>>} */
557 unhandledExpressionMemberChain: new HookMap(
558 () => new SyncBailHook(["expression", "members"])
559 ),
560 /** @type {SyncBailHook<[ConditionalExpression], boolean | void>} */
561 expressionConditionalOperator: new SyncBailHook(["expression"]),
562 /** @type {SyncBailHook<[LogicalExpression], boolean | void>} */
563 expressionLogicalOperator: new SyncBailHook(["expression"]),
564 /** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */
565 program: new SyncBailHook(["ast", "comments"]),
566 /** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */
567 finish: new SyncBailHook(["ast", "comments"])
568 });
569 this.sourceType = sourceType;
570 /** @type {ScopeInfo} */
571 this.scope = undefined;
572 /** @type {ParserState} */
573 this.state = undefined;
574 /** @type {Comment[] | undefined} */
575 this.comments = undefined;
576 /** @type {Set<number> | undefined} */
577 this.semicolons = undefined;
578 /** @type {StatementPath | undefined} */
579 this.statementPath = undefined;
580 /** @type {Statement | ModuleDeclaration | Expression | undefined} */
581 this.prevStatement = undefined;
582 /** @type {WeakMap<Expression, Set<DestructuringAssignmentProperty>> | undefined} */
583 this.destructuringAssignmentProperties = undefined;
584 this.currentTagData = undefined;
585 this.magicCommentContext = createMagicCommentContext();
586 this._initializeEvaluating();
587 }
588
589 _initializeEvaluating() {
590 this.hooks.evaluate.for("Literal").tap("JavascriptParser", _expr => {
591 const expr = /** @type {Literal} */ (_expr);
592
593 switch (typeof expr.value) {
594 case "number":
595 return new BasicEvaluatedExpression()
596 .setNumber(expr.value)
597 .setRange(/** @type {Range} */ (expr.range));
598 case "bigint":
599 return new BasicEvaluatedExpression()
600 .setBigInt(expr.value)
601 .setRange(/** @type {Range} */ (expr.range));
602 case "string":
603 return new BasicEvaluatedExpression()
604 .setString(expr.value)
605 .setRange(/** @type {Range} */ (expr.range));
606 case "boolean":
607 return new BasicEvaluatedExpression()
608 .setBoolean(expr.value)
609 .setRange(/** @type {Range} */ (expr.range));
610 }
611 if (expr.value === null) {
612 return new BasicEvaluatedExpression()
613 .setNull()
614 .setRange(/** @type {Range} */ (expr.range));
615 }
616 if (expr.value instanceof RegExp) {
617 return new BasicEvaluatedExpression()
618 .setRegExp(expr.value)
619 .setRange(/** @type {Range} */ (expr.range));
620 }
621 });
622 this.hooks.evaluate.for("NewExpression").tap("JavascriptParser", _expr => {
623 const expr = /** @type {NewExpression} */ (_expr);
624 const callee = expr.callee;
625 if (callee.type !== "Identifier") return;
626 if (callee.name !== "RegExp") {
627 return this.callHooksForName(
628 this.hooks.evaluateNewExpression,
629 callee.name,
630 expr
631 );
632 } else if (
633 expr.arguments.length > 2 ||
634 this.getVariableInfo("RegExp") !== "RegExp"
635 )
636 return;
637
638 let regExp;
639 const arg1 = expr.arguments[0];
640
641 if (arg1) {
642 if (arg1.type === "SpreadElement") return;
643
644 const evaluatedRegExp = this.evaluateExpression(arg1);
645
646 if (!evaluatedRegExp) return;
647
648 regExp = evaluatedRegExp.asString();
649
650 if (!regExp) return;
651 } else {
652 return (
653 new BasicEvaluatedExpression()
654 // eslint-disable-next-line prefer-regex-literals
655 .setRegExp(new RegExp(""))
656 .setRange(/** @type {Range} */ (expr.range))
657 );
658 }
659
660 let flags;
661 const arg2 = expr.arguments[1];
662
663 if (arg2) {
664 if (arg2.type === "SpreadElement") return;
665
666 const evaluatedFlags = this.evaluateExpression(arg2);
667
668 if (!evaluatedFlags) return;
669
670 if (!evaluatedFlags.isUndefined()) {
671 flags = evaluatedFlags.asString();
672
673 if (
674 flags === undefined ||
675 !BasicEvaluatedExpression.isValidRegExpFlags(flags)
676 )
677 return;
678 }
679 }
680
681 return new BasicEvaluatedExpression()
682 .setRegExp(flags ? new RegExp(regExp, flags) : new RegExp(regExp))
683 .setRange(/** @type {Range} */ (expr.range));
684 });
685 this.hooks.evaluate
686 .for("LogicalExpression")
687 .tap("JavascriptParser", _expr => {
688 const expr = /** @type {LogicalExpression} */ (_expr);
689
690 const left = this.evaluateExpression(expr.left);
691 let returnRight = false;
692 /** @type {boolean | undefined} */
693 let allowedRight;
694 if (expr.operator === "&&") {
695 const leftAsBool = left.asBool();
696 if (leftAsBool === false)
697 return left.setRange(/** @type {Range} */ (expr.range));
698 returnRight = leftAsBool === true;
699 allowedRight = false;
700 } else if (expr.operator === "||") {
701 const leftAsBool = left.asBool();
702 if (leftAsBool === true)
703 return left.setRange(/** @type {Range} */ (expr.range));
704 returnRight = leftAsBool === false;
705 allowedRight = true;
706 } else if (expr.operator === "??") {
707 const leftAsNullish = left.asNullish();
708 if (leftAsNullish === false)
709 return left.setRange(/** @type {Range} */ (expr.range));
710 if (leftAsNullish !== true) return;
711 returnRight = true;
712 } else return;
713 const right = this.evaluateExpression(expr.right);
714 if (returnRight) {
715 if (left.couldHaveSideEffects()) right.setSideEffects();
716 return right.setRange(/** @type {Range} */ (expr.range));
717 }
718
719 const asBool = right.asBool();
720
721 if (allowedRight === true && asBool === true) {
722 return new BasicEvaluatedExpression()
723 .setRange(/** @type {Range} */ (expr.range))
724 .setTruthy();
725 } else if (allowedRight === false && asBool === false) {
726 return new BasicEvaluatedExpression()
727 .setRange(/** @type {Range} */ (expr.range))
728 .setFalsy();
729 }
730 });
731
732 /**
733 * In simple logical cases, we can use valueAsExpression to assist us in evaluating the expression on
734 * either side of a [BinaryExpression](https://github.com/estree/estree/blob/master/es5.md#binaryexpression).
735 * This supports scenarios in webpack like conditionally `import()`'ing modules based on some simple evaluation:
736 *
737 * ```js
738 * if (1 === 3) {
739 * import("./moduleA"); // webpack will auto evaluate this and not import the modules
740 * }
741 * ```
742 *
743 * Additional scenarios include evaluation of strings inside of dynamic import statements:
744 *
745 * ```js
746 * const foo = "foo";
747 * const bar = "bar";
748 *
749 * import("./" + foo + bar); // webpack will auto evaluate this into import("./foobar")
750 * ```
751 * @param {boolean | number | bigint | string} value the value to convert to an expression
752 * @param {BinaryExpression | UnaryExpression} expr the expression being evaluated
753 * @param {boolean} sideEffects whether the expression has side effects
754 * @returns {BasicEvaluatedExpression | undefined} the evaluated expression
755 * @example
756 *
757 * ```js
758 * const binaryExpr = new BinaryExpression("+",
759 * { type: "Literal", value: 2 },
760 * { type: "Literal", value: 3 }
761 * );
762 *
763 * const leftValue = 2;
764 * const rightValue = 3;
765 *
766 * const leftExpr = valueAsExpression(leftValue, binaryExpr.left, false);
767 * const rightExpr = valueAsExpression(rightValue, binaryExpr.right, false);
768 * const result = new BasicEvaluatedExpression()
769 * .setNumber(leftExpr.number + rightExpr.number)
770 * .setRange(binaryExpr.range);
771 *
772 * console.log(result.number); // Output: 5
773 * ```
774 */
775 const valueAsExpression = (value, expr, sideEffects) => {
776 switch (typeof value) {
777 case "boolean":
778 return new BasicEvaluatedExpression()
779 .setBoolean(value)
780 .setSideEffects(sideEffects)
781 .setRange(/** @type {Range} */ (expr.range));
782 case "number":
783 return new BasicEvaluatedExpression()
784 .setNumber(value)
785 .setSideEffects(sideEffects)
786 .setRange(/** @type {Range} */ (expr.range));
787 case "bigint":
788 return new BasicEvaluatedExpression()
789 .setBigInt(value)
790 .setSideEffects(sideEffects)
791 .setRange(/** @type {Range} */ (expr.range));
792 case "string":
793 return new BasicEvaluatedExpression()
794 .setString(value)
795 .setSideEffects(sideEffects)
796 .setRange(/** @type {Range} */ (expr.range));
797 }
798 };
799
800 this.hooks.evaluate
801 .for("BinaryExpression")
802 .tap("JavascriptParser", _expr => {
803 const expr = /** @type {BinaryExpression} */ (_expr);
804
805 /**
806 * Evaluates a binary expression if and only if it is a const operation (e.g. 1 + 2, "a" + "b", etc.).
807 * @template T
808 * @param {(leftOperand: T, rightOperand: T) => boolean | number | bigint | string} operandHandler the handler for the operation (e.g. (a, b) => a + b)
809 * @returns {BasicEvaluatedExpression | undefined} the evaluated expression
810 */
811 const handleConstOperation = operandHandler => {
812 const left = this.evaluateExpression(expr.left);
813 if (!left.isCompileTimeValue()) return;
814
815 const right = this.evaluateExpression(expr.right);
816 if (!right.isCompileTimeValue()) return;
817
818 const result = operandHandler(
819 left.asCompileTimeValue(),
820 right.asCompileTimeValue()
821 );
822 return valueAsExpression(
823 result,
824 expr,
825 left.couldHaveSideEffects() || right.couldHaveSideEffects()
826 );
827 };
828
829 /**
830 * Helper function to determine if two booleans are always different. This is used in `handleStrictEqualityComparison`
831 * to determine if an expressions boolean or nullish conversion is equal or not.
832 * @param {boolean} a first boolean to compare
833 * @param {boolean} b second boolean to compare
834 * @returns {boolean} true if the two booleans are always different, false otherwise
835 */
836 const isAlwaysDifferent = (a, b) =>
837 (a === true && b === false) || (a === false && b === true);
838
839 /**
840 * @param {BasicEvaluatedExpression} left left
841 * @param {BasicEvaluatedExpression} right right
842 * @param {BasicEvaluatedExpression} res res
843 * @param {boolean} eql true for "===" and false for "!=="
844 * @returns {BasicEvaluatedExpression | undefined} result
845 */
846 const handleTemplateStringCompare = (left, right, res, eql) => {
847 /**
848 * @param {BasicEvaluatedExpression[]} parts parts
849 * @returns {string} value
850 */
851 const getPrefix = parts => {
852 let value = "";
853 for (const p of parts) {
854 const v = p.asString();
855 if (v !== undefined) value += v;
856 else break;
857 }
858 return value;
859 };
860 /**
861 * @param {BasicEvaluatedExpression[]} parts parts
862 * @returns {string} value
863 */
864 const getSuffix = parts => {
865 let value = "";
866 for (let i = parts.length - 1; i >= 0; i--) {
867 const v = parts[i].asString();
868 if (v !== undefined) value = v + value;
869 else break;
870 }
871 return value;
872 };
873 const leftPrefix = getPrefix(
874 /** @type {BasicEvaluatedExpression[]} */ (left.parts)
875 );
876 const rightPrefix = getPrefix(
877 /** @type {BasicEvaluatedExpression[]} */ (right.parts)
878 );
879 const leftSuffix = getSuffix(
880 /** @type {BasicEvaluatedExpression[]} */ (left.parts)
881 );
882 const rightSuffix = getSuffix(
883 /** @type {BasicEvaluatedExpression[]} */ (right.parts)
884 );
885 const lenPrefix = Math.min(leftPrefix.length, rightPrefix.length);
886 const lenSuffix = Math.min(leftSuffix.length, rightSuffix.length);
887 const prefixMismatch =
888 lenPrefix > 0 &&
889 leftPrefix.slice(0, lenPrefix) !== rightPrefix.slice(0, lenPrefix);
890 const suffixMismatch =
891 lenSuffix > 0 &&
892 leftSuffix.slice(-lenSuffix) !== rightSuffix.slice(-lenSuffix);
893 if (prefixMismatch || suffixMismatch) {
894 return res
895 .setBoolean(!eql)
896 .setSideEffects(
897 left.couldHaveSideEffects() || right.couldHaveSideEffects()
898 );
899 }
900 };
901
902 /**
903 * Helper function to handle BinaryExpressions using strict equality comparisons (e.g. "===" and "!==").
904 * @param {boolean} eql true for "===" and false for "!=="
905 * @returns {BasicEvaluatedExpression | undefined} the evaluated expression
906 */
907 const handleStrictEqualityComparison = eql => {
908 const left = this.evaluateExpression(expr.left);
909 const right = this.evaluateExpression(expr.right);
910 const res = new BasicEvaluatedExpression();
911 res.setRange(/** @type {Range} */ (expr.range));
912
913 const leftConst = left.isCompileTimeValue();
914 const rightConst = right.isCompileTimeValue();
915
916 if (leftConst && rightConst) {
917 return res
918 .setBoolean(
919 eql ===
920 (left.asCompileTimeValue() === right.asCompileTimeValue())
921 )
922 .setSideEffects(
923 left.couldHaveSideEffects() || right.couldHaveSideEffects()
924 );
925 }
926
927 if (left.isArray() && right.isArray()) {
928 return res
929 .setBoolean(!eql)
930 .setSideEffects(
931 left.couldHaveSideEffects() || right.couldHaveSideEffects()
932 );
933 }
934 if (left.isTemplateString() && right.isTemplateString()) {
935 return handleTemplateStringCompare(left, right, res, eql);
936 }
937
938 const leftPrimitive = left.isPrimitiveType();
939 const rightPrimitive = right.isPrimitiveType();
940
941 if (
942 // Primitive !== Object or
943 // compile-time object types are never equal to something at runtime
944 (leftPrimitive === false &&
945 (leftConst || rightPrimitive === true)) ||
946 (rightPrimitive === false &&
947 (rightConst || leftPrimitive === true)) ||
948 // Different nullish or boolish status also means not equal
949 isAlwaysDifferent(
950 /** @type {boolean} */ (left.asBool()),
951 /** @type {boolean} */ (right.asBool())
952 ) ||
953 isAlwaysDifferent(
954 /** @type {boolean} */ (left.asNullish()),
955 /** @type {boolean} */ (right.asNullish())
956 )
957 ) {
958 return res
959 .setBoolean(!eql)
960 .setSideEffects(
961 left.couldHaveSideEffects() || right.couldHaveSideEffects()
962 );
963 }
964 };
965
966 /**
967 * Helper function to handle BinaryExpressions using abstract equality comparisons (e.g. "==" and "!=").
968 * @param {boolean} eql true for "==" and false for "!="
969 * @returns {BasicEvaluatedExpression | undefined} the evaluated expression
970 */
971 const handleAbstractEqualityComparison = eql => {
972 const left = this.evaluateExpression(expr.left);
973 const right = this.evaluateExpression(expr.right);
974 const res = new BasicEvaluatedExpression();
975 res.setRange(/** @type {Range} */ (expr.range));
976
977 const leftConst = left.isCompileTimeValue();
978 const rightConst = right.isCompileTimeValue();
979
980 if (leftConst && rightConst) {
981 return res
982 .setBoolean(
983 eql ===
984 // eslint-disable-next-line eqeqeq
985 (left.asCompileTimeValue() == right.asCompileTimeValue())
986 )
987 .setSideEffects(
988 left.couldHaveSideEffects() || right.couldHaveSideEffects()
989 );
990 }
991
992 if (left.isArray() && right.isArray()) {
993 return res
994 .setBoolean(!eql)
995 .setSideEffects(
996 left.couldHaveSideEffects() || right.couldHaveSideEffects()
997 );
998 }
999 if (left.isTemplateString() && right.isTemplateString()) {
1000 return handleTemplateStringCompare(left, right, res, eql);
1001 }
1002 };
1003
1004 if (expr.operator === "+") {
1005 const left = this.evaluateExpression(expr.left);
1006 const right = this.evaluateExpression(expr.right);
1007 const res = new BasicEvaluatedExpression();
1008 if (left.isString()) {
1009 if (right.isString()) {
1010 res.setString(
1011 /** @type {string} */ (left.string) +
1012 /** @type {string} */ (right.string)
1013 );
1014 } else if (right.isNumber()) {
1015 res.setString(/** @type {string} */ (left.string) + right.number);
1016 } else if (
1017 right.isWrapped() &&
1018 right.prefix &&
1019 right.prefix.isString()
1020 ) {
1021 // "left" + ("prefix" + inner + "postfix")
1022 // => ("leftPrefix" + inner + "postfix")
1023 res.setWrapped(
1024 new BasicEvaluatedExpression()
1025 .setString(
1026 /** @type {string} */ (left.string) +
1027 /** @type {string} */ (right.prefix.string)
1028 )
1029 .setRange(
1030 joinRanges(
1031 /** @type {Range} */ (left.range),
1032 /** @type {Range} */ (right.prefix.range)
1033 )
1034 ),
1035 right.postfix,
1036 right.wrappedInnerExpressions
1037 );
1038 } else if (right.isWrapped()) {
1039 // "left" + ([null] + inner + "postfix")
1040 // => ("left" + inner + "postfix")
1041 res.setWrapped(
1042 left,
1043 right.postfix,
1044 right.wrappedInnerExpressions
1045 );
1046 } else {
1047 // "left" + expr
1048 // => ("left" + expr + "")
1049 res.setWrapped(left, null, [right]);
1050 }
1051 } else if (left.isNumber()) {
1052 if (right.isString()) {
1053 res.setString(left.number + /** @type {string} */ (right.string));
1054 } else if (right.isNumber()) {
1055 res.setNumber(
1056 /** @type {number} */ (left.number) +
1057 /** @type {number} */ (right.number)
1058 );
1059 } else {
1060 return;
1061 }
1062 } else if (left.isBigInt()) {
1063 if (right.isBigInt()) {
1064 res.setBigInt(
1065 /** @type {bigint} */ (left.bigint) +
1066 /** @type {bigint} */ (right.bigint)
1067 );
1068 }
1069 } else if (left.isWrapped()) {
1070 if (left.postfix && left.postfix.isString() && right.isString()) {
1071 // ("prefix" + inner + "postfix") + "right"
1072 // => ("prefix" + inner + "postfixRight")
1073 res.setWrapped(
1074 left.prefix,
1075 new BasicEvaluatedExpression()
1076 .setString(
1077 /** @type {string} */ (left.postfix.string) +
1078 /** @type {string} */ (right.string)
1079 )
1080 .setRange(
1081 joinRanges(
1082 /** @type {Range} */ (left.postfix.range),
1083 /** @type {Range} */ (right.range)
1084 )
1085 ),
1086 left.wrappedInnerExpressions
1087 );
1088 } else if (
1089 left.postfix &&
1090 left.postfix.isString() &&
1091 right.isNumber()
1092 ) {
1093 // ("prefix" + inner + "postfix") + 123
1094 // => ("prefix" + inner + "postfix123")
1095 res.setWrapped(
1096 left.prefix,
1097 new BasicEvaluatedExpression()
1098 .setString(
1099 /** @type {string} */ (left.postfix.string) +
1100 /** @type {number} */ (right.number)
1101 )
1102 .setRange(
1103 joinRanges(
1104 /** @type {Range} */ (left.postfix.range),
1105 /** @type {Range} */ (right.range)
1106 )
1107 ),
1108 left.wrappedInnerExpressions
1109 );
1110 } else if (right.isString()) {
1111 // ("prefix" + inner + [null]) + "right"
1112 // => ("prefix" + inner + "right")
1113 res.setWrapped(left.prefix, right, left.wrappedInnerExpressions);
1114 } else if (right.isNumber()) {
1115 // ("prefix" + inner + [null]) + 123
1116 // => ("prefix" + inner + "123")
1117 res.setWrapped(
1118 left.prefix,
1119 new BasicEvaluatedExpression()
1120 .setString(String(right.number))
1121 .setRange(/** @type {Range} */ (right.range)),
1122 left.wrappedInnerExpressions
1123 );
1124 } else if (right.isWrapped()) {
1125 // ("prefix1" + inner1 + "postfix1") + ("prefix2" + inner2 + "postfix2")
1126 // ("prefix1" + inner1 + "postfix1" + "prefix2" + inner2 + "postfix2")
1127 res.setWrapped(
1128 left.prefix,
1129 right.postfix,
1130 left.wrappedInnerExpressions &&
1131 right.wrappedInnerExpressions &&
1132 left.wrappedInnerExpressions
1133 .concat(left.postfix ? [left.postfix] : [])
1134 .concat(right.prefix ? [right.prefix] : [])
1135 .concat(right.wrappedInnerExpressions)
1136 );
1137 } else {
1138 // ("prefix" + inner + postfix) + expr
1139 // => ("prefix" + inner + postfix + expr + [null])
1140 res.setWrapped(
1141 left.prefix,
1142 null,
1143 left.wrappedInnerExpressions &&
1144 left.wrappedInnerExpressions.concat(
1145 left.postfix ? [left.postfix, right] : [right]
1146 )
1147 );
1148 }
1149 } else if (right.isString()) {
1150 // left + "right"
1151 // => ([null] + left + "right")
1152 res.setWrapped(null, right, [left]);
1153 } else if (right.isWrapped()) {
1154 // left + (prefix + inner + "postfix")
1155 // => ([null] + left + prefix + inner + "postfix")
1156 res.setWrapped(
1157 null,
1158 right.postfix,
1159 right.wrappedInnerExpressions &&
1160 (right.prefix ? [left, right.prefix] : [left]).concat(
1161 right.wrappedInnerExpressions
1162 )
1163 );
1164 } else {
1165 return;
1166 }
1167 if (left.couldHaveSideEffects() || right.couldHaveSideEffects())
1168 res.setSideEffects();
1169 res.setRange(/** @type {Range} */ (expr.range));
1170 return res;
1171 } else if (expr.operator === "-") {
1172 return handleConstOperation((l, r) => l - r);
1173 } else if (expr.operator === "*") {
1174 return handleConstOperation((l, r) => l * r);
1175 } else if (expr.operator === "/") {
1176 return handleConstOperation((l, r) => l / r);
1177 } else if (expr.operator === "**") {
1178 return handleConstOperation((l, r) => l ** r);
1179 } else if (expr.operator === "===") {
1180 return handleStrictEqualityComparison(true);
1181 } else if (expr.operator === "==") {
1182 return handleAbstractEqualityComparison(true);
1183 } else if (expr.operator === "!==") {
1184 return handleStrictEqualityComparison(false);
1185 } else if (expr.operator === "!=") {
1186 return handleAbstractEqualityComparison(false);
1187 } else if (expr.operator === "&") {
1188 return handleConstOperation((l, r) => l & r);
1189 } else if (expr.operator === "|") {
1190 return handleConstOperation((l, r) => l | r);
1191 } else if (expr.operator === "^") {
1192 return handleConstOperation((l, r) => l ^ r);
1193 } else if (expr.operator === ">>>") {
1194 return handleConstOperation((l, r) => l >>> r);
1195 } else if (expr.operator === ">>") {
1196 return handleConstOperation((l, r) => l >> r);
1197 } else if (expr.operator === "<<") {
1198 return handleConstOperation((l, r) => l << r);
1199 } else if (expr.operator === "<") {
1200 return handleConstOperation((l, r) => l < r);
1201 } else if (expr.operator === ">") {
1202 return handleConstOperation((l, r) => l > r);
1203 } else if (expr.operator === "<=") {
1204 return handleConstOperation((l, r) => l <= r);
1205 } else if (expr.operator === ">=") {
1206 return handleConstOperation((l, r) => l >= r);
1207 }
1208 });
1209 this.hooks.evaluate
1210 .for("UnaryExpression")
1211 .tap("JavascriptParser", _expr => {
1212 const expr = /** @type {UnaryExpression} */ (_expr);
1213
1214 /**
1215 * Evaluates a UnaryExpression if and only if it is a basic const operator (e.g. +a, -a, ~a).
1216 * @template T
1217 * @param {(operand: T) => boolean | number | bigint | string} operandHandler handler for the operand
1218 * @returns {BasicEvaluatedExpression | undefined} evaluated expression
1219 */
1220 const handleConstOperation = operandHandler => {
1221 const argument = this.evaluateExpression(expr.argument);
1222 if (!argument.isCompileTimeValue()) return;
1223 const result = operandHandler(argument.asCompileTimeValue());
1224 return valueAsExpression(
1225 result,
1226 expr,
1227 argument.couldHaveSideEffects()
1228 );
1229 };
1230
1231 if (expr.operator === "typeof") {
1232 switch (expr.argument.type) {
1233 case "Identifier": {
1234 const res = this.callHooksForName(
1235 this.hooks.evaluateTypeof,
1236 expr.argument.name,
1237 expr
1238 );
1239 if (res !== undefined) return res;
1240 break;
1241 }
1242 case "MetaProperty": {
1243 const res = this.callHooksForName(
1244 this.hooks.evaluateTypeof,
1245 /** @type {string} */ (getRootName(expr.argument)),
1246 expr
1247 );
1248 if (res !== undefined) return res;
1249 break;
1250 }
1251 case "MemberExpression": {
1252 const res = this.callHooksForExpression(
1253 this.hooks.evaluateTypeof,
1254 expr.argument,
1255 expr
1256 );
1257 if (res !== undefined) return res;
1258 break;
1259 }
1260 case "ChainExpression": {
1261 const res = this.callHooksForExpression(
1262 this.hooks.evaluateTypeof,
1263 expr.argument.expression,
1264 expr
1265 );
1266 if (res !== undefined) return res;
1267 break;
1268 }
1269 case "FunctionExpression": {
1270 return new BasicEvaluatedExpression()
1271 .setString("function")
1272 .setRange(/** @type {Range} */ (expr.range));
1273 }
1274 }
1275 const arg = this.evaluateExpression(expr.argument);
1276 if (arg.isUnknown()) return;
1277 if (arg.isString()) {
1278 return new BasicEvaluatedExpression()
1279 .setString("string")
1280 .setRange(/** @type {Range} */ (expr.range));
1281 }
1282 if (arg.isWrapped()) {
1283 return new BasicEvaluatedExpression()
1284 .setString("string")
1285 .setSideEffects()
1286 .setRange(/** @type {Range} */ (expr.range));
1287 }
1288 if (arg.isUndefined()) {
1289 return new BasicEvaluatedExpression()
1290 .setString("undefined")
1291 .setRange(/** @type {Range} */ (expr.range));
1292 }
1293 if (arg.isNumber()) {
1294 return new BasicEvaluatedExpression()
1295 .setString("number")
1296 .setRange(/** @type {Range} */ (expr.range));
1297 }
1298 if (arg.isBigInt()) {
1299 return new BasicEvaluatedExpression()
1300 .setString("bigint")
1301 .setRange(/** @type {Range} */ (expr.range));
1302 }
1303 if (arg.isBoolean()) {
1304 return new BasicEvaluatedExpression()
1305 .setString("boolean")
1306 .setRange(/** @type {Range} */ (expr.range));
1307 }
1308 if (arg.isConstArray() || arg.isRegExp() || arg.isNull()) {
1309 return new BasicEvaluatedExpression()
1310 .setString("object")
1311 .setRange(/** @type {Range} */ (expr.range));
1312 }
1313 if (arg.isArray()) {
1314 return new BasicEvaluatedExpression()
1315 .setString("object")
1316 .setSideEffects(arg.couldHaveSideEffects())
1317 .setRange(/** @type {Range} */ (expr.range));
1318 }
1319 } else if (expr.operator === "!") {
1320 const argument = this.evaluateExpression(expr.argument);
1321 const bool = argument.asBool();
1322 if (typeof bool !== "boolean") return;
1323 return new BasicEvaluatedExpression()
1324 .setBoolean(!bool)
1325 .setSideEffects(argument.couldHaveSideEffects())
1326 .setRange(/** @type {Range} */ (expr.range));
1327 } else if (expr.operator === "~") {
1328 return handleConstOperation(v => ~v);
1329 } else if (expr.operator === "+") {
1330 // eslint-disable-next-line no-implicit-coercion
1331 return handleConstOperation(v => +v);
1332 } else if (expr.operator === "-") {
1333 return handleConstOperation(v => -v);
1334 }
1335 });
1336 this.hooks.evaluateTypeof
1337 .for("undefined")
1338 .tap("JavascriptParser", expr =>
1339 new BasicEvaluatedExpression()
1340 .setString("undefined")
1341 .setRange(/** @type {Range} */ (expr.range))
1342 );
1343 this.hooks.evaluate.for("Identifier").tap("JavascriptParser", expr => {
1344 if (/** @type {Identifier} */ (expr).name === "undefined") {
1345 return new BasicEvaluatedExpression()
1346 .setUndefined()
1347 .setRange(/** @type {Range} */ (expr.range));
1348 }
1349 });
1350 /**
1351 * @param {"Identifier" | "ThisExpression" | "MemberExpression"} exprType expression type name
1352 * @param {function(Expression | SpreadElement): GetInfoResult | undefined} getInfo get info
1353 * @returns {void}
1354 */
1355 const tapEvaluateWithVariableInfo = (exprType, getInfo) => {
1356 /** @type {Expression | undefined} */
1357 let cachedExpression;
1358 /** @type {GetInfoResult | undefined} */
1359 let cachedInfo;
1360 this.hooks.evaluate.for(exprType).tap("JavascriptParser", expr => {
1361 const expression =
1362 /** @type {Identifier | ThisExpression | MemberExpression} */ (expr);
1363
1364 const info = getInfo(expression);
1365 if (info !== undefined) {
1366 return this.callHooksForInfoWithFallback(
1367 this.hooks.evaluateIdentifier,
1368 info.name,
1369 name => {
1370 cachedExpression = expression;
1371 cachedInfo = info;
1372 },
1373 name => {
1374 const hook = this.hooks.evaluateDefinedIdentifier.get(name);
1375 if (hook !== undefined) {
1376 return hook.call(expression);
1377 }
1378 },
1379 expression
1380 );
1381 }
1382 });
1383 this.hooks.evaluate
1384 .for(exprType)
1385 .tap({ name: "JavascriptParser", stage: 100 }, expr => {
1386 const expression =
1387 /** @type {Identifier | ThisExpression | MemberExpression} */
1388 (expr);
1389 const info =
1390 cachedExpression === expression ? cachedInfo : getInfo(expression);
1391 if (info !== undefined) {
1392 return new BasicEvaluatedExpression()
1393 .setIdentifier(
1394 info.name,
1395 info.rootInfo,
1396 info.getMembers,
1397 info.getMembersOptionals,
1398 info.getMemberRanges
1399 )
1400 .setRange(/** @type {Range} */ (expression.range));
1401 }
1402 });
1403 this.hooks.finish.tap("JavascriptParser", () => {
1404 // Cleanup for GC
1405 cachedExpression = cachedInfo = undefined;
1406 });
1407 };
1408 tapEvaluateWithVariableInfo("Identifier", expr => {
1409 const info = this.getVariableInfo(/** @type {Identifier} */ (expr).name);
1410 if (
1411 typeof info === "string" ||
1412 (info instanceof VariableInfo && typeof info.freeName === "string")
1413 ) {
1414 return {
1415 name: info,
1416 rootInfo: info,
1417 getMembers: () => [],
1418 getMembersOptionals: () => [],
1419 getMemberRanges: () => []
1420 };
1421 }
1422 });
1423 tapEvaluateWithVariableInfo("ThisExpression", expr => {
1424 const info = this.getVariableInfo("this");
1425 if (
1426 typeof info === "string" ||
1427 (info instanceof VariableInfo && typeof info.freeName === "string")
1428 ) {
1429 return {
1430 name: info,
1431 rootInfo: info,
1432 getMembers: () => [],
1433 getMembersOptionals: () => [],
1434 getMemberRanges: () => []
1435 };
1436 }
1437 });
1438 this.hooks.evaluate.for("MetaProperty").tap("JavascriptParser", expr => {
1439 const metaProperty = /** @type {MetaProperty} */ (expr);
1440
1441 return this.callHooksForName(
1442 this.hooks.evaluateIdentifier,
1443 /** @type {string} */ (getRootName(metaProperty)),
1444 metaProperty
1445 );
1446 });
1447 tapEvaluateWithVariableInfo("MemberExpression", expr =>
1448 this.getMemberExpressionInfo(
1449 /** @type {MemberExpression} */ (expr),
1450 ALLOWED_MEMBER_TYPES_EXPRESSION
1451 )
1452 );
1453
1454 this.hooks.evaluate.for("CallExpression").tap("JavascriptParser", _expr => {
1455 const expr = /** @type {CallExpression} */ (_expr);
1456 if (
1457 expr.callee.type === "MemberExpression" &&
1458 expr.callee.property.type ===
1459 (expr.callee.computed ? "Literal" : "Identifier")
1460 ) {
1461 // type Super also possible here
1462 const param = this.evaluateExpression(
1463 /** @type {Expression} */ (expr.callee.object)
1464 );
1465 const property =
1466 expr.callee.property.type === "Literal"
1467 ? `${expr.callee.property.value}`
1468 : expr.callee.property.name;
1469 const hook = this.hooks.evaluateCallExpressionMember.get(property);
1470 if (hook !== undefined) {
1471 return hook.call(expr, param);
1472 }
1473 } else if (expr.callee.type === "Identifier") {
1474 return this.callHooksForName(
1475 this.hooks.evaluateCallExpression,
1476 expr.callee.name,
1477 expr
1478 );
1479 }
1480 });
1481 this.hooks.evaluateCallExpressionMember
1482 .for("indexOf")
1483 .tap("JavascriptParser", (expr, param) => {
1484 if (!param.isString()) return;
1485 if (expr.arguments.length === 0) return;
1486 const [arg1, arg2] = expr.arguments;
1487 if (arg1.type === "SpreadElement") return;
1488 const arg1Eval = this.evaluateExpression(arg1);
1489 if (!arg1Eval.isString()) return;
1490 const arg1Value = /** @type {string} */ (arg1Eval.string);
1491
1492 let result;
1493 if (arg2) {
1494 if (arg2.type === "SpreadElement") return;
1495 const arg2Eval = this.evaluateExpression(arg2);
1496 if (!arg2Eval.isNumber()) return;
1497 result = /** @type {string} */ (param.string).indexOf(
1498 arg1Value,
1499 arg2Eval.number
1500 );
1501 } else {
1502 result = /** @type {string} */ (param.string).indexOf(arg1Value);
1503 }
1504 return new BasicEvaluatedExpression()
1505 .setNumber(result)
1506 .setSideEffects(param.couldHaveSideEffects())
1507 .setRange(/** @type {Range} */ (expr.range));
1508 });
1509 this.hooks.evaluateCallExpressionMember
1510 .for("replace")
1511 .tap("JavascriptParser", (expr, param) => {
1512 if (!param.isString()) return;
1513 if (expr.arguments.length !== 2) return;
1514 if (expr.arguments[0].type === "SpreadElement") return;
1515 if (expr.arguments[1].type === "SpreadElement") return;
1516 const arg1 = this.evaluateExpression(expr.arguments[0]);
1517 const arg2 = this.evaluateExpression(expr.arguments[1]);
1518 if (!arg1.isString() && !arg1.isRegExp()) return;
1519 const arg1Value = /** @type {string | RegExp} */ (
1520 arg1.regExp || arg1.string
1521 );
1522 if (!arg2.isString()) return;
1523 const arg2Value = /** @type {string} */ (arg2.string);
1524 return new BasicEvaluatedExpression()
1525 .setString(
1526 /** @type {string} */ (param.string).replace(arg1Value, arg2Value)
1527 )
1528 .setSideEffects(param.couldHaveSideEffects())
1529 .setRange(/** @type {Range} */ (expr.range));
1530 });
1531 for (const fn of ["substr", "substring", "slice"]) {
1532 this.hooks.evaluateCallExpressionMember
1533 .for(fn)
1534 .tap("JavascriptParser", (expr, param) => {
1535 if (!param.isString()) return;
1536 let arg1;
1537 let result;
1538 const str = /** @type {string} */ (param.string);
1539 switch (expr.arguments.length) {
1540 case 1:
1541 if (expr.arguments[0].type === "SpreadElement") return;
1542 arg1 = this.evaluateExpression(expr.arguments[0]);
1543 if (!arg1.isNumber()) return;
1544 result = str[
1545 /** @type {"substr" | "substring" | "slice"} */ (fn)
1546 ](/** @type {number} */ (arg1.number));
1547 break;
1548 case 2: {
1549 if (expr.arguments[0].type === "SpreadElement") return;
1550 if (expr.arguments[1].type === "SpreadElement") return;
1551 arg1 = this.evaluateExpression(expr.arguments[0]);
1552 const arg2 = this.evaluateExpression(expr.arguments[1]);
1553 if (!arg1.isNumber()) return;
1554 if (!arg2.isNumber()) return;
1555 result = str[
1556 /** @type {"substr" | "substring" | "slice"} */ (fn)
1557 ](
1558 /** @type {number} */ (arg1.number),
1559 /** @type {number} */ (arg2.number)
1560 );
1561 break;
1562 }
1563 default:
1564 return;
1565 }
1566 return new BasicEvaluatedExpression()
1567 .setString(result)
1568 .setSideEffects(param.couldHaveSideEffects())
1569 .setRange(/** @type {Range} */ (expr.range));
1570 });
1571 }
1572
1573 /**
1574 * @param {"cooked" | "raw"} kind kind of values to get
1575 * @param {TemplateLiteral} templateLiteralExpr TemplateLiteral expr
1576 * @returns {{quasis: BasicEvaluatedExpression[], parts: BasicEvaluatedExpression[]}} Simplified template
1577 */
1578 const getSimplifiedTemplateResult = (kind, templateLiteralExpr) => {
1579 /** @type {BasicEvaluatedExpression[]} */
1580 const quasis = [];
1581 /** @type {BasicEvaluatedExpression[]} */
1582 const parts = [];
1583
1584 for (let i = 0; i < templateLiteralExpr.quasis.length; i++) {
1585 const quasiExpr = templateLiteralExpr.quasis[i];
1586 const quasi = quasiExpr.value[kind];
1587
1588 if (i > 0) {
1589 const prevExpr = parts[parts.length - 1];
1590 const expr = this.evaluateExpression(
1591 templateLiteralExpr.expressions[i - 1]
1592 );
1593 const exprAsString = expr.asString();
1594 if (
1595 typeof exprAsString === "string" &&
1596 !expr.couldHaveSideEffects()
1597 ) {
1598 // We can merge quasi + expr + quasi when expr
1599 // is a const string
1600
1601 prevExpr.setString(prevExpr.string + exprAsString + quasi);
1602 prevExpr.setRange([
1603 /** @type {Range} */ (prevExpr.range)[0],
1604 /** @type {Range} */ (quasiExpr.range)[1]
1605 ]);
1606 // We unset the expression as it doesn't match to a single expression
1607 prevExpr.setExpression(undefined);
1608 continue;
1609 }
1610 parts.push(expr);
1611 }
1612
1613 const part = new BasicEvaluatedExpression()
1614 .setString(/** @type {string} */ (quasi))
1615 .setRange(/** @type {Range} */ (quasiExpr.range))
1616 .setExpression(quasiExpr);
1617 quasis.push(part);
1618 parts.push(part);
1619 }
1620 return {
1621 quasis,
1622 parts
1623 };
1624 };
1625
1626 this.hooks.evaluate
1627 .for("TemplateLiteral")
1628 .tap("JavascriptParser", _node => {
1629 const node = /** @type {TemplateLiteral} */ (_node);
1630
1631 const { quasis, parts } = getSimplifiedTemplateResult("cooked", node);
1632 if (parts.length === 1) {
1633 return parts[0].setRange(/** @type {Range} */ (node.range));
1634 }
1635 return new BasicEvaluatedExpression()
1636 .setTemplateString(quasis, parts, "cooked")
1637 .setRange(/** @type {Range} */ (node.range));
1638 });
1639 this.hooks.evaluate
1640 .for("TaggedTemplateExpression")
1641 .tap("JavascriptParser", _node => {
1642 const node = /** @type {TaggedTemplateExpression} */ (_node);
1643 const tag = this.evaluateExpression(node.tag);
1644
1645 if (tag.isIdentifier() && tag.identifier === "String.raw") {
1646 const { quasis, parts } = getSimplifiedTemplateResult(
1647 "raw",
1648 node.quasi
1649 );
1650 return new BasicEvaluatedExpression()
1651 .setTemplateString(quasis, parts, "raw")
1652 .setRange(/** @type {Range} */ (node.range));
1653 }
1654 });
1655
1656 this.hooks.evaluateCallExpressionMember
1657 .for("concat")
1658 .tap("JavascriptParser", (expr, param) => {
1659 if (!param.isString() && !param.isWrapped()) return;
1660 let stringSuffix = null;
1661 let hasUnknownParams = false;
1662 const innerExpressions = [];
1663 for (let i = expr.arguments.length - 1; i >= 0; i--) {
1664 const arg = expr.arguments[i];
1665 if (arg.type === "SpreadElement") return;
1666 const argExpr = this.evaluateExpression(arg);
1667 if (
1668 hasUnknownParams ||
1669 (!argExpr.isString() && !argExpr.isNumber())
1670 ) {
1671 hasUnknownParams = true;
1672 innerExpressions.push(argExpr);
1673 continue;
1674 }
1675
1676 /** @type {string} */
1677 const value = argExpr.isString()
1678 ? /** @type {string} */ (argExpr.string)
1679 : String(/** @type {number} */ (argExpr.number));
1680
1681 /** @type {string} */
1682 const newString = value + (stringSuffix ? stringSuffix.string : "");
1683 const newRange = /** @type {Range} */ ([
1684 /** @type {Range} */ (argExpr.range)[0],
1685 /** @type {Range} */ ((stringSuffix || argExpr).range)[1]
1686 ]);
1687 stringSuffix = new BasicEvaluatedExpression()
1688 .setString(newString)
1689 .setSideEffects(
1690 (stringSuffix && stringSuffix.couldHaveSideEffects()) ||
1691 argExpr.couldHaveSideEffects()
1692 )
1693 .setRange(newRange);
1694 }
1695
1696 if (hasUnknownParams) {
1697 const prefix = param.isString() ? param : param.prefix;
1698 const inner =
1699 param.isWrapped() && param.wrappedInnerExpressions
1700 ? param.wrappedInnerExpressions.concat(innerExpressions.reverse())
1701 : innerExpressions.reverse();
1702 return new BasicEvaluatedExpression()
1703 .setWrapped(prefix, stringSuffix, inner)
1704 .setRange(/** @type {Range} */ (expr.range));
1705 } else if (param.isWrapped()) {
1706 const postfix = stringSuffix || param.postfix;
1707 const inner = param.wrappedInnerExpressions
1708 ? param.wrappedInnerExpressions.concat(innerExpressions.reverse())
1709 : innerExpressions.reverse();
1710 return new BasicEvaluatedExpression()
1711 .setWrapped(param.prefix, postfix, inner)
1712 .setRange(/** @type {Range} */ (expr.range));
1713 }
1714 const newString =
1715 /** @type {string} */ (param.string) +
1716 (stringSuffix ? stringSuffix.string : "");
1717 return new BasicEvaluatedExpression()
1718 .setString(newString)
1719 .setSideEffects(
1720 (stringSuffix && stringSuffix.couldHaveSideEffects()) ||
1721 param.couldHaveSideEffects()
1722 )
1723 .setRange(/** @type {Range} */ (expr.range));
1724 });
1725 this.hooks.evaluateCallExpressionMember
1726 .for("split")
1727 .tap("JavascriptParser", (expr, param) => {
1728 if (!param.isString()) return;
1729 if (expr.arguments.length !== 1) return;
1730 if (expr.arguments[0].type === "SpreadElement") return;
1731 let result;
1732 const arg = this.evaluateExpression(expr.arguments[0]);
1733 if (arg.isString()) {
1734 result =
1735 /** @type {string} */
1736 (param.string).split(/** @type {string} */ (arg.string));
1737 } else if (arg.isRegExp()) {
1738 result = /** @type {string} */ (param.string).split(
1739 /** @type {RegExp} */ (arg.regExp)
1740 );
1741 } else {
1742 return;
1743 }
1744 return new BasicEvaluatedExpression()
1745 .setArray(result)
1746 .setSideEffects(param.couldHaveSideEffects())
1747 .setRange(/** @type {Range} */ (expr.range));
1748 });
1749 this.hooks.evaluate
1750 .for("ConditionalExpression")
1751 .tap("JavascriptParser", _expr => {
1752 const expr = /** @type {ConditionalExpression} */ (_expr);
1753
1754 const condition = this.evaluateExpression(expr.test);
1755 const conditionValue = condition.asBool();
1756 let res;
1757 if (conditionValue === undefined) {
1758 const consequent = this.evaluateExpression(expr.consequent);
1759 const alternate = this.evaluateExpression(expr.alternate);
1760 res = new BasicEvaluatedExpression();
1761 if (consequent.isConditional()) {
1762 res.setOptions(
1763 /** @type {BasicEvaluatedExpression[]} */ (consequent.options)
1764 );
1765 } else {
1766 res.setOptions([consequent]);
1767 }
1768 if (alternate.isConditional()) {
1769 res.addOptions(
1770 /** @type {BasicEvaluatedExpression[]} */ (alternate.options)
1771 );
1772 } else {
1773 res.addOptions([alternate]);
1774 }
1775 } else {
1776 res = this.evaluateExpression(
1777 conditionValue ? expr.consequent : expr.alternate
1778 );
1779 if (condition.couldHaveSideEffects()) res.setSideEffects();
1780 }
1781 res.setRange(/** @type {Range} */ (expr.range));
1782 return res;
1783 });
1784 this.hooks.evaluate
1785 .for("ArrayExpression")
1786 .tap("JavascriptParser", _expr => {
1787 const expr = /** @type {ArrayExpression} */ (_expr);
1788
1789 const items = expr.elements.map(
1790 element =>
1791 element !== null &&
1792 element.type !== "SpreadElement" &&
1793 this.evaluateExpression(element)
1794 );
1795 if (!items.every(Boolean)) return;
1796 return new BasicEvaluatedExpression()
1797 .setItems(/** @type {BasicEvaluatedExpression[]} */ (items))
1798 .setRange(/** @type {Range} */ (expr.range));
1799 });
1800 this.hooks.evaluate
1801 .for("ChainExpression")
1802 .tap("JavascriptParser", _expr => {
1803 const expr = /** @type {ChainExpression} */ (_expr);
1804 /** @type {Expression[]} */
1805 const optionalExpressionsStack = [];
1806 /** @type {Expression|Super} */
1807 let next = expr.expression;
1808
1809 while (
1810 next.type === "MemberExpression" ||
1811 next.type === "CallExpression"
1812 ) {
1813 if (next.type === "MemberExpression") {
1814 if (next.optional) {
1815 // SuperNode can not be optional
1816 optionalExpressionsStack.push(
1817 /** @type {Expression} */ (next.object)
1818 );
1819 }
1820 next = next.object;
1821 } else {
1822 if (next.optional) {
1823 // SuperNode can not be optional
1824 optionalExpressionsStack.push(
1825 /** @type {Expression} */ (next.callee)
1826 );
1827 }
1828 next = next.callee;
1829 }
1830 }
1831
1832 while (optionalExpressionsStack.length > 0) {
1833 const expression =
1834 /** @type {Expression} */
1835 (optionalExpressionsStack.pop());
1836 const evaluated = this.evaluateExpression(expression);
1837
1838 if (evaluated.asNullish()) {
1839 return evaluated.setRange(/** @type {Range} */ (_expr.range));
1840 }
1841 }
1842 return this.evaluateExpression(expr.expression);
1843 });
1844 }
1845
1846 /**
1847 * @param {Expression} node node
1848 * @returns {Set<DestructuringAssignmentProperty> | undefined} destructured identifiers
1849 */
1850 destructuringAssignmentPropertiesFor(node) {
1851 if (!this.destructuringAssignmentProperties) return;
1852 return this.destructuringAssignmentProperties.get(node);
1853 }
1854
1855 /**
1856 * @param {Expression | SpreadElement} expr expression
1857 * @returns {string | VariableInfoInterface | undefined} identifier
1858 */
1859 getRenameIdentifier(expr) {
1860 const result = this.evaluateExpression(expr);
1861 if (result.isIdentifier()) {
1862 return result.identifier;
1863 }
1864 }
1865
1866 /**
1867 * @param {ClassExpression | ClassDeclaration} classy a class node
1868 * @returns {void}
1869 */
1870 walkClass(classy) {
1871 if (
1872 classy.superClass &&
1873 !this.hooks.classExtendsExpression.call(classy.superClass, classy)
1874 ) {
1875 this.walkExpression(classy.superClass);
1876 }
1877 if (classy.body && classy.body.type === "ClassBody") {
1878 const scopeParams = [];
1879 // Add class name in scope for recursive calls
1880 if (classy.id) {
1881 scopeParams.push(classy.id);
1882 }
1883 this.inClassScope(true, scopeParams, () => {
1884 for (const classElement of /** @type {TODO} */ (classy.body.body)) {
1885 if (!this.hooks.classBodyElement.call(classElement, classy)) {
1886 if (classElement.computed && classElement.key) {
1887 this.walkExpression(classElement.key);
1888 }
1889 if (classElement.value) {
1890 if (
1891 !this.hooks.classBodyValue.call(
1892 classElement.value,
1893 classElement,
1894 classy
1895 )
1896 ) {
1897 const wasTopLevel = this.scope.topLevelScope;
1898 this.scope.topLevelScope = false;
1899 this.walkExpression(classElement.value);
1900 this.scope.topLevelScope = wasTopLevel;
1901 }
1902 } else if (classElement.type === "StaticBlock") {
1903 const wasTopLevel = this.scope.topLevelScope;
1904 this.scope.topLevelScope = false;
1905 this.walkBlockStatement(classElement);
1906 this.scope.topLevelScope = wasTopLevel;
1907 }
1908 }
1909 }
1910 });
1911 }
1912 }
1913
1914 /**
1915 * Pre walking iterates the scope for variable declarations
1916 * @param {(Statement | ModuleDeclaration)[]} statements statements
1917 */
1918 preWalkStatements(statements) {
1919 for (let index = 0, len = statements.length; index < len; index++) {
1920 const statement = statements[index];
1921 this.preWalkStatement(statement);
1922 }
1923 }
1924
1925 /**
1926 * Block pre walking iterates the scope for block variable declarations
1927 * @param {(Statement | ModuleDeclaration)[]} statements statements
1928 */
1929 blockPreWalkStatements(statements) {
1930 for (let index = 0, len = statements.length; index < len; index++) {
1931 const statement = statements[index];
1932 this.blockPreWalkStatement(statement);
1933 }
1934 }
1935
1936 /**
1937 * Walking iterates the statements and expressions and processes them
1938 * @param {(Statement | ModuleDeclaration)[]} statements statements
1939 */
1940 walkStatements(statements) {
1941 for (let index = 0, len = statements.length; index < len; index++) {
1942 const statement = statements[index];
1943 this.walkStatement(statement);
1944 }
1945 }
1946
1947 /**
1948 * Walking iterates the statements and expressions and processes them
1949 * @param {Statement | ModuleDeclaration} statement statement
1950 */
1951 preWalkStatement(statement) {
1952 /** @type {StatementPath} */
1953 (this.statementPath).push(statement);
1954 if (this.hooks.preStatement.call(statement)) {
1955 this.prevStatement =
1956 /** @type {StatementPath} */
1957 (this.statementPath).pop();
1958 return;
1959 }
1960 switch (statement.type) {
1961 case "BlockStatement":
1962 this.preWalkBlockStatement(statement);
1963 break;
1964 case "DoWhileStatement":
1965 this.preWalkDoWhileStatement(statement);
1966 break;
1967 case "ForInStatement":
1968 this.preWalkForInStatement(statement);
1969 break;
1970 case "ForOfStatement":
1971 this.preWalkForOfStatement(statement);
1972 break;
1973 case "ForStatement":
1974 this.preWalkForStatement(statement);
1975 break;
1976 case "FunctionDeclaration":
1977 this.preWalkFunctionDeclaration(statement);
1978 break;
1979 case "IfStatement":
1980 this.preWalkIfStatement(statement);
1981 break;
1982 case "LabeledStatement":
1983 this.preWalkLabeledStatement(statement);
1984 break;
1985 case "SwitchStatement":
1986 this.preWalkSwitchStatement(statement);
1987 break;
1988 case "TryStatement":
1989 this.preWalkTryStatement(statement);
1990 break;
1991 case "VariableDeclaration":
1992 this.preWalkVariableDeclaration(statement);
1993 break;
1994 case "WhileStatement":
1995 this.preWalkWhileStatement(statement);
1996 break;
1997 case "WithStatement":
1998 this.preWalkWithStatement(statement);
1999 break;
2000 }
2001 this.prevStatement =
2002 /** @type {StatementPath} */
2003 (this.statementPath).pop();
2004 }
2005
2006 /**
2007 * @param {Statement | ModuleDeclaration} statement statement
2008 */
2009 blockPreWalkStatement(statement) {
2010 /** @type {StatementPath} */
2011 (this.statementPath).push(statement);
2012 if (this.hooks.blockPreStatement.call(statement)) {
2013 this.prevStatement =
2014 /** @type {StatementPath} */
2015 (this.statementPath).pop();
2016 return;
2017 }
2018 switch (statement.type) {
2019 case "ImportDeclaration":
2020 this.blockPreWalkImportDeclaration(statement);
2021 break;
2022 case "ExportAllDeclaration":
2023 this.blockPreWalkExportAllDeclaration(statement);
2024 break;
2025 case "ExportDefaultDeclaration":
2026 this.blockPreWalkExportDefaultDeclaration(statement);
2027 break;
2028 case "ExportNamedDeclaration":
2029 this.blockPreWalkExportNamedDeclaration(statement);
2030 break;
2031 case "VariableDeclaration":
2032 this.blockPreWalkVariableDeclaration(statement);
2033 break;
2034 case "ClassDeclaration":
2035 this.blockPreWalkClassDeclaration(statement);
2036 break;
2037 case "ExpressionStatement":
2038 this.blockPreWalkExpressionStatement(statement);
2039 }
2040 this.prevStatement =
2041 /** @type {StatementPath} */
2042 (this.statementPath).pop();
2043 }
2044
2045 /**
2046 * @param {Statement | ModuleDeclaration} statement statement
2047 */
2048 walkStatement(statement) {
2049 /** @type {StatementPath} */
2050 (this.statementPath).push(statement);
2051 if (this.hooks.statement.call(statement) !== undefined) {
2052 this.prevStatement =
2053 /** @type {StatementPath} */
2054 (this.statementPath).pop();
2055 return;
2056 }
2057 switch (statement.type) {
2058 case "BlockStatement":
2059 this.walkBlockStatement(statement);
2060 break;
2061 case "ClassDeclaration":
2062 this.walkClassDeclaration(statement);
2063 break;
2064 case "DoWhileStatement":
2065 this.walkDoWhileStatement(statement);
2066 break;
2067 case "ExportDefaultDeclaration":
2068 this.walkExportDefaultDeclaration(statement);
2069 break;
2070 case "ExportNamedDeclaration":
2071 this.walkExportNamedDeclaration(statement);
2072 break;
2073 case "ExpressionStatement":
2074 this.walkExpressionStatement(statement);
2075 break;
2076 case "ForInStatement":
2077 this.walkForInStatement(statement);
2078 break;
2079 case "ForOfStatement":
2080 this.walkForOfStatement(statement);
2081 break;
2082 case "ForStatement":
2083 this.walkForStatement(statement);
2084 break;
2085 case "FunctionDeclaration":
2086 this.walkFunctionDeclaration(statement);
2087 break;
2088 case "IfStatement":
2089 this.walkIfStatement(statement);
2090 break;
2091 case "LabeledStatement":
2092 this.walkLabeledStatement(statement);
2093 break;
2094 case "ReturnStatement":
2095 this.walkReturnStatement(statement);
2096 break;
2097 case "SwitchStatement":
2098 this.walkSwitchStatement(statement);
2099 break;
2100 case "ThrowStatement":
2101 this.walkThrowStatement(statement);
2102 break;
2103 case "TryStatement":
2104 this.walkTryStatement(statement);
2105 break;
2106 case "VariableDeclaration":
2107 this.walkVariableDeclaration(statement);
2108 break;
2109 case "WhileStatement":
2110 this.walkWhileStatement(statement);
2111 break;
2112 case "WithStatement":
2113 this.walkWithStatement(statement);
2114 break;
2115 }
2116 this.prevStatement =
2117 /** @type {StatementPath} */
2118 (this.statementPath).pop();
2119 }
2120
2121 /**
2122 * Walks a statements that is nested within a parent statement
2123 * and can potentially be a non-block statement.
2124 * This enforces the nested statement to never be in ASI position.
2125 * @param {Statement} statement the nested statement
2126 */
2127 walkNestedStatement(statement) {
2128 this.prevStatement = undefined;
2129 this.walkStatement(statement);
2130 }
2131
2132 // Real Statements
2133 /**
2134 * @param {BlockStatement} statement block statement
2135 */
2136 preWalkBlockStatement(statement) {
2137 this.preWalkStatements(statement.body);
2138 }
2139
2140 /**
2141 * @param {BlockStatement} statement block statement
2142 */
2143 walkBlockStatement(statement) {
2144 this.inBlockScope(() => {
2145 const body = statement.body;
2146 const prev = this.prevStatement;
2147 this.blockPreWalkStatements(body);
2148 this.prevStatement = prev;
2149 this.walkStatements(body);
2150 });
2151 }
2152
2153 /**
2154 * @param {ExpressionStatement} statement expression statement
2155 */
2156 walkExpressionStatement(statement) {
2157 this.walkExpression(statement.expression);
2158 }
2159
2160 /**
2161 * @param {IfStatement} statement if statement
2162 */
2163 preWalkIfStatement(statement) {
2164 this.preWalkStatement(statement.consequent);
2165 if (statement.alternate) {
2166 this.preWalkStatement(statement.alternate);
2167 }
2168 }
2169
2170 /**
2171 * @param {IfStatement} statement if statement
2172 */
2173 walkIfStatement(statement) {
2174 const result = this.hooks.statementIf.call(statement);
2175 if (result === undefined) {
2176 this.walkExpression(statement.test);
2177 this.walkNestedStatement(statement.consequent);
2178 if (statement.alternate) {
2179 this.walkNestedStatement(statement.alternate);
2180 }
2181 } else if (result) {
2182 this.walkNestedStatement(statement.consequent);
2183 } else if (statement.alternate) {
2184 this.walkNestedStatement(statement.alternate);
2185 }
2186 }
2187
2188 /**
2189 * @param {LabeledStatement} statement with statement
2190 */
2191 preWalkLabeledStatement(statement) {
2192 this.preWalkStatement(statement.body);
2193 }
2194
2195 /**
2196 * @param {LabeledStatement} statement with statement
2197 */
2198 walkLabeledStatement(statement) {
2199 const hook = this.hooks.label.get(statement.label.name);
2200 if (hook !== undefined) {
2201 const result = hook.call(statement);
2202 if (result === true) return;
2203 }
2204 this.walkNestedStatement(statement.body);
2205 }
2206
2207 /**
2208 * @param {WithStatement} statement with statement
2209 */
2210 preWalkWithStatement(statement) {
2211 this.preWalkStatement(statement.body);
2212 }
2213
2214 /**
2215 * @param {WithStatement} statement with statement
2216 */
2217 walkWithStatement(statement) {
2218 this.walkExpression(statement.object);
2219 this.walkNestedStatement(statement.body);
2220 }
2221
2222 /**
2223 * @param {SwitchStatement} statement switch statement
2224 */
2225 preWalkSwitchStatement(statement) {
2226 this.preWalkSwitchCases(statement.cases);
2227 }
2228
2229 /**
2230 * @param {SwitchStatement} statement switch statement
2231 */
2232 walkSwitchStatement(statement) {
2233 this.walkExpression(statement.discriminant);
2234 this.walkSwitchCases(statement.cases);
2235 }
2236
2237 /**
2238 * @param {ReturnStatement | ThrowStatement} statement return or throw statement
2239 */
2240 walkTerminatingStatement(statement) {
2241 if (statement.argument) this.walkExpression(statement.argument);
2242 }
2243
2244 /**
2245 * @param {ReturnStatement} statement return statement
2246 */
2247 walkReturnStatement(statement) {
2248 this.walkTerminatingStatement(statement);
2249 }
2250
2251 /**
2252 * @param {ThrowStatement} statement return statement
2253 */
2254 walkThrowStatement(statement) {
2255 this.walkTerminatingStatement(statement);
2256 }
2257
2258 /**
2259 * @param {TryStatement} statement try statement
2260 */
2261 preWalkTryStatement(statement) {
2262 this.preWalkStatement(statement.block);
2263 if (statement.handler) this.preWalkCatchClause(statement.handler);
2264 if (statement.finalizer) this.preWalkStatement(statement.finalizer);
2265 }
2266
2267 /**
2268 * @param {TryStatement} statement try statement
2269 */
2270 walkTryStatement(statement) {
2271 if (this.scope.inTry) {
2272 this.walkStatement(statement.block);
2273 } else {
2274 this.scope.inTry = true;
2275 this.walkStatement(statement.block);
2276 this.scope.inTry = false;
2277 }
2278 if (statement.handler) this.walkCatchClause(statement.handler);
2279 if (statement.finalizer) this.walkStatement(statement.finalizer);
2280 }
2281
2282 /**
2283 * @param {WhileStatement} statement while statement
2284 */
2285 preWalkWhileStatement(statement) {
2286 this.preWalkStatement(statement.body);
2287 }
2288
2289 /**
2290 * @param {WhileStatement} statement while statement
2291 */
2292 walkWhileStatement(statement) {
2293 this.walkExpression(statement.test);
2294 this.walkNestedStatement(statement.body);
2295 }
2296
2297 /**
2298 * @param {DoWhileStatement} statement do while statement
2299 */
2300 preWalkDoWhileStatement(statement) {
2301 this.preWalkStatement(statement.body);
2302 }
2303
2304 /**
2305 * @param {DoWhileStatement} statement do while statement
2306 */
2307 walkDoWhileStatement(statement) {
2308 this.walkNestedStatement(statement.body);
2309 this.walkExpression(statement.test);
2310 }
2311
2312 /**
2313 * @param {ForStatement} statement for statement
2314 */
2315 preWalkForStatement(statement) {
2316 if (statement.init && statement.init.type === "VariableDeclaration") {
2317 this.preWalkStatement(statement.init);
2318 }
2319 this.preWalkStatement(statement.body);
2320 }
2321
2322 /**
2323 * @param {ForStatement} statement for statement
2324 */
2325 walkForStatement(statement) {
2326 this.inBlockScope(() => {
2327 if (statement.init) {
2328 if (statement.init.type === "VariableDeclaration") {
2329 this.blockPreWalkVariableDeclaration(statement.init);
2330 this.prevStatement = undefined;
2331 this.walkStatement(statement.init);
2332 } else {
2333 this.walkExpression(statement.init);
2334 }
2335 }
2336 if (statement.test) {
2337 this.walkExpression(statement.test);
2338 }
2339 if (statement.update) {
2340 this.walkExpression(statement.update);
2341 }
2342 const body = statement.body;
2343 if (body.type === "BlockStatement") {
2344 // no need to add additional scope
2345 const prev = this.prevStatement;
2346 this.blockPreWalkStatements(body.body);
2347 this.prevStatement = prev;
2348 this.walkStatements(body.body);
2349 } else {
2350 this.walkNestedStatement(body);
2351 }
2352 });
2353 }
2354
2355 /**
2356 * @param {ForInStatement} statement for statement
2357 */
2358 preWalkForInStatement(statement) {
2359 if (statement.left.type === "VariableDeclaration") {
2360 this.preWalkVariableDeclaration(statement.left);
2361 }
2362 this.preWalkStatement(statement.body);
2363 }
2364
2365 /**
2366 * @param {ForInStatement} statement for statement
2367 */
2368 walkForInStatement(statement) {
2369 this.inBlockScope(() => {
2370 if (statement.left.type === "VariableDeclaration") {
2371 this.blockPreWalkVariableDeclaration(statement.left);
2372 this.walkVariableDeclaration(statement.left);
2373 } else {
2374 this.walkPattern(statement.left);
2375 }
2376 this.walkExpression(statement.right);
2377 const body = statement.body;
2378 if (body.type === "BlockStatement") {
2379 // no need to add additional scope
2380 const prev = this.prevStatement;
2381 this.blockPreWalkStatements(body.body);
2382 this.prevStatement = prev;
2383 this.walkStatements(body.body);
2384 } else {
2385 this.walkNestedStatement(body);
2386 }
2387 });
2388 }
2389
2390 /**
2391 * @param {ForOfStatement} statement statement
2392 */
2393 preWalkForOfStatement(statement) {
2394 if (statement.await && this.scope.topLevelScope === true) {
2395 this.hooks.topLevelAwait.call(statement);
2396 }
2397 if (statement.left.type === "VariableDeclaration") {
2398 this.preWalkVariableDeclaration(statement.left);
2399 }
2400 this.preWalkStatement(statement.body);
2401 }
2402
2403 /**
2404 * @param {ForOfStatement} statement for statement
2405 */
2406 walkForOfStatement(statement) {
2407 this.inBlockScope(() => {
2408 if (statement.left.type === "VariableDeclaration") {
2409 this.blockPreWalkVariableDeclaration(statement.left);
2410 this.walkVariableDeclaration(statement.left);
2411 } else {
2412 this.walkPattern(statement.left);
2413 }
2414 this.walkExpression(statement.right);
2415 const body = statement.body;
2416 if (body.type === "BlockStatement") {
2417 // no need to add additional scope
2418 const prev = this.prevStatement;
2419 this.blockPreWalkStatements(body.body);
2420 this.prevStatement = prev;
2421 this.walkStatements(body.body);
2422 } else {
2423 this.walkNestedStatement(body);
2424 }
2425 });
2426 }
2427
2428 /**
2429 * @param {FunctionDeclaration} statement function declaration
2430 */
2431 preWalkFunctionDeclaration(statement) {
2432 if (statement.id) {
2433 this.defineVariable(statement.id.name);
2434 }
2435 }
2436
2437 /**
2438 * @param {FunctionDeclaration} statement function declaration
2439 */
2440 walkFunctionDeclaration(statement) {
2441 const wasTopLevel = this.scope.topLevelScope;
2442 this.scope.topLevelScope = false;
2443 this.inFunctionScope(true, statement.params, () => {
2444 for (const param of statement.params) {
2445 this.walkPattern(param);
2446 }
2447 if (statement.body.type === "BlockStatement") {
2448 this.detectMode(statement.body.body);
2449 const prev = this.prevStatement;
2450 this.preWalkStatement(statement.body);
2451 this.prevStatement = prev;
2452 this.walkStatement(statement.body);
2453 } else {
2454 this.walkExpression(statement.body);
2455 }
2456 });
2457 this.scope.topLevelScope = wasTopLevel;
2458 }
2459
2460 /**
2461 * @param {ExpressionStatement} statement expression statement
2462 */
2463 blockPreWalkExpressionStatement(statement) {
2464 const expression = statement.expression;
2465 switch (expression.type) {
2466 case "AssignmentExpression":
2467 this.preWalkAssignmentExpression(expression);
2468 }
2469 }
2470
2471 /**
2472 * @param {AssignmentExpression} expression assignment expression
2473 */
2474 preWalkAssignmentExpression(expression) {
2475 if (
2476 expression.left.type !== "ObjectPattern" ||
2477 !this.destructuringAssignmentProperties
2478 )
2479 return;
2480 const keys = this._preWalkObjectPattern(expression.left);
2481 if (!keys) return;
2482
2483 // check multiple assignments
2484 if (this.destructuringAssignmentProperties.has(expression)) {
2485 const set =
2486 /** @type {Set<DestructuringAssignmentProperty>} */
2487 (this.destructuringAssignmentProperties.get(expression));
2488 this.destructuringAssignmentProperties.delete(expression);
2489 for (const id of set) keys.add(id);
2490 }
2491
2492 this.destructuringAssignmentProperties.set(
2493 expression.right.type === "AwaitExpression"
2494 ? expression.right.argument
2495 : expression.right,
2496 keys
2497 );
2498
2499 if (expression.right.type === "AssignmentExpression") {
2500 this.preWalkAssignmentExpression(expression.right);
2501 }
2502 }
2503
2504 /**
2505 * @param {ImportDeclaration} statement statement
2506 */
2507 blockPreWalkImportDeclaration(statement) {
2508 const source = /** @type {ImportSource} */ (statement.source.value);
2509 this.hooks.import.call(statement, source);
2510 for (const specifier of statement.specifiers) {
2511 const name = specifier.local.name;
2512 switch (specifier.type) {
2513 case "ImportDefaultSpecifier":
2514 if (
2515 !this.hooks.importSpecifier.call(statement, source, "default", name)
2516 ) {
2517 this.defineVariable(name);
2518 }
2519 break;
2520 case "ImportSpecifier":
2521 if (
2522 !this.hooks.importSpecifier.call(
2523 statement,
2524 source,
2525 /** @type {Identifier} */
2526 (specifier.imported).name ||
2527 /** @type {string} */
2528 (
2529 /** @type {Literal} */
2530 (specifier.imported).value
2531 ),
2532 name
2533 )
2534 ) {
2535 this.defineVariable(name);
2536 }
2537 break;
2538 case "ImportNamespaceSpecifier":
2539 if (!this.hooks.importSpecifier.call(statement, source, null, name)) {
2540 this.defineVariable(name);
2541 }
2542 break;
2543 default:
2544 this.defineVariable(name);
2545 }
2546 }
2547 }
2548
2549 /**
2550 * @param {Declaration} declaration declaration
2551 * @param {OnIdent} onIdent on ident callback
2552 */
2553 enterDeclaration(declaration, onIdent) {
2554 switch (declaration.type) {
2555 case "VariableDeclaration":
2556 for (const declarator of declaration.declarations) {
2557 switch (declarator.type) {
2558 case "VariableDeclarator": {
2559 this.enterPattern(declarator.id, onIdent);
2560 break;
2561 }
2562 }
2563 }
2564 break;
2565 case "FunctionDeclaration":
2566 this.enterPattern(declaration.id, onIdent);
2567 break;
2568 case "ClassDeclaration":
2569 this.enterPattern(declaration.id, onIdent);
2570 break;
2571 }
2572 }
2573
2574 /**
2575 * @param {ExportNamedDeclaration} statement statement
2576 */
2577 blockPreWalkExportNamedDeclaration(statement) {
2578 let source;
2579 if (statement.source) {
2580 source = /** @type {ImportSource} */ (statement.source.value);
2581 this.hooks.exportImport.call(statement, source);
2582 } else {
2583 this.hooks.export.call(statement);
2584 }
2585 if (
2586 statement.declaration &&
2587 !this.hooks.exportDeclaration.call(statement, statement.declaration)
2588 ) {
2589 const prev = this.prevStatement;
2590 this.preWalkStatement(statement.declaration);
2591 this.prevStatement = prev;
2592 this.blockPreWalkStatement(statement.declaration);
2593 let index = 0;
2594 this.enterDeclaration(statement.declaration, def => {
2595 this.hooks.exportSpecifier.call(statement, def, def, index++);
2596 });
2597 }
2598 if (statement.specifiers) {
2599 for (
2600 let specifierIndex = 0;
2601 specifierIndex < statement.specifiers.length;
2602 specifierIndex++
2603 ) {
2604 const specifier = statement.specifiers[specifierIndex];
2605 switch (specifier.type) {
2606 case "ExportSpecifier": {
2607 const localName =
2608 /** @type {Identifier} */ (specifier.local).name ||
2609 /** @type {string} */ (
2610 /** @type {Literal} */ (specifier.local).value
2611 );
2612 const name =
2613 /** @type {Identifier} */
2614 (specifier.exported).name ||
2615 /** @type {string} */
2616 (/** @type {Literal} */ (specifier.exported).value);
2617 if (source) {
2618 this.hooks.exportImportSpecifier.call(
2619 statement,
2620 source,
2621 localName,
2622 name,
2623 specifierIndex
2624 );
2625 } else {
2626 this.hooks.exportSpecifier.call(
2627 statement,
2628 localName,
2629 name,
2630 specifierIndex
2631 );
2632 }
2633 break;
2634 }
2635 }
2636 }
2637 }
2638 }
2639
2640 /**
2641 * @param {ExportNamedDeclaration} statement the statement
2642 */
2643 walkExportNamedDeclaration(statement) {
2644 if (statement.declaration) {
2645 this.walkStatement(statement.declaration);
2646 }
2647 }
2648
2649 /**
2650 * @param {TODO} statement statement
2651 */
2652 blockPreWalkExportDefaultDeclaration(statement) {
2653 const prev = this.prevStatement;
2654 this.preWalkStatement(statement.declaration);
2655 this.prevStatement = prev;
2656 this.blockPreWalkStatement(statement.declaration);
2657 if (
2658 /** @type {FunctionDeclaration | ClassDeclaration} */ (
2659 statement.declaration
2660 ).id &&
2661 statement.declaration.type !== "FunctionExpression" &&
2662 statement.declaration.type !== "ClassExpression"
2663 ) {
2664 const declaration =
2665 /** @type {FunctionDeclaration | ClassDeclaration} */
2666 (statement.declaration);
2667 this.hooks.exportSpecifier.call(
2668 statement,
2669 declaration.id.name,
2670 "default",
2671 undefined
2672 );
2673 }
2674 }
2675
2676 /**
2677 * @param {ExportDefaultDeclaration} statement statement
2678 */
2679 walkExportDefaultDeclaration(statement) {
2680 this.hooks.export.call(statement);
2681 if (
2682 /** @type {FunctionDeclaration | ClassDeclaration} */ (
2683 statement.declaration
2684 ).id &&
2685 statement.declaration.type !== "FunctionExpression" &&
2686 statement.declaration.type !== "ClassExpression"
2687 ) {
2688 const declaration =
2689 /** @type {FunctionDeclaration | ClassDeclaration} */
2690 (statement.declaration);
2691 if (!this.hooks.exportDeclaration.call(statement, declaration)) {
2692 this.walkStatement(declaration);
2693 }
2694 } else {
2695 // Acorn parses `export default function() {}` as `FunctionDeclaration` and
2696 // `export default class {}` as `ClassDeclaration`, both with `id = null`.
2697 // These nodes must be treated as expressions.
2698 if (
2699 statement.declaration.type === "FunctionDeclaration" ||
2700 statement.declaration.type === "ClassDeclaration"
2701 ) {
2702 this.walkStatement(
2703 /** @type {FunctionDeclaration | ClassDeclaration} */
2704 (statement.declaration)
2705 );
2706 } else {
2707 this.walkExpression(statement.declaration);
2708 }
2709
2710 if (
2711 !this.hooks.exportExpression.call(
2712 statement,
2713 /** @type {TODO} */ (statement).declaration
2714 )
2715 ) {
2716 this.hooks.exportSpecifier.call(
2717 statement,
2718 /** @type {TODO} */ (statement.declaration),
2719 "default",
2720 undefined
2721 );
2722 }
2723 }
2724 }
2725
2726 /**
2727 * @param {ExportAllDeclaration} statement statement
2728 */
2729 blockPreWalkExportAllDeclaration(statement) {
2730 const source = /** @type {ImportSource} */ (statement.source.value);
2731 const name = statement.exported
2732 ? /** @type {Identifier} */
2733 (statement.exported).name ||
2734 /** @type {string} */
2735 (/** @type {Literal} */ (statement.exported).value)
2736 : null;
2737 this.hooks.exportImport.call(statement, source);
2738 this.hooks.exportImportSpecifier.call(statement, source, null, name, 0);
2739 }
2740
2741 /**
2742 * @param {VariableDeclaration} statement variable declaration
2743 */
2744 preWalkVariableDeclaration(statement) {
2745 if (statement.kind !== "var") return;
2746 this._preWalkVariableDeclaration(statement, this.hooks.varDeclarationVar);
2747 }
2748
2749 /**
2750 * @param {VariableDeclaration} statement variable declaration
2751 */
2752 blockPreWalkVariableDeclaration(statement) {
2753 if (statement.kind === "var") return;
2754 const hookMap =
2755 statement.kind === "const"
2756 ? this.hooks.varDeclarationConst
2757 : this.hooks.varDeclarationLet;
2758 this._preWalkVariableDeclaration(statement, hookMap);
2759 }
2760
2761 /**
2762 * @param {VariableDeclaration} statement variable declaration
2763 * @param {TODO} hookMap map of hooks
2764 */
2765 _preWalkVariableDeclaration(statement, hookMap) {
2766 for (const declarator of statement.declarations) {
2767 switch (declarator.type) {
2768 case "VariableDeclarator": {
2769 this.preWalkVariableDeclarator(declarator);
2770 if (!this.hooks.preDeclarator.call(declarator, statement)) {
2771 this.enterPattern(declarator.id, (name, decl) => {
2772 let hook = hookMap.get(name);
2773 if (hook === undefined || !hook.call(decl)) {
2774 hook = this.hooks.varDeclaration.get(name);
2775 if (hook === undefined || !hook.call(decl)) {
2776 this.defineVariable(name);
2777 }
2778 }
2779 });
2780 }
2781 break;
2782 }
2783 }
2784 }
2785 }
2786
2787 /**
2788 * @param {ObjectPattern} objectPattern object pattern
2789 * @returns {Set<DestructuringAssignmentProperty> | undefined} set of names or undefined if not all keys are identifiers
2790 */
2791 _preWalkObjectPattern(objectPattern) {
2792 /** @type {Set<DestructuringAssignmentProperty>} */
2793 const props = new Set();
2794 const properties = objectPattern.properties;
2795 for (let i = 0; i < properties.length; i++) {
2796 const property = properties[i];
2797 if (property.type !== "Property") return;
2798 if (property.shorthand && property.value.type === "Identifier") {
2799 this.scope.inShorthand = property.value.name;
2800 }
2801 const key = property.key;
2802 if (key.type === "Identifier") {
2803 props.add({
2804 id: key.name,
2805 range: key.range,
2806 shorthand: this.scope.inShorthand
2807 });
2808 } else {
2809 const id = this.evaluateExpression(key);
2810 const str = id.asString();
2811 if (str) {
2812 props.add({
2813 id: str,
2814 range: key.range,
2815 shorthand: this.scope.inShorthand
2816 });
2817 } else {
2818 // could not evaluate key
2819 return;
2820 }
2821 }
2822 this.scope.inShorthand = false;
2823 }
2824
2825 return props;
2826 }
2827
2828 /**
2829 * @param {VariableDeclarator} declarator variable declarator
2830 */
2831 preWalkVariableDeclarator(declarator) {
2832 if (
2833 !declarator.init ||
2834 declarator.id.type !== "ObjectPattern" ||
2835 !this.destructuringAssignmentProperties
2836 )
2837 return;
2838 const keys = this._preWalkObjectPattern(declarator.id);
2839
2840 if (!keys) return;
2841 this.destructuringAssignmentProperties.set(
2842 declarator.init.type === "AwaitExpression"
2843 ? declarator.init.argument
2844 : declarator.init,
2845 keys
2846 );
2847
2848 if (declarator.init.type === "AssignmentExpression") {
2849 this.preWalkAssignmentExpression(declarator.init);
2850 }
2851 }
2852
2853 /**
2854 * @param {VariableDeclaration} statement variable declaration
2855 */
2856 walkVariableDeclaration(statement) {
2857 for (const declarator of statement.declarations) {
2858 switch (declarator.type) {
2859 case "VariableDeclarator": {
2860 const renameIdentifier =
2861 declarator.init && this.getRenameIdentifier(declarator.init);
2862 if (renameIdentifier && declarator.id.type === "Identifier") {
2863 const hook = this.hooks.canRename.get(renameIdentifier);
2864 if (
2865 hook !== undefined &&
2866 hook.call(/** @type {Expression} */ (declarator.init))
2867 ) {
2868 // renaming with "var a = b;"
2869 const hook = this.hooks.rename.get(renameIdentifier);
2870 if (
2871 hook === undefined ||
2872 !hook.call(/** @type {Expression} */ (declarator.init))
2873 ) {
2874 this.setVariable(declarator.id.name, renameIdentifier);
2875 }
2876 break;
2877 }
2878 }
2879 if (!this.hooks.declarator.call(declarator, statement)) {
2880 this.walkPattern(declarator.id);
2881 if (declarator.init) this.walkExpression(declarator.init);
2882 }
2883 break;
2884 }
2885 }
2886 }
2887 }
2888
2889 /**
2890 * @param {ClassDeclaration} statement class declaration
2891 */
2892 blockPreWalkClassDeclaration(statement) {
2893 if (statement.id) {
2894 this.defineVariable(statement.id.name);
2895 }
2896 }
2897
2898 /**
2899 * @param {ClassDeclaration} statement class declaration
2900 */
2901 walkClassDeclaration(statement) {
2902 this.walkClass(statement);
2903 }
2904
2905 /**
2906 * @param {SwitchCase[]} switchCases switch statement
2907 */
2908 preWalkSwitchCases(switchCases) {
2909 for (let index = 0, len = switchCases.length; index < len; index++) {
2910 const switchCase = switchCases[index];
2911 this.preWalkStatements(switchCase.consequent);
2912 }
2913 }
2914
2915 /**
2916 * @param {SwitchCase[]} switchCases switch statement
2917 */
2918 walkSwitchCases(switchCases) {
2919 this.inBlockScope(() => {
2920 const len = switchCases.length;
2921
2922 // we need to pre walk all statements first since we can have invalid code
2923 // import A from "module";
2924 // switch(1) {
2925 // case 1:
2926 // console.log(A); // should fail at runtime
2927 // case 2:
2928 // const A = 1;
2929 // }
2930 for (let index = 0; index < len; index++) {
2931 const switchCase = switchCases[index];
2932
2933 if (switchCase.consequent.length > 0) {
2934 const prev = this.prevStatement;
2935 this.blockPreWalkStatements(switchCase.consequent);
2936 this.prevStatement = prev;
2937 }
2938 }
2939
2940 for (let index = 0; index < len; index++) {
2941 const switchCase = switchCases[index];
2942
2943 if (switchCase.test) {
2944 this.walkExpression(switchCase.test);
2945 }
2946 if (switchCase.consequent.length > 0) {
2947 this.walkStatements(switchCase.consequent);
2948 }
2949 }
2950 });
2951 }
2952
2953 /**
2954 * @param {CatchClause} catchClause catch clause
2955 */
2956 preWalkCatchClause(catchClause) {
2957 this.preWalkStatement(catchClause.body);
2958 }
2959
2960 /**
2961 * @param {CatchClause} catchClause catch clause
2962 */
2963 walkCatchClause(catchClause) {
2964 this.inBlockScope(() => {
2965 // Error binding is optional in catch clause since ECMAScript 2019
2966 if (catchClause.param !== null) {
2967 this.enterPattern(catchClause.param, ident => {
2968 this.defineVariable(ident);
2969 });
2970 this.walkPattern(catchClause.param);
2971 }
2972 const prev = this.prevStatement;
2973 this.blockPreWalkStatement(catchClause.body);
2974 this.prevStatement = prev;
2975 this.walkStatement(catchClause.body);
2976 });
2977 }
2978
2979 /**
2980 * @param {Pattern} pattern pattern
2981 */
2982 walkPattern(pattern) {
2983 switch (pattern.type) {
2984 case "ArrayPattern":
2985 this.walkArrayPattern(pattern);
2986 break;
2987 case "AssignmentPattern":
2988 this.walkAssignmentPattern(pattern);
2989 break;
2990 case "MemberExpression":
2991 this.walkMemberExpression(pattern);
2992 break;
2993 case "ObjectPattern":
2994 this.walkObjectPattern(pattern);
2995 break;
2996 case "RestElement":
2997 this.walkRestElement(pattern);
2998 break;
2999 }
3000 }
3001
3002 /**
3003 * @param {AssignmentPattern} pattern assignment pattern
3004 */
3005 walkAssignmentPattern(pattern) {
3006 this.walkExpression(pattern.right);
3007 this.walkPattern(pattern.left);
3008 }
3009
3010 /**
3011 * @param {ObjectPattern} pattern pattern
3012 */
3013 walkObjectPattern(pattern) {
3014 for (let i = 0, len = pattern.properties.length; i < len; i++) {
3015 const prop = pattern.properties[i];
3016 if (prop) {
3017 if (prop.type === "RestElement") {
3018 continue;
3019 }
3020 if (prop.computed) this.walkExpression(prop.key);
3021 if (prop.value) this.walkPattern(prop.value);
3022 }
3023 }
3024 }
3025
3026 /**
3027 * @param {ArrayPattern} pattern array pattern
3028 */
3029 walkArrayPattern(pattern) {
3030 for (let i = 0, len = pattern.elements.length; i < len; i++) {
3031 const element = pattern.elements[i];
3032 if (element) this.walkPattern(element);
3033 }
3034 }
3035
3036 /**
3037 * @param {RestElement} pattern rest element
3038 */
3039 walkRestElement(pattern) {
3040 this.walkPattern(pattern.argument);
3041 }
3042
3043 /**
3044 * @param {(Expression | SpreadElement | null)[]} expressions expressions
3045 */
3046 walkExpressions(expressions) {
3047 for (const expression of expressions) {
3048 if (expression) {
3049 this.walkExpression(expression);
3050 }
3051 }
3052 }
3053
3054 /**
3055 * @param {TODO} expression expression
3056 */
3057 walkExpression(expression) {
3058 switch (expression.type) {
3059 case "ArrayExpression":
3060 this.walkArrayExpression(expression);
3061 break;
3062 case "ArrowFunctionExpression":
3063 this.walkArrowFunctionExpression(expression);
3064 break;
3065 case "AssignmentExpression":
3066 this.walkAssignmentExpression(expression);
3067 break;
3068 case "AwaitExpression":
3069 this.walkAwaitExpression(expression);
3070 break;
3071 case "BinaryExpression":
3072 this.walkBinaryExpression(expression);
3073 break;
3074 case "CallExpression":
3075 this.walkCallExpression(expression);
3076 break;
3077 case "ChainExpression":
3078 this.walkChainExpression(expression);
3079 break;
3080 case "ClassExpression":
3081 this.walkClassExpression(expression);
3082 break;
3083 case "ConditionalExpression":
3084 this.walkConditionalExpression(expression);
3085 break;
3086 case "FunctionExpression":
3087 this.walkFunctionExpression(expression);
3088 break;
3089 case "Identifier":
3090 this.walkIdentifier(expression);
3091 break;
3092 case "ImportExpression":
3093 this.walkImportExpression(expression);
3094 break;
3095 case "LogicalExpression":
3096 this.walkLogicalExpression(expression);
3097 break;
3098 case "MetaProperty":
3099 this.walkMetaProperty(expression);
3100 break;
3101 case "MemberExpression":
3102 this.walkMemberExpression(expression);
3103 break;
3104 case "NewExpression":
3105 this.walkNewExpression(expression);
3106 break;
3107 case "ObjectExpression":
3108 this.walkObjectExpression(expression);
3109 break;
3110 case "SequenceExpression":
3111 this.walkSequenceExpression(expression);
3112 break;
3113 case "SpreadElement":
3114 this.walkSpreadElement(expression);
3115 break;
3116 case "TaggedTemplateExpression":
3117 this.walkTaggedTemplateExpression(expression);
3118 break;
3119 case "TemplateLiteral":
3120 this.walkTemplateLiteral(expression);
3121 break;
3122 case "ThisExpression":
3123 this.walkThisExpression(expression);
3124 break;
3125 case "UnaryExpression":
3126 this.walkUnaryExpression(expression);
3127 break;
3128 case "UpdateExpression":
3129 this.walkUpdateExpression(expression);
3130 break;
3131 case "YieldExpression":
3132 this.walkYieldExpression(expression);
3133 break;
3134 }
3135 }
3136
3137 /**
3138 * @param {AwaitExpression} expression await expression
3139 */
3140 walkAwaitExpression(expression) {
3141 if (this.scope.topLevelScope === true)
3142 this.hooks.topLevelAwait.call(expression);
3143 this.walkExpression(expression.argument);
3144 }
3145
3146 /**
3147 * @param {ArrayExpression} expression array expression
3148 */
3149 walkArrayExpression(expression) {
3150 if (expression.elements) {
3151 this.walkExpressions(expression.elements);
3152 }
3153 }
3154
3155 /**
3156 * @param {SpreadElement} expression spread element
3157 */
3158 walkSpreadElement(expression) {
3159 if (expression.argument) {
3160 this.walkExpression(expression.argument);
3161 }
3162 }
3163
3164 /**
3165 * @param {ObjectExpression} expression object expression
3166 */
3167 walkObjectExpression(expression) {
3168 for (
3169 let propIndex = 0, len = expression.properties.length;
3170 propIndex < len;
3171 propIndex++
3172 ) {
3173 const prop = expression.properties[propIndex];
3174 this.walkProperty(prop);
3175 }
3176 }
3177
3178 /**
3179 * @param {Property | SpreadElement} prop property or spread element
3180 */
3181 walkProperty(prop) {
3182 if (prop.type === "SpreadElement") {
3183 this.walkExpression(prop.argument);
3184 return;
3185 }
3186 if (prop.computed) {
3187 this.walkExpression(prop.key);
3188 }
3189 if (prop.shorthand && prop.value && prop.value.type === "Identifier") {
3190 this.scope.inShorthand = prop.value.name;
3191 this.walkIdentifier(prop.value);
3192 this.scope.inShorthand = false;
3193 } else {
3194 this.walkExpression(prop.value);
3195 }
3196 }
3197
3198 /**
3199 * @param {FunctionExpression} expression arrow function expression
3200 */
3201 walkFunctionExpression(expression) {
3202 const wasTopLevel = this.scope.topLevelScope;
3203 this.scope.topLevelScope = false;
3204 const scopeParams = [...expression.params];
3205
3206 // Add function name in scope for recursive calls
3207 if (expression.id) {
3208 scopeParams.push(expression.id);
3209 }
3210
3211 this.inFunctionScope(true, scopeParams, () => {
3212 for (const param of expression.params) {
3213 this.walkPattern(param);
3214 }
3215 if (expression.body.type === "BlockStatement") {
3216 this.detectMode(expression.body.body);
3217 const prev = this.prevStatement;
3218 this.preWalkStatement(expression.body);
3219 this.prevStatement = prev;
3220 this.walkStatement(expression.body);
3221 } else {
3222 this.walkExpression(expression.body);
3223 }
3224 });
3225 this.scope.topLevelScope = wasTopLevel;
3226 }
3227
3228 /**
3229 * @param {ArrowFunctionExpression} expression arrow function expression
3230 */
3231 walkArrowFunctionExpression(expression) {
3232 const wasTopLevel = this.scope.topLevelScope;
3233 this.scope.topLevelScope = wasTopLevel ? "arrow" : false;
3234 this.inFunctionScope(false, expression.params, () => {
3235 for (const param of expression.params) {
3236 this.walkPattern(param);
3237 }
3238 if (expression.body.type === "BlockStatement") {
3239 this.detectMode(expression.body.body);
3240 const prev = this.prevStatement;
3241 this.preWalkStatement(expression.body);
3242 this.prevStatement = prev;
3243 this.walkStatement(expression.body);
3244 } else {
3245 this.walkExpression(expression.body);
3246 }
3247 });
3248 this.scope.topLevelScope = wasTopLevel;
3249 }
3250
3251 /**
3252 * @param {SequenceExpression} expression the sequence
3253 */
3254 walkSequenceExpression(expression) {
3255 if (!expression.expressions) return;
3256 // We treat sequence expressions like statements when they are one statement level
3257 // This has some benefits for optimizations that only work on statement level
3258 const currentStatement =
3259 /** @type {StatementPath} */
3260 (this.statementPath)[
3261 /** @type {StatementPath} */
3262 (this.statementPath).length - 1
3263 ];
3264 if (
3265 currentStatement === expression ||
3266 (currentStatement.type === "ExpressionStatement" &&
3267 currentStatement.expression === expression)
3268 ) {
3269 const old =
3270 /** @type {StatementPathItem} */
3271 (/** @type {StatementPath} */ (this.statementPath).pop());
3272 const prev = this.prevStatement;
3273 for (const expr of expression.expressions) {
3274 /** @type {StatementPath} */
3275 (this.statementPath).push(expr);
3276 this.walkExpression(expr);
3277 this.prevStatement =
3278 /** @type {StatementPath} */
3279 (this.statementPath).pop();
3280 }
3281 this.prevStatement = prev;
3282 /** @type {StatementPath} */
3283 (this.statementPath).push(old);
3284 } else {
3285 this.walkExpressions(expression.expressions);
3286 }
3287 }
3288
3289 /**
3290 * @param {UpdateExpression} expression the update expression
3291 */
3292 walkUpdateExpression(expression) {
3293 this.walkExpression(expression.argument);
3294 }
3295
3296 /**
3297 * @param {UnaryExpression} expression the unary expression
3298 */
3299 walkUnaryExpression(expression) {
3300 if (expression.operator === "typeof") {
3301 const result = this.callHooksForExpression(
3302 this.hooks.typeof,
3303 expression.argument,
3304 expression
3305 );
3306 if (result === true) return;
3307 if (expression.argument.type === "ChainExpression") {
3308 const result = this.callHooksForExpression(
3309 this.hooks.typeof,
3310 expression.argument.expression,
3311 expression
3312 );
3313 if (result === true) return;
3314 }
3315 }
3316 this.walkExpression(expression.argument);
3317 }
3318
3319 /**
3320 * @param {LogicalExpression | BinaryExpression} expression the expression
3321 */
3322 walkLeftRightExpression(expression) {
3323 this.walkExpression(expression.left);
3324 this.walkExpression(expression.right);
3325 }
3326
3327 /**
3328 * @param {BinaryExpression} expression the binary expression
3329 */
3330 walkBinaryExpression(expression) {
3331 if (this.hooks.binaryExpression.call(expression) === undefined) {
3332 this.walkLeftRightExpression(expression);
3333 }
3334 }
3335
3336 /**
3337 * @param {LogicalExpression} expression the logical expression
3338 */
3339 walkLogicalExpression(expression) {
3340 const result = this.hooks.expressionLogicalOperator.call(expression);
3341 if (result === undefined) {
3342 this.walkLeftRightExpression(expression);
3343 } else if (result) {
3344 this.walkExpression(expression.right);
3345 }
3346 }
3347
3348 /**
3349 * @param {AssignmentExpression} expression assignment expression
3350 */
3351 walkAssignmentExpression(expression) {
3352 if (expression.left.type === "Identifier") {
3353 const renameIdentifier = this.getRenameIdentifier(expression.right);
3354 if (
3355 renameIdentifier &&
3356 this.callHooksForInfo(
3357 this.hooks.canRename,
3358 renameIdentifier,
3359 expression.right
3360 )
3361 ) {
3362 // renaming "a = b;"
3363 if (
3364 !this.callHooksForInfo(
3365 this.hooks.rename,
3366 renameIdentifier,
3367 expression.right
3368 )
3369 ) {
3370 this.setVariable(
3371 expression.left.name,
3372 typeof renameIdentifier === "string"
3373 ? this.getVariableInfo(renameIdentifier)
3374 : renameIdentifier
3375 );
3376 }
3377 return;
3378 }
3379 this.walkExpression(expression.right);
3380 this.enterPattern(expression.left, (name, decl) => {
3381 if (!this.callHooksForName(this.hooks.assign, name, expression)) {
3382 this.walkExpression(expression.left);
3383 }
3384 });
3385 return;
3386 }
3387 if (expression.left.type.endsWith("Pattern")) {
3388 this.walkExpression(expression.right);
3389 this.enterPattern(expression.left, (name, decl) => {
3390 if (!this.callHooksForName(this.hooks.assign, name, expression)) {
3391 this.defineVariable(name);
3392 }
3393 });
3394 this.walkPattern(expression.left);
3395 } else if (expression.left.type === "MemberExpression") {
3396 const exprName = this.getMemberExpressionInfo(
3397 expression.left,
3398 ALLOWED_MEMBER_TYPES_EXPRESSION
3399 );
3400 if (
3401 exprName &&
3402 this.callHooksForInfo(
3403 this.hooks.assignMemberChain,
3404 exprName.rootInfo,
3405 expression,
3406 exprName.getMembers()
3407 )
3408 ) {
3409 return;
3410 }
3411 this.walkExpression(expression.right);
3412 this.walkExpression(expression.left);
3413 } else {
3414 this.walkExpression(expression.right);
3415 this.walkExpression(expression.left);
3416 }
3417 }
3418
3419 /**
3420 * @param {ConditionalExpression} expression conditional expression
3421 */
3422 walkConditionalExpression(expression) {
3423 const result = this.hooks.expressionConditionalOperator.call(expression);
3424 if (result === undefined) {
3425 this.walkExpression(expression.test);
3426 this.walkExpression(expression.consequent);
3427 if (expression.alternate) {
3428 this.walkExpression(expression.alternate);
3429 }
3430 } else if (result) {
3431 this.walkExpression(expression.consequent);
3432 } else if (expression.alternate) {
3433 this.walkExpression(expression.alternate);
3434 }
3435 }
3436
3437 /**
3438 * @param {NewExpression} expression new expression
3439 */
3440 walkNewExpression(expression) {
3441 const result = this.callHooksForExpression(
3442 this.hooks.new,
3443 expression.callee,
3444 expression
3445 );
3446 if (result === true) return;
3447 this.walkExpression(expression.callee);
3448 if (expression.arguments) {
3449 this.walkExpressions(expression.arguments);
3450 }
3451 }
3452
3453 /**
3454 * @param {YieldExpression} expression yield expression
3455 */
3456 walkYieldExpression(expression) {
3457 if (expression.argument) {
3458 this.walkExpression(expression.argument);
3459 }
3460 }
3461
3462 /**
3463 * @param {TemplateLiteral} expression template literal
3464 */
3465 walkTemplateLiteral(expression) {
3466 if (expression.expressions) {
3467 this.walkExpressions(expression.expressions);
3468 }
3469 }
3470
3471 /**
3472 * @param {TaggedTemplateExpression} expression tagged template expression
3473 */
3474 walkTaggedTemplateExpression(expression) {
3475 if (expression.tag) {
3476 this.scope.inTaggedTemplateTag = true;
3477 this.walkExpression(expression.tag);
3478 this.scope.inTaggedTemplateTag = false;
3479 }
3480 if (expression.quasi && expression.quasi.expressions) {
3481 this.walkExpressions(expression.quasi.expressions);
3482 }
3483 }
3484
3485 /**
3486 * @param {ClassExpression} expression the class expression
3487 */
3488 walkClassExpression(expression) {
3489 this.walkClass(expression);
3490 }
3491
3492 /**
3493 * @param {ChainExpression} expression expression
3494 */
3495 walkChainExpression(expression) {
3496 const result = this.hooks.optionalChaining.call(expression);
3497
3498 if (result === undefined) {
3499 if (expression.expression.type === "CallExpression") {
3500 this.walkCallExpression(expression.expression);
3501 } else {
3502 this.walkMemberExpression(expression.expression);
3503 }
3504 }
3505 }
3506
3507 /**
3508 * @private
3509 * @param {FunctionExpression | ArrowFunctionExpression} functionExpression function expression
3510 * @param {(Expression | SpreadElement)[]} options options
3511 * @param {Expression | SpreadElement | null} currentThis current this
3512 */
3513 _walkIIFE(functionExpression, options, currentThis) {
3514 /**
3515 * @param {Expression | SpreadElement} argOrThis arg or this
3516 * @returns {string | VariableInfoInterface | undefined} var info
3517 */
3518 const getVarInfo = argOrThis => {
3519 const renameIdentifier = this.getRenameIdentifier(argOrThis);
3520 if (
3521 renameIdentifier &&
3522 this.callHooksForInfo(
3523 this.hooks.canRename,
3524 renameIdentifier,
3525 /** @type {Expression} */
3526 (argOrThis)
3527 ) &&
3528 !this.callHooksForInfo(
3529 this.hooks.rename,
3530 renameIdentifier,
3531 /** @type {Expression} */
3532 (argOrThis)
3533 )
3534 ) {
3535 return typeof renameIdentifier === "string"
3536 ? /** @type {string} */ (this.getVariableInfo(renameIdentifier))
3537 : renameIdentifier;
3538 }
3539 this.walkExpression(argOrThis);
3540 };
3541 const { params, type } = functionExpression;
3542 const arrow = type === "ArrowFunctionExpression";
3543 const renameThis = currentThis ? getVarInfo(currentThis) : null;
3544 const varInfoForArgs = options.map(getVarInfo);
3545 const wasTopLevel = this.scope.topLevelScope;
3546 this.scope.topLevelScope = wasTopLevel && arrow ? "arrow" : false;
3547 const scopeParams =
3548 /** @type {(Identifier | string)[]} */
3549 (params.filter((identifier, idx) => !varInfoForArgs[idx]));
3550
3551 // Add function name in scope for recursive calls
3552 if (
3553 functionExpression.type === "FunctionExpression" &&
3554 functionExpression.id
3555 ) {
3556 scopeParams.push(functionExpression.id.name);
3557 }
3558
3559 this.inFunctionScope(true, scopeParams, () => {
3560 if (renameThis && !arrow) {
3561 this.setVariable("this", renameThis);
3562 }
3563 for (let i = 0; i < varInfoForArgs.length; i++) {
3564 const varInfo = varInfoForArgs[i];
3565 if (!varInfo) continue;
3566 if (!params[i] || params[i].type !== "Identifier") continue;
3567 this.setVariable(/** @type {Identifier} */ (params[i]).name, varInfo);
3568 }
3569 if (functionExpression.body.type === "BlockStatement") {
3570 this.detectMode(functionExpression.body.body);
3571 const prev = this.prevStatement;
3572 this.preWalkStatement(functionExpression.body);
3573 this.prevStatement = prev;
3574 this.walkStatement(functionExpression.body);
3575 } else {
3576 this.walkExpression(functionExpression.body);
3577 }
3578 });
3579 this.scope.topLevelScope = wasTopLevel;
3580 }
3581
3582 /**
3583 * @param {ImportExpression} expression import expression
3584 */
3585 walkImportExpression(expression) {
3586 const result = this.hooks.importCall.call(expression);
3587 if (result === true) return;
3588
3589 this.walkExpression(expression.source);
3590 }
3591
3592 /**
3593 * @param {CallExpression} expression expression
3594 */
3595 walkCallExpression(expression) {
3596 /**
3597 * @param {FunctionExpression | ArrowFunctionExpression} fn function
3598 * @returns {boolean} true when simple function
3599 */
3600 const isSimpleFunction = fn =>
3601 fn.params.every(p => p.type === "Identifier");
3602 if (
3603 expression.callee.type === "MemberExpression" &&
3604 expression.callee.object.type.endsWith("FunctionExpression") &&
3605 !expression.callee.computed &&
3606 // eslint-disable-next-line no-warning-comments
3607 // @ts-ignore
3608 // TODO check me and handle more cases
3609 (expression.callee.property.name === "call" ||
3610 // eslint-disable-next-line no-warning-comments
3611 // @ts-ignore
3612 expression.callee.property.name === "bind") &&
3613 expression.arguments.length > 0 &&
3614 isSimpleFunction(
3615 /** @type {FunctionExpression | ArrowFunctionExpression} */
3616 (expression.callee.object)
3617 )
3618 ) {
3619 // (function(…) { }.call/bind(?, …))
3620 this._walkIIFE(
3621 /** @type {FunctionExpression | ArrowFunctionExpression} */
3622 (expression.callee.object),
3623 expression.arguments.slice(1),
3624 expression.arguments[0]
3625 );
3626 } else if (
3627 expression.callee.type.endsWith("FunctionExpression") &&
3628 isSimpleFunction(
3629 /** @type {FunctionExpression | ArrowFunctionExpression} */
3630 (expression.callee)
3631 )
3632 ) {
3633 // (function(…) { }(…))
3634 this._walkIIFE(
3635 /** @type {FunctionExpression | ArrowFunctionExpression} */
3636 (expression.callee),
3637 expression.arguments,
3638 null
3639 );
3640 } else {
3641 if (expression.callee.type === "MemberExpression") {
3642 const exprInfo = this.getMemberExpressionInfo(
3643 expression.callee,
3644 ALLOWED_MEMBER_TYPES_CALL_EXPRESSION
3645 );
3646 if (exprInfo && exprInfo.type === "call") {
3647 const result = this.callHooksForInfo(
3648 this.hooks.callMemberChainOfCallMemberChain,
3649 exprInfo.rootInfo,
3650 expression,
3651 exprInfo.getCalleeMembers(),
3652 exprInfo.call,
3653 exprInfo.getMembers(),
3654 exprInfo.getMemberRanges()
3655 );
3656 if (result === true) return;
3657 }
3658 }
3659 const callee = this.evaluateExpression(
3660 /** @type {TODO} */ (expression.callee)
3661 );
3662 if (callee.isIdentifier()) {
3663 const result1 = this.callHooksForInfo(
3664 this.hooks.callMemberChain,
3665 /** @type {NonNullable<BasicEvaluatedExpression["rootInfo"]>} */
3666 (callee.rootInfo),
3667 expression,
3668 /** @type {NonNullable<BasicEvaluatedExpression["getMembers"]>} */
3669 (callee.getMembers)(),
3670 callee.getMembersOptionals
3671 ? callee.getMembersOptionals()
3672 : /** @type {NonNullable<BasicEvaluatedExpression["getMembers"]>} */
3673 (callee.getMembers)().map(() => false),
3674 callee.getMemberRanges ? callee.getMemberRanges() : []
3675 );
3676 if (result1 === true) return;
3677 const result2 = this.callHooksForInfo(
3678 this.hooks.call,
3679 /** @type {NonNullable<BasicEvaluatedExpression["identifier"]>} */
3680 (callee.identifier),
3681 expression
3682 );
3683 if (result2 === true) return;
3684 }
3685
3686 if (expression.callee) {
3687 if (expression.callee.type === "MemberExpression") {
3688 // because of call context we need to walk the call context as expression
3689 this.walkExpression(expression.callee.object);
3690 if (expression.callee.computed === true)
3691 this.walkExpression(expression.callee.property);
3692 } else {
3693 this.walkExpression(expression.callee);
3694 }
3695 }
3696 if (expression.arguments) this.walkExpressions(expression.arguments);
3697 }
3698 }
3699
3700 /**
3701 * @param {MemberExpression} expression member expression
3702 */
3703 walkMemberExpression(expression) {
3704 const exprInfo = this.getMemberExpressionInfo(
3705 expression,
3706 ALLOWED_MEMBER_TYPES_ALL
3707 );
3708 if (exprInfo) {
3709 switch (exprInfo.type) {
3710 case "expression": {
3711 const result1 = this.callHooksForInfo(
3712 this.hooks.expression,
3713 exprInfo.name,
3714 expression
3715 );
3716 if (result1 === true) return;
3717 const members = exprInfo.getMembers();
3718 const membersOptionals = exprInfo.getMembersOptionals();
3719 const memberRanges = exprInfo.getMemberRanges();
3720 const result2 = this.callHooksForInfo(
3721 this.hooks.expressionMemberChain,
3722 exprInfo.rootInfo,
3723 expression,
3724 members,
3725 membersOptionals,
3726 memberRanges
3727 );
3728 if (result2 === true) return;
3729 this.walkMemberExpressionWithExpressionName(
3730 expression,
3731 exprInfo.name,
3732 exprInfo.rootInfo,
3733 members.slice(),
3734 () =>
3735 this.callHooksForInfo(
3736 this.hooks.unhandledExpressionMemberChain,
3737 exprInfo.rootInfo,
3738 expression,
3739 members
3740 )
3741 );
3742 return;
3743 }
3744 case "call": {
3745 const result = this.callHooksForInfo(
3746 this.hooks.memberChainOfCallMemberChain,
3747 exprInfo.rootInfo,
3748 expression,
3749 exprInfo.getCalleeMembers(),
3750 exprInfo.call,
3751 exprInfo.getMembers(),
3752 exprInfo.getMemberRanges()
3753 );
3754 if (result === true) return;
3755 // Fast skip over the member chain as we already called memberChainOfCallMemberChain
3756 // and call computed property are literals anyway
3757 this.walkExpression(exprInfo.call);
3758 return;
3759 }
3760 }
3761 }
3762 this.walkExpression(expression.object);
3763 if (expression.computed === true) this.walkExpression(expression.property);
3764 }
3765
3766 /**
3767 * @param {TODO} expression member expression
3768 * @param {string} name name
3769 * @param {string | VariableInfo} rootInfo root info
3770 * @param {string[]} members members
3771 * @param {TODO} onUnhandled on unhandled callback
3772 */
3773 walkMemberExpressionWithExpressionName(
3774 expression,
3775 name,
3776 rootInfo,
3777 members,
3778 onUnhandled
3779 ) {
3780 if (expression.object.type === "MemberExpression") {
3781 // optimize the case where expression.object is a MemberExpression too.
3782 // we can keep info here when calling walkMemberExpression directly
3783 const property =
3784 expression.property.name || `${expression.property.value}`;
3785 name = name.slice(0, -property.length - 1);
3786 members.pop();
3787 const result = this.callHooksForInfo(
3788 this.hooks.expression,
3789 name,
3790 expression.object
3791 );
3792 if (result === true) return;
3793 this.walkMemberExpressionWithExpressionName(
3794 expression.object,
3795 name,
3796 rootInfo,
3797 members,
3798 onUnhandled
3799 );
3800 } else if (!onUnhandled || !onUnhandled()) {
3801 this.walkExpression(expression.object);
3802 }
3803 if (expression.computed === true) this.walkExpression(expression.property);
3804 }
3805
3806 /**
3807 * @param {ThisExpression} expression this expression
3808 */
3809 walkThisExpression(expression) {
3810 this.callHooksForName(this.hooks.expression, "this", expression);
3811 }
3812
3813 /**
3814 * @param {Identifier} expression identifier
3815 */
3816 walkIdentifier(expression) {
3817 this.callHooksForName(this.hooks.expression, expression.name, expression);
3818 }
3819
3820 /**
3821 * @param {MetaProperty} metaProperty meta property
3822 */
3823 walkMetaProperty(metaProperty) {
3824 this.hooks.expression.for(getRootName(metaProperty)).call(metaProperty);
3825 }
3826
3827 /**
3828 * @template T
3829 * @template R
3830 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
3831 * @param {Expression | Super} expr expression
3832 * @param {AsArray<T>} args args for the hook
3833 * @returns {R | undefined} result of hook
3834 */
3835 callHooksForExpression(hookMap, expr, ...args) {
3836 return this.callHooksForExpressionWithFallback(
3837 hookMap,
3838 expr,
3839 undefined,
3840 undefined,
3841 ...args
3842 );
3843 }
3844
3845 /**
3846 * @template T
3847 * @template R
3848 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
3849 * @param {Expression | Super} expr expression info
3850 * @param {(function(string, string | ScopeInfo | VariableInfo, function(): string[]): any) | undefined} fallback callback when variable in not handled by hooks
3851 * @param {(function(string): any) | undefined} defined callback when variable is defined
3852 * @param {AsArray<T>} args args for the hook
3853 * @returns {R | undefined} result of hook
3854 */
3855 callHooksForExpressionWithFallback(
3856 hookMap,
3857 expr,
3858 fallback,
3859 defined,
3860 ...args
3861 ) {
3862 const exprName = this.getMemberExpressionInfo(
3863 expr,
3864 ALLOWED_MEMBER_TYPES_EXPRESSION
3865 );
3866 if (exprName !== undefined) {
3867 const members = exprName.getMembers();
3868 return this.callHooksForInfoWithFallback(
3869 hookMap,
3870 members.length === 0 ? exprName.rootInfo : exprName.name,
3871 fallback &&
3872 (name => fallback(name, exprName.rootInfo, exprName.getMembers)),
3873 defined && (() => defined(exprName.name)),
3874 ...args
3875 );
3876 }
3877 }
3878
3879 /**
3880 * @template T
3881 * @template R
3882 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
3883 * @param {string} name key in map
3884 * @param {AsArray<T>} args args for the hook
3885 * @returns {R | undefined} result of hook
3886 */
3887 callHooksForName(hookMap, name, ...args) {
3888 return this.callHooksForNameWithFallback(
3889 hookMap,
3890 name,
3891 undefined,
3892 undefined,
3893 ...args
3894 );
3895 }
3896
3897 /**
3898 * @template T
3899 * @template R
3900 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks that should be called
3901 * @param {ExportedVariableInfo} info variable info
3902 * @param {AsArray<T>} args args for the hook
3903 * @returns {R | undefined} result of hook
3904 */
3905 callHooksForInfo(hookMap, info, ...args) {
3906 return this.callHooksForInfoWithFallback(
3907 hookMap,
3908 info,
3909 undefined,
3910 undefined,
3911 ...args
3912 );
3913 }
3914
3915 /**
3916 * @template T
3917 * @template R
3918 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
3919 * @param {ExportedVariableInfo} info variable info
3920 * @param {(function(string): any) | undefined} fallback callback when variable in not handled by hooks
3921 * @param {(function(string=): any) | undefined} defined callback when variable is defined
3922 * @param {AsArray<T>} args args for the hook
3923 * @returns {R | undefined} result of hook
3924 */
3925 callHooksForInfoWithFallback(hookMap, info, fallback, defined, ...args) {
3926 let name;
3927 if (typeof info === "string") {
3928 name = info;
3929 } else {
3930 if (!(info instanceof VariableInfo)) {
3931 if (defined !== undefined) {
3932 return defined();
3933 }
3934 return;
3935 }
3936 let tagInfo = info.tagInfo;
3937 while (tagInfo !== undefined) {
3938 const hook = hookMap.get(tagInfo.tag);
3939 if (hook !== undefined) {
3940 this.currentTagData = tagInfo.data;
3941 const result = hook.call(...args);
3942 this.currentTagData = undefined;
3943 if (result !== undefined) return result;
3944 }
3945 tagInfo = tagInfo.next;
3946 }
3947 if (info.freeName === true) {
3948 if (defined !== undefined) {
3949 return defined();
3950 }
3951 return;
3952 }
3953 name = info.freeName;
3954 }
3955 const hook = hookMap.get(name);
3956 if (hook !== undefined) {
3957 const result = hook.call(...args);
3958 if (result !== undefined) return result;
3959 }
3960 if (fallback !== undefined) {
3961 return fallback(/** @type {string} */ (name));
3962 }
3963 }
3964
3965 /**
3966 * @template T
3967 * @template R
3968 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
3969 * @param {string} name key in map
3970 * @param {(function(string): any) | undefined} fallback callback when variable in not handled by hooks
3971 * @param {(function(): any) | undefined} defined callback when variable is defined
3972 * @param {AsArray<T>} args args for the hook
3973 * @returns {R | undefined} result of hook
3974 */
3975 callHooksForNameWithFallback(hookMap, name, fallback, defined, ...args) {
3976 return this.callHooksForInfoWithFallback(
3977 hookMap,
3978 this.getVariableInfo(name),
3979 fallback,
3980 defined,
3981 ...args
3982 );
3983 }
3984
3985 /**
3986 * @deprecated
3987 * @param {any} params scope params
3988 * @param {function(): void} fn inner function
3989 * @returns {void}
3990 */
3991 inScope(params, fn) {
3992 const oldScope = this.scope;
3993 this.scope = {
3994 topLevelScope: oldScope.topLevelScope,
3995 inTry: false,
3996 inShorthand: false,
3997 inTaggedTemplateTag: false,
3998 isStrict: oldScope.isStrict,
3999 isAsmJs: oldScope.isAsmJs,
4000 definitions: oldScope.definitions.createChild()
4001 };
4002
4003 this.undefineVariable("this");
4004
4005 this.enterPatterns(params, ident => {
4006 this.defineVariable(ident);
4007 });
4008
4009 fn();
4010
4011 this.scope = oldScope;
4012 }
4013
4014 /**
4015 * @param {boolean} hasThis true, when this is defined
4016 * @param {Identifier[]} params scope params
4017 * @param {function(): void} fn inner function
4018 * @returns {void}
4019 */
4020 inClassScope(hasThis, params, fn) {
4021 const oldScope = this.scope;
4022 this.scope = {
4023 topLevelScope: oldScope.topLevelScope,
4024 inTry: false,
4025 inShorthand: false,
4026 inTaggedTemplateTag: false,
4027 isStrict: oldScope.isStrict,
4028 isAsmJs: oldScope.isAsmJs,
4029 definitions: oldScope.definitions.createChild()
4030 };
4031
4032 if (hasThis) {
4033 this.undefineVariable("this");
4034 }
4035
4036 this.enterPatterns(params, ident => {
4037 this.defineVariable(ident);
4038 });
4039
4040 fn();
4041
4042 this.scope = oldScope;
4043 }
4044
4045 /**
4046 * @param {boolean} hasThis true, when this is defined
4047 * @param {(Pattern | string)[]} params scope params
4048 * @param {function(): void} fn inner function
4049 * @returns {void}
4050 */
4051 inFunctionScope(hasThis, params, fn) {
4052 const oldScope = this.scope;
4053 this.scope = {
4054 topLevelScope: oldScope.topLevelScope,
4055 inTry: false,
4056 inShorthand: false,
4057 inTaggedTemplateTag: false,
4058 isStrict: oldScope.isStrict,
4059 isAsmJs: oldScope.isAsmJs,
4060 definitions: oldScope.definitions.createChild()
4061 };
4062
4063 if (hasThis) {
4064 this.undefineVariable("this");
4065 }
4066
4067 this.enterPatterns(params, ident => {
4068 this.defineVariable(ident);
4069 });
4070
4071 fn();
4072
4073 this.scope = oldScope;
4074 }
4075
4076 /**
4077 * @param {function(): void} fn inner function
4078 * @returns {void}
4079 */
4080 inBlockScope(fn) {
4081 const oldScope = this.scope;
4082 this.scope = {
4083 topLevelScope: oldScope.topLevelScope,
4084 inTry: oldScope.inTry,
4085 inShorthand: false,
4086 inTaggedTemplateTag: false,
4087 isStrict: oldScope.isStrict,
4088 isAsmJs: oldScope.isAsmJs,
4089 definitions: oldScope.definitions.createChild()
4090 };
4091
4092 fn();
4093
4094 this.scope = oldScope;
4095 }
4096
4097 /**
4098 * @param {Array<Directive | Statement | ModuleDeclaration>} statements statements
4099 */
4100 detectMode(statements) {
4101 const isLiteral =
4102 statements.length >= 1 &&
4103 statements[0].type === "ExpressionStatement" &&
4104 statements[0].expression.type === "Literal";
4105 if (
4106 isLiteral &&
4107 /** @type {Literal} */
4108 (/** @type {ExpressionStatement} */ (statements[0]).expression).value ===
4109 "use strict"
4110 ) {
4111 this.scope.isStrict = true;
4112 }
4113 if (
4114 isLiteral &&
4115 /** @type {Literal} */
4116 (/** @type {ExpressionStatement} */ (statements[0]).expression).value ===
4117 "use asm"
4118 ) {
4119 this.scope.isAsmJs = true;
4120 }
4121 }
4122
4123 /**
4124 * @param {(string | Pattern | Property)[]} patterns patterns
4125 * @param {OnIdentString} onIdent on ident callback
4126 */
4127 enterPatterns(patterns, onIdent) {
4128 for (const pattern of patterns) {
4129 if (typeof pattern !== "string") {
4130 this.enterPattern(pattern, onIdent);
4131 } else if (pattern) {
4132 onIdent(pattern);
4133 }
4134 }
4135 }
4136
4137 /**
4138 * @param {Pattern | Property} pattern pattern
4139 * @param {OnIdent} onIdent on ident callback
4140 */
4141 enterPattern(pattern, onIdent) {
4142 if (!pattern) return;
4143 switch (pattern.type) {
4144 case "ArrayPattern":
4145 this.enterArrayPattern(pattern, onIdent);
4146 break;
4147 case "AssignmentPattern":
4148 this.enterAssignmentPattern(pattern, onIdent);
4149 break;
4150 case "Identifier":
4151 this.enterIdentifier(pattern, onIdent);
4152 break;
4153 case "ObjectPattern":
4154 this.enterObjectPattern(pattern, onIdent);
4155 break;
4156 case "RestElement":
4157 this.enterRestElement(pattern, onIdent);
4158 break;
4159 case "Property":
4160 if (pattern.shorthand && pattern.value.type === "Identifier") {
4161 this.scope.inShorthand = pattern.value.name;
4162 this.enterIdentifier(pattern.value, onIdent);
4163 this.scope.inShorthand = false;
4164 } else {
4165 this.enterPattern(/** @type {Pattern} */ (pattern.value), onIdent);
4166 }
4167 break;
4168 }
4169 }
4170
4171 /**
4172 * @param {Identifier} pattern identifier pattern
4173 * @param {OnIdent} onIdent callback
4174 */
4175 enterIdentifier(pattern, onIdent) {
4176 if (!this.callHooksForName(this.hooks.pattern, pattern.name, pattern)) {
4177 onIdent(pattern.name, pattern);
4178 }
4179 }
4180
4181 /**
4182 * @param {ObjectPattern} pattern object pattern
4183 * @param {OnIdent} onIdent callback
4184 */
4185 enterObjectPattern(pattern, onIdent) {
4186 for (
4187 let propIndex = 0, len = pattern.properties.length;
4188 propIndex < len;
4189 propIndex++
4190 ) {
4191 const prop = pattern.properties[propIndex];
4192 this.enterPattern(prop, onIdent);
4193 }
4194 }
4195
4196 /**
4197 * @param {ArrayPattern} pattern object pattern
4198 * @param {OnIdent} onIdent callback
4199 */
4200 enterArrayPattern(pattern, onIdent) {
4201 for (
4202 let elementIndex = 0, len = pattern.elements.length;
4203 elementIndex < len;
4204 elementIndex++
4205 ) {
4206 const element = pattern.elements[elementIndex];
4207
4208 if (element) {
4209 this.enterPattern(element, onIdent);
4210 }
4211 }
4212 }
4213
4214 /**
4215 * @param {RestElement} pattern object pattern
4216 * @param {OnIdent} onIdent callback
4217 */
4218 enterRestElement(pattern, onIdent) {
4219 this.enterPattern(pattern.argument, onIdent);
4220 }
4221
4222 /**
4223 * @param {AssignmentPattern} pattern object pattern
4224 * @param {OnIdent} onIdent callback
4225 */
4226 enterAssignmentPattern(pattern, onIdent) {
4227 this.enterPattern(pattern.left, onIdent);
4228 }
4229
4230 /**
4231 * @param {Expression | SpreadElement | PrivateIdentifier} expression expression node
4232 * @returns {BasicEvaluatedExpression} evaluation result
4233 */
4234 evaluateExpression(expression) {
4235 try {
4236 const hook = this.hooks.evaluate.get(expression.type);
4237 if (hook !== undefined) {
4238 const result = hook.call(expression);
4239 if (result !== undefined && result !== null) {
4240 result.setExpression(expression);
4241 return result;
4242 }
4243 }
4244 } catch (err) {
4245 console.warn(err);
4246 // ignore error
4247 }
4248 return new BasicEvaluatedExpression()
4249 .setRange(/** @type {Range} */ (expression.range))
4250 .setExpression(expression);
4251 }
4252
4253 /**
4254 * @param {Expression} expression expression
4255 * @returns {string} parsed string
4256 */
4257 parseString(expression) {
4258 switch (expression.type) {
4259 case "BinaryExpression":
4260 if (expression.operator === "+") {
4261 return (
4262 this.parseString(/** @type {Expression} */ (expression.left)) +
4263 this.parseString(expression.right)
4264 );
4265 }
4266 break;
4267 case "Literal":
4268 return String(expression.value);
4269 }
4270 throw new Error(
4271 `${expression.type} is not supported as parameter for require`
4272 );
4273 }
4274
4275 /**
4276 * @param {Expression} expression expression
4277 * @returns {{ range?: Range, value: string, code: boolean, conditional: TODO }} result
4278 */
4279 parseCalculatedString(expression) {
4280 switch (expression.type) {
4281 case "BinaryExpression":
4282 if (expression.operator === "+") {
4283 const left = this.parseCalculatedString(
4284 /** @type {Expression} */
4285 (expression.left)
4286 );
4287 const right = this.parseCalculatedString(expression.right);
4288 if (left.code) {
4289 return {
4290 range: left.range,
4291 value: left.value,
4292 code: true,
4293 conditional: false
4294 };
4295 } else if (right.code) {
4296 return {
4297 range: [
4298 /** @type {Range} */
4299 (left.range)[0],
4300 right.range
4301 ? right.range[1]
4302 : /** @type {Range} */ (left.range)[1]
4303 ],
4304 value: left.value + right.value,
4305 code: true,
4306 conditional: false
4307 };
4308 }
4309 return {
4310 range: [
4311 /** @type {Range} */
4312 (left.range)[0],
4313 /** @type {Range} */
4314 (right.range)[1]
4315 ],
4316 value: left.value + right.value,
4317 code: false,
4318 conditional: false
4319 };
4320 }
4321 break;
4322 case "ConditionalExpression": {
4323 const consequent = this.parseCalculatedString(expression.consequent);
4324 const alternate = this.parseCalculatedString(expression.alternate);
4325 const items = [];
4326 if (consequent.conditional) {
4327 items.push(...consequent.conditional);
4328 } else if (!consequent.code) {
4329 items.push(consequent);
4330 } else {
4331 break;
4332 }
4333 if (alternate.conditional) {
4334 items.push(...alternate.conditional);
4335 } else if (!alternate.code) {
4336 items.push(alternate);
4337 } else {
4338 break;
4339 }
4340 return {
4341 range: undefined,
4342 value: "",
4343 code: true,
4344 conditional: items
4345 };
4346 }
4347 case "Literal":
4348 return {
4349 range: expression.range,
4350 value: String(expression.value),
4351 code: false,
4352 conditional: false
4353 };
4354 }
4355 return {
4356 range: undefined,
4357 value: "",
4358 code: true,
4359 conditional: false
4360 };
4361 }
4362
4363 /**
4364 * @param {string | Buffer | PreparsedAst} source the source to parse
4365 * @param {ParserState} state the parser state
4366 * @returns {ParserState} the parser state
4367 */
4368 parse(source, state) {
4369 let ast;
4370 /** @type {import("acorn").Comment[]} */
4371 let comments;
4372 const semicolons = new Set();
4373 if (source === null) {
4374 throw new Error("source must not be null");
4375 }
4376 if (Buffer.isBuffer(source)) {
4377 source = source.toString("utf-8");
4378 }
4379 if (typeof source === "object") {
4380 ast = /** @type {Program} */ (source);
4381 comments = source.comments;
4382 } else {
4383 comments = [];
4384 ast = JavascriptParser._parse(source, {
4385 sourceType: this.sourceType,
4386 onComment: comments,
4387 onInsertedSemicolon: pos => semicolons.add(pos)
4388 });
4389 }
4390
4391 const oldScope = this.scope;
4392 const oldState = this.state;
4393 const oldComments = this.comments;
4394 const oldSemicolons = this.semicolons;
4395 const oldStatementPath = this.statementPath;
4396 const oldPrevStatement = this.prevStatement;
4397 this.scope = {
4398 topLevelScope: true,
4399 inTry: false,
4400 inShorthand: false,
4401 inTaggedTemplateTag: false,
4402 isStrict: false,
4403 isAsmJs: false,
4404 definitions: new StackedMap()
4405 };
4406 /** @type {ParserState} */
4407 this.state = state;
4408 this.comments = comments;
4409 this.semicolons = semicolons;
4410 this.statementPath = [];
4411 this.prevStatement = undefined;
4412 if (this.hooks.program.call(ast, comments) === undefined) {
4413 this.destructuringAssignmentProperties = new WeakMap();
4414 this.detectMode(ast.body);
4415 this.preWalkStatements(ast.body);
4416 this.prevStatement = undefined;
4417 this.blockPreWalkStatements(ast.body);
4418 this.prevStatement = undefined;
4419 this.walkStatements(ast.body);
4420 this.destructuringAssignmentProperties = undefined;
4421 }
4422 this.hooks.finish.call(ast, comments);
4423 this.scope = oldScope;
4424 /** @type {ParserState} */
4425 this.state = oldState;
4426 this.comments = oldComments;
4427 this.semicolons = oldSemicolons;
4428 this.statementPath = oldStatementPath;
4429 this.prevStatement = oldPrevStatement;
4430 return state;
4431 }
4432
4433 /**
4434 * @param {string} source source code
4435 * @returns {BasicEvaluatedExpression} evaluation result
4436 */
4437 evaluate(source) {
4438 const ast = JavascriptParser._parse(`(${source})`, {
4439 sourceType: this.sourceType,
4440 locations: false
4441 });
4442 if (ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement") {
4443 throw new Error("evaluate: Source is not a expression");
4444 }
4445 return this.evaluateExpression(ast.body[0].expression);
4446 }
4447
4448 /**
4449 * @param {Expression | Declaration | PrivateIdentifier | null | undefined} expr an expression
4450 * @param {number} commentsStartPos source position from which annotation comments are checked
4451 * @returns {boolean} true, when the expression is pure
4452 */
4453 isPure(expr, commentsStartPos) {
4454 if (!expr) return true;
4455 const result = this.hooks.isPure
4456 .for(expr.type)
4457 .call(expr, commentsStartPos);
4458 if (typeof result === "boolean") return result;
4459 switch (expr.type) {
4460 // TODO handle more cases
4461 case "ClassDeclaration":
4462 case "ClassExpression": {
4463 if (expr.body.type !== "ClassBody") return false;
4464 if (
4465 expr.superClass &&
4466 !this.isPure(expr.superClass, /** @type {Range} */ (expr.range)[0])
4467 ) {
4468 return false;
4469 }
4470 const items =
4471 /** @type {TODO[]} */
4472 (expr.body.body);
4473 return items.every(item => {
4474 if (
4475 item.computed &&
4476 item.key &&
4477 !this.isPure(item.key, item.range[0])
4478 ) {
4479 return false;
4480 }
4481
4482 if (
4483 item.static &&
4484 item.value &&
4485 !this.isPure(
4486 item.value,
4487 item.key ? item.key.range[1] : item.range[0]
4488 )
4489 ) {
4490 return false;
4491 }
4492
4493 if (item.type === "StaticBlock") {
4494 return false;
4495 }
4496
4497 if (
4498 expr.superClass &&
4499 item.type === "MethodDefinition" &&
4500 item.kind === "constructor"
4501 ) {
4502 return false;
4503 }
4504
4505 return true;
4506 });
4507 }
4508
4509 case "FunctionDeclaration":
4510 case "FunctionExpression":
4511 case "ArrowFunctionExpression":
4512 case "ThisExpression":
4513 case "Literal":
4514 case "TemplateLiteral":
4515 case "Identifier":
4516 case "PrivateIdentifier":
4517 return true;
4518
4519 case "VariableDeclaration":
4520 return expr.declarations.every(decl =>
4521 this.isPure(decl.init, /** @type {Range} */ (decl.range)[0])
4522 );
4523
4524 case "ConditionalExpression":
4525 return (
4526 this.isPure(expr.test, commentsStartPos) &&
4527 this.isPure(
4528 expr.consequent,
4529 /** @type {Range} */ (expr.test.range)[1]
4530 ) &&
4531 this.isPure(
4532 expr.alternate,
4533 /** @type {Range} */ (expr.consequent.range)[1]
4534 )
4535 );
4536
4537 case "LogicalExpression":
4538 return (
4539 this.isPure(expr.left, commentsStartPos) &&
4540 this.isPure(expr.right, /** @type {Range} */ (expr.left.range)[1])
4541 );
4542
4543 case "SequenceExpression":
4544 return expr.expressions.every(expr => {
4545 const pureFlag = this.isPure(expr, commentsStartPos);
4546 commentsStartPos = /** @type {Range} */ (expr.range)[1];
4547 return pureFlag;
4548 });
4549
4550 case "CallExpression": {
4551 const pureFlag =
4552 /** @type {Range} */ (expr.range)[0] - commentsStartPos > 12 &&
4553 this.getComments([
4554 commentsStartPos,
4555 /** @type {Range} */ (expr.range)[0]
4556 ]).some(
4557 comment =>
4558 comment.type === "Block" &&
4559 /^\s*(#|@)__PURE__\s*$/.test(comment.value)
4560 );
4561 if (!pureFlag) return false;
4562 commentsStartPos = /** @type {Range} */ (expr.callee.range)[1];
4563 return expr.arguments.every(arg => {
4564 if (arg.type === "SpreadElement") return false;
4565 const pureFlag = this.isPure(arg, commentsStartPos);
4566 commentsStartPos = /** @type {Range} */ (arg.range)[1];
4567 return pureFlag;
4568 });
4569 }
4570 }
4571 const evaluated = this.evaluateExpression(expr);
4572 return !evaluated.couldHaveSideEffects();
4573 }
4574
4575 /**
4576 * @param {Range} range range
4577 * @returns {Comment[]} comments in the range
4578 */
4579 getComments(range) {
4580 const [rangeStart, rangeEnd] = range;
4581 /**
4582 * @param {Comment} comment comment
4583 * @param {number} needle needle
4584 * @returns {number} compared
4585 */
4586 const compare = (comment, needle) =>
4587 /** @type {Range} */ (comment.range)[0] - needle;
4588 const comments = /** @type {Comment[]} */ (this.comments);
4589 let idx = binarySearchBounds.ge(comments, rangeStart, compare);
4590 /** @type {Comment[]} */
4591 const commentsInRange = [];
4592 while (
4593 comments[idx] &&
4594 /** @type {Range} */ (comments[idx].range)[1] <= rangeEnd
4595 ) {
4596 commentsInRange.push(comments[idx]);
4597 idx++;
4598 }
4599
4600 return commentsInRange;
4601 }
4602
4603 /**
4604 * @param {number} pos source code position
4605 * @returns {boolean} true when a semicolon has been inserted before this position, false if not
4606 */
4607 isAsiPosition(pos) {
4608 const currentStatement =
4609 /** @type {StatementPath} */
4610 (this.statementPath)[
4611 /** @type {StatementPath} */
4612 (this.statementPath).length - 1
4613 ];
4614 if (currentStatement === undefined) throw new Error("Not in statement");
4615 const range = /** @type {Range} */ (currentStatement.range);
4616
4617 return (
4618 // Either asking directly for the end position of the current statement
4619 (range[1] === pos &&
4620 /** @type {Set<number>} */ (this.semicolons).has(pos)) ||
4621 // Or asking for the start position of the current statement,
4622 // here we have to check multiple things
4623 (range[0] === pos &&
4624 // is there a previous statement which might be relevant?
4625 this.prevStatement !== undefined &&
4626 // is the end position of the previous statement an ASI position?
4627 /** @type {Set<number>} */ (this.semicolons).has(
4628 /** @type {Range} */ (this.prevStatement.range)[1]
4629 ))
4630 );
4631 }
4632
4633 /**
4634 * @param {number} pos source code position
4635 * @returns {void}
4636 */
4637 setAsiPosition(pos) {
4638 /** @type {Set<number>} */ (this.semicolons).add(pos);
4639 }
4640
4641 /**
4642 * @param {number} pos source code position
4643 * @returns {void}
4644 */
4645 unsetAsiPosition(pos) {
4646 /** @type {Set<number>} */ (this.semicolons).delete(pos);
4647 }
4648
4649 /**
4650 * @param {Expression} expr expression
4651 * @returns {boolean} true, when the expression is a statement level expression
4652 */
4653 isStatementLevelExpression(expr) {
4654 const currentStatement =
4655 /** @type {StatementPath} */
4656 (this.statementPath)[
4657 /** @type {StatementPath} */
4658 (this.statementPath).length - 1
4659 ];
4660 return (
4661 expr === currentStatement ||
4662 (currentStatement.type === "ExpressionStatement" &&
4663 currentStatement.expression === expr)
4664 );
4665 }
4666
4667 /**
4668 * @param {string} name name
4669 * @param {symbol} tag tag info
4670 * @returns {TODO} tag data
4671 */
4672 getTagData(name, tag) {
4673 const info = this.scope.definitions.get(name);
4674 if (info instanceof VariableInfo) {
4675 let tagInfo = info.tagInfo;
4676 while (tagInfo !== undefined) {
4677 if (tagInfo.tag === tag) return tagInfo.data;
4678 tagInfo = tagInfo.next;
4679 }
4680 }
4681 }
4682
4683 /**
4684 * @param {string} name name
4685 * @param {symbol} tag tag info
4686 * @param {TODO=} data data
4687 */
4688 tagVariable(name, tag, data) {
4689 const oldInfo = this.scope.definitions.get(name);
4690 /** @type {VariableInfo} */
4691 let newInfo;
4692 if (oldInfo === undefined) {
4693 newInfo = new VariableInfo(this.scope, name, {
4694 tag,
4695 data,
4696 next: undefined
4697 });
4698 } else if (oldInfo instanceof VariableInfo) {
4699 newInfo = new VariableInfo(oldInfo.declaredScope, oldInfo.freeName, {
4700 tag,
4701 data,
4702 next: oldInfo.tagInfo
4703 });
4704 } else {
4705 newInfo = new VariableInfo(oldInfo, true, {
4706 tag,
4707 data,
4708 next: undefined
4709 });
4710 }
4711 this.scope.definitions.set(name, newInfo);
4712 }
4713
4714 /**
4715 * @param {string} name variable name
4716 */
4717 defineVariable(name) {
4718 const oldInfo = this.scope.definitions.get(name);
4719 // Don't redefine variable in same scope to keep existing tags
4720 if (oldInfo instanceof VariableInfo && oldInfo.declaredScope === this.scope)
4721 return;
4722 this.scope.definitions.set(name, this.scope);
4723 }
4724
4725 /**
4726 * @param {string} name variable name
4727 */
4728 undefineVariable(name) {
4729 this.scope.definitions.delete(name);
4730 }
4731
4732 /**
4733 * @param {string} name variable name
4734 * @returns {boolean} true, when variable is defined
4735 */
4736 isVariableDefined(name) {
4737 const info = this.scope.definitions.get(name);
4738 if (info === undefined) return false;
4739 if (info instanceof VariableInfo) {
4740 return info.freeName === true;
4741 }
4742 return true;
4743 }
4744
4745 /**
4746 * @param {string} name variable name
4747 * @returns {string | ExportedVariableInfo} info for this variable
4748 */
4749 getVariableInfo(name) {
4750 const value = this.scope.definitions.get(name);
4751 if (value === undefined) {
4752 return name;
4753 }
4754 return value;
4755 }
4756
4757 /**
4758 * @param {string} name variable name
4759 * @param {string | ExportedVariableInfo} variableInfo new info for this variable
4760 * @returns {void}
4761 */
4762 setVariable(name, variableInfo) {
4763 if (typeof variableInfo === "string") {
4764 if (variableInfo === name) {
4765 this.scope.definitions.delete(name);
4766 } else {
4767 this.scope.definitions.set(
4768 name,
4769 new VariableInfo(this.scope, variableInfo, undefined)
4770 );
4771 }
4772 } else {
4773 this.scope.definitions.set(name, variableInfo);
4774 }
4775 }
4776
4777 /**
4778 * @param {TagInfo} tagInfo tag info
4779 * @returns {VariableInfo} variable info
4780 */
4781 evaluatedVariable(tagInfo) {
4782 return new VariableInfo(this.scope, undefined, tagInfo);
4783 }
4784
4785 /**
4786 * @param {Range} range range of the comment
4787 * @returns {{ options: Record<string, any> | null, errors: (Error & { comment: Comment })[] | null }} result
4788 */
4789 parseCommentOptions(range) {
4790 const comments = this.getComments(range);
4791 if (comments.length === 0) {
4792 return EMPTY_COMMENT_OPTIONS;
4793 }
4794 /** @type {Record<string, EXPECTED_ANY> } */
4795 const options = {};
4796 /** @type {(Error & { comment: Comment })[]} */
4797 const errors = [];
4798 for (const comment of comments) {
4799 const { value } = comment;
4800 if (value && webpackCommentRegExp.test(value)) {
4801 // try compile only if webpack options comment is present
4802 try {
4803 for (let [key, val] of Object.entries(
4804 vm.runInContext(
4805 `(function(){return {${value}};})()`,
4806 this.magicCommentContext
4807 )
4808 )) {
4809 if (typeof val === "object" && val !== null) {
4810 val =
4811 val.constructor.name === "RegExp"
4812 ? new RegExp(val)
4813 : JSON.parse(JSON.stringify(val));
4814 }
4815 options[key] = val;
4816 }
4817 } catch (err) {
4818 const newErr = new Error(String(/** @type {Error} */ (err).message));
4819 newErr.stack = String(/** @type {Error} */ (err).stack);
4820 Object.assign(newErr, { comment });
4821 errors.push(/** @type {(Error & { comment: Comment })} */ (newErr));
4822 }
4823 }
4824 }
4825 return { options, errors };
4826 }
4827
4828 /**
4829 * @param {Expression | Super} expression a member expression
4830 * @returns {{ members: string[], object: Expression | Super, membersOptionals: boolean[], memberRanges: Range[] }} member names (reverse order) and remaining object
4831 */
4832 extractMemberExpressionChain(expression) {
4833 /** @type {Node} */
4834 let expr = expression;
4835 const members = [];
4836 const membersOptionals = [];
4837 const memberRanges = [];
4838 while (expr.type === "MemberExpression") {
4839 if (expr.computed) {
4840 if (expr.property.type !== "Literal") break;
4841 members.push(`${expr.property.value}`); // the literal
4842 memberRanges.push(/** @type {Range} */ (expr.object.range)); // the range of the expression fragment before the literal
4843 } else {
4844 if (expr.property.type !== "Identifier") break;
4845 members.push(expr.property.name); // the identifier
4846 memberRanges.push(/** @type {Range} */ (expr.object.range)); // the range of the expression fragment before the identifier
4847 }
4848 membersOptionals.push(expr.optional);
4849 expr = expr.object;
4850 }
4851
4852 return {
4853 members,
4854 membersOptionals,
4855 memberRanges,
4856 object: expr
4857 };
4858 }
4859
4860 /**
4861 * @param {string} varName variable name
4862 * @returns {{name: string, info: VariableInfo | string} | undefined} name of the free variable and variable info for that
4863 */
4864 getFreeInfoFromVariable(varName) {
4865 const info = this.getVariableInfo(varName);
4866 let name;
4867 if (info instanceof VariableInfo) {
4868 name = info.freeName;
4869 if (typeof name !== "string") return;
4870 } else if (typeof info !== "string") {
4871 return;
4872 } else {
4873 name = info;
4874 }
4875 return { info, name };
4876 }
4877
4878 /** @typedef {{ type: "call", call: CallExpression, calleeName: string, rootInfo: string | VariableInfo, getCalleeMembers: () => string[], name: string, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[]}} CallExpressionInfo */
4879 /** @typedef {{ type: "expression", rootInfo: string | VariableInfo, name: string, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[]}} ExpressionExpressionInfo */
4880
4881 /**
4882 * @param {Expression | Super} expression a member expression
4883 * @param {number} allowedTypes which types should be returned, presented in bit mask
4884 * @returns {CallExpressionInfo | ExpressionExpressionInfo | undefined} expression info
4885 */
4886 getMemberExpressionInfo(expression, allowedTypes) {
4887 const { object, members, membersOptionals, memberRanges } =
4888 this.extractMemberExpressionChain(expression);
4889 switch (object.type) {
4890 case "CallExpression": {
4891 if ((allowedTypes & ALLOWED_MEMBER_TYPES_CALL_EXPRESSION) === 0) return;
4892 let callee = object.callee;
4893 let rootMembers = EMPTY_ARRAY;
4894 if (callee.type === "MemberExpression") {
4895 ({ object: callee, members: rootMembers } =
4896 this.extractMemberExpressionChain(callee));
4897 }
4898 const rootName = getRootName(callee);
4899 if (!rootName) return;
4900 const result = this.getFreeInfoFromVariable(rootName);
4901 if (!result) return;
4902 const { info: rootInfo, name: resolvedRoot } = result;
4903 const calleeName = objectAndMembersToName(resolvedRoot, rootMembers);
4904 return {
4905 type: "call",
4906 call: object,
4907 calleeName,
4908 rootInfo,
4909 getCalleeMembers: memoize(() => rootMembers.reverse()),
4910 name: objectAndMembersToName(`${calleeName}()`, members),
4911 getMembers: memoize(() => members.reverse()),
4912 getMembersOptionals: memoize(() => membersOptionals.reverse()),
4913 getMemberRanges: memoize(() => memberRanges.reverse())
4914 };
4915 }
4916 case "Identifier":
4917 case "MetaProperty":
4918 case "ThisExpression": {
4919 if ((allowedTypes & ALLOWED_MEMBER_TYPES_EXPRESSION) === 0) return;
4920 const rootName = getRootName(object);
4921 if (!rootName) return;
4922
4923 const result = this.getFreeInfoFromVariable(rootName);
4924 if (!result) return;
4925 const { info: rootInfo, name: resolvedRoot } = result;
4926 return {
4927 type: "expression",
4928 name: objectAndMembersToName(resolvedRoot, members),
4929 rootInfo,
4930 getMembers: memoize(() => members.reverse()),
4931 getMembersOptionals: memoize(() => membersOptionals.reverse()),
4932 getMemberRanges: memoize(() => memberRanges.reverse())
4933 };
4934 }
4935 }
4936 }
4937
4938 /**
4939 * @param {MemberExpression} expression an expression
4940 * @returns {{ name: string, rootInfo: ExportedVariableInfo, getMembers: () => string[]} | undefined} name info
4941 */
4942 getNameForExpression(expression) {
4943 return this.getMemberExpressionInfo(
4944 expression,
4945 ALLOWED_MEMBER_TYPES_EXPRESSION
4946 );
4947 }
4948
4949 /**
4950 * @param {string} code source code
4951 * @param {ParseOptions} options parsing options
4952 * @returns {Program} parsed ast
4953 */
4954 static _parse(code, options) {
4955 const type = options ? options.sourceType : "module";
4956 /** @type {AcornOptions} */
4957 const parserOptions = {
4958 ...defaultParserOptions,
4959 allowReturnOutsideFunction: type === "script",
4960 ...options,
4961 sourceType: type === "auto" ? "module" : type
4962 };
4963
4964 /** @type {import("acorn").Program | undefined} */
4965 let ast;
4966 let error;
4967 let threw = false;
4968 try {
4969 ast = parser.parse(code, parserOptions);
4970 } catch (err) {
4971 error = err;
4972 threw = true;
4973 }
4974
4975 if (threw && type === "auto") {
4976 parserOptions.sourceType = "script";
4977 if (!("allowReturnOutsideFunction" in options)) {
4978 parserOptions.allowReturnOutsideFunction = true;
4979 }
4980 if (Array.isArray(parserOptions.onComment)) {
4981 parserOptions.onComment.length = 0;
4982 }
4983 try {
4984 ast = parser.parse(code, parserOptions);
4985 threw = false;
4986 } catch (_err) {
4987 // we use the error from first parse try
4988 // so nothing to do here
4989 }
4990 }
4991
4992 if (threw) {
4993 throw error;
4994 }
4995
4996 return /** @type {Program} */ (ast);
4997 }
4998}
4999
5000module.exports = JavascriptParser;
5001module.exports.ALLOWED_MEMBER_TYPES_ALL = ALLOWED_MEMBER_TYPES_ALL;
5002module.exports.ALLOWED_MEMBER_TYPES_EXPRESSION =
5003 ALLOWED_MEMBER_TYPES_EXPRESSION;
5004module.exports.ALLOWED_MEMBER_TYPES_CALL_EXPRESSION =
5005 ALLOWED_MEMBER_TYPES_CALL_EXPRESSION;
5006module.exports.getImportAttributes = getImportAttributes;
5007module.exports.VariableInfo = VariableInfo;
Note: See TracBrowser for help on using the repository browser.