source: frontend/node_modules/webpack/lib/javascript/JavascriptParser.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

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