UNPKG

112 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { Parser: AcornParser } = require("acorn");
9const { SyncBailHook, HookMap } = require("tapable");
10const vm = require("vm");
11const Parser = require("../Parser");
12const StackedMap = require("../util/StackedMap");
13const binarySearchBounds = require("../util/binarySearchBounds");
14const memoize = require("../util/memoize");
15const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
16
17/** @typedef {import("acorn").Options} AcornOptions */
18/** @typedef {import("estree").ArrayExpression} ArrayExpressionNode */
19/** @typedef {import("estree").BinaryExpression} BinaryExpressionNode */
20/** @typedef {import("estree").BlockStatement} BlockStatementNode */
21/** @typedef {import("estree").SequenceExpression} SequenceExpressionNode */
22/** @typedef {import("estree").CallExpression} CallExpressionNode */
23/** @typedef {import("estree").ClassDeclaration} ClassDeclarationNode */
24/** @typedef {import("estree").ClassExpression} ClassExpressionNode */
25/** @typedef {import("estree").Comment} CommentNode */
26/** @typedef {import("estree").ConditionalExpression} ConditionalExpressionNode */
27/** @typedef {import("estree").Declaration} DeclarationNode */
28/** @typedef {import("estree").Expression} ExpressionNode */
29/** @typedef {import("estree").Identifier} IdentifierNode */
30/** @typedef {import("estree").IfStatement} IfStatementNode */
31/** @typedef {import("estree").LabeledStatement} LabeledStatementNode */
32/** @typedef {import("estree").Literal} LiteralNode */
33/** @typedef {import("estree").LogicalExpression} LogicalExpressionNode */
34/** @typedef {import("estree").ChainExpression} ChainExpressionNode */
35/** @typedef {import("estree").MemberExpression} MemberExpressionNode */
36/** @typedef {import("estree").MetaProperty} MetaPropertyNode */
37/** @typedef {import("estree").MethodDefinition} MethodDefinitionNode */
38/** @typedef {import("estree").ModuleDeclaration} ModuleDeclarationNode */
39/** @typedef {import("estree").NewExpression} NewExpressionNode */
40/** @typedef {import("estree").Node} AnyNode */
41/** @typedef {import("estree").Program} ProgramNode */
42/** @typedef {import("estree").Statement} StatementNode */
43/** @typedef {import("estree").Super} SuperNode */
44/** @typedef {import("estree").TaggedTemplateExpression} TaggedTemplateExpressionNode */
45/** @typedef {import("estree").TemplateLiteral} TemplateLiteralNode */
46/** @typedef {import("estree").ThisExpression} ThisExpressionNode */
47/** @typedef {import("estree").UnaryExpression} UnaryExpressionNode */
48/** @typedef {import("estree").VariableDeclarator} VariableDeclaratorNode */
49/** @template T @typedef {import("tapable").AsArray<T>} AsArray<T> */
50/** @typedef {import("../Parser").ParserState} ParserState */
51/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
52/** @typedef {{declaredScope: ScopeInfo, freeName: string | true, tagInfo: TagInfo | undefined}} VariableInfoInterface */
53/** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => string[] }} GetInfoResult */
54
55const EMPTY_ARRAY = [];
56const ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = 0b01;
57const ALLOWED_MEMBER_TYPES_EXPRESSION = 0b10;
58const ALLOWED_MEMBER_TYPES_ALL = 0b11;
59
60// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
61
62const parser = AcornParser;
63
64class VariableInfo {
65 /**
66 * @param {ScopeInfo} declaredScope scope in which the variable is declared
67 * @param {string | true} freeName which free name the variable aliases, or true when none
68 * @param {TagInfo | undefined} tagInfo info about tags
69 */
70 constructor(declaredScope, freeName, tagInfo) {
71 this.declaredScope = declaredScope;
72 this.freeName = freeName;
73 this.tagInfo = tagInfo;
74 }
75}
76
77/** @typedef {string | ScopeInfo | VariableInfo} ExportedVariableInfo */
78/** @typedef {LiteralNode | string | null | undefined} ImportSource */
79/** @typedef {Omit<AcornOptions, "sourceType" | "ecmaVersion"> & { sourceType: "module" | "script" | "auto", ecmaVersion?: AcornOptions["ecmaVersion"] }} ParseOptions */
80
81/**
82 * @typedef {Object} TagInfo
83 * @property {any} tag
84 * @property {any} data
85 * @property {TagInfo | undefined} next
86 */
87
88/**
89 * @typedef {Object} ScopeInfo
90 * @property {StackedMap<string, VariableInfo | ScopeInfo>} definitions
91 * @property {boolean | "arrow"} topLevelScope
92 * @property {boolean} inShorthand
93 * @property {boolean} isStrict
94 * @property {boolean} isAsmJs
95 * @property {boolean} inTry
96 */
97
98const joinRanges = (startRange, endRange) => {
99 if (!endRange) return startRange;
100 if (!startRange) return endRange;
101 return [startRange[0], endRange[1]];
102};
103
104const objectAndMembersToName = (object, membersReversed) => {
105 let name = object;
106 for (let i = membersReversed.length - 1; i >= 0; i--) {
107 name = name + "." + membersReversed[i];
108 }
109 return name;
110};
111
112const getRootName = expression => {
113 switch (expression.type) {
114 case "Identifier":
115 return expression.name;
116 case "ThisExpression":
117 return "this";
118 case "MetaProperty":
119 return `${expression.meta.name}.${expression.property.name}`;
120 default:
121 return undefined;
122 }
123};
124
125/** @type {AcornOptions} */
126const defaultParserOptions = {
127 ranges: true,
128 locations: true,
129 ecmaVersion: "latest",
130 sourceType: "module",
131 allowAwaitOutsideFunction: true,
132 onComment: null
133};
134
135// regexp to match at least one "magic comment"
136const webpackCommentRegExp = new RegExp(/(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/);
137
138const EMPTY_COMMENT_OPTIONS = {
139 options: null,
140 errors: null
141};
142
143class JavascriptParser extends Parser {
144 /**
145 * @param {"module" | "script" | "auto"} sourceType default source type
146 */
147 constructor(sourceType = "auto") {
148 super();
149 this.hooks = Object.freeze({
150 /** @type {HookMap<SyncBailHook<[UnaryExpressionNode], BasicEvaluatedExpression | undefined | null>>} */
151 evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])),
152 /** @type {HookMap<SyncBailHook<[ExpressionNode], BasicEvaluatedExpression | undefined | null>>} */
153 evaluate: new HookMap(() => new SyncBailHook(["expression"])),
154 /** @type {HookMap<SyncBailHook<[IdentifierNode | ThisExpressionNode | MemberExpressionNode | MetaPropertyNode], BasicEvaluatedExpression | undefined | null>>} */
155 evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])),
156 /** @type {HookMap<SyncBailHook<[IdentifierNode | ThisExpressionNode | MemberExpressionNode], BasicEvaluatedExpression | undefined | null>>} */
157 evaluateDefinedIdentifier: new HookMap(
158 () => new SyncBailHook(["expression"])
159 ),
160 /** @type {HookMap<SyncBailHook<[CallExpressionNode, BasicEvaluatedExpression | undefined], BasicEvaluatedExpression | undefined | null>>} */
161 evaluateCallExpressionMember: new HookMap(
162 () => new SyncBailHook(["expression", "param"])
163 ),
164 /** @type {HookMap<SyncBailHook<[ExpressionNode | DeclarationNode | PrivateIdentifierNode, number], boolean | void>>} */
165 isPure: new HookMap(
166 () => new SyncBailHook(["expression", "commentsStartPosition"])
167 ),
168 /** @type {SyncBailHook<[StatementNode | ModuleDeclarationNode], boolean | void>} */
169 preStatement: new SyncBailHook(["statement"]),
170
171 /** @type {SyncBailHook<[StatementNode | ModuleDeclarationNode], boolean | void>} */
172 blockPreStatement: new SyncBailHook(["declaration"]),
173 /** @type {SyncBailHook<[StatementNode | ModuleDeclarationNode], boolean | void>} */
174 statement: new SyncBailHook(["statement"]),
175 /** @type {SyncBailHook<[IfStatementNode], boolean | void>} */
176 statementIf: new SyncBailHook(["statement"]),
177 /** @type {SyncBailHook<[ExpressionNode, ClassExpressionNode | ClassDeclarationNode], boolean | void>} */
178 classExtendsExpression: new SyncBailHook(["expression", "statement"]),
179 /** @type {SyncBailHook<[MethodDefinitionNode | PropertyDefinitionNode, ClassExpressionNode | ClassDeclarationNode], boolean | void>} */
180 classBodyElement: new SyncBailHook(["element", "statement"]),
181 /** @type {SyncBailHook<[ExpressionNode, MethodDefinitionNode | PropertyDefinitionNode, ClassExpressionNode | ClassDeclarationNode], boolean | void>} */
182 classBodyValue: new SyncBailHook(["expression", "element", "statement"]),
183 /** @type {HookMap<SyncBailHook<[LabeledStatementNode], boolean | void>>} */
184 label: new HookMap(() => new SyncBailHook(["statement"])),
185 /** @type {SyncBailHook<[StatementNode, ImportSource], boolean | void>} */
186 import: new SyncBailHook(["statement", "source"]),
187 /** @type {SyncBailHook<[StatementNode, ImportSource, string, string], boolean | void>} */
188 importSpecifier: new SyncBailHook([
189 "statement",
190 "source",
191 "exportName",
192 "identifierName"
193 ]),
194 /** @type {SyncBailHook<[StatementNode], boolean | void>} */
195 export: new SyncBailHook(["statement"]),
196 /** @type {SyncBailHook<[StatementNode, ImportSource], boolean | void>} */
197 exportImport: new SyncBailHook(["statement", "source"]),
198 /** @type {SyncBailHook<[StatementNode, DeclarationNode], boolean | void>} */
199 exportDeclaration: new SyncBailHook(["statement", "declaration"]),
200 /** @type {SyncBailHook<[StatementNode, DeclarationNode], boolean | void>} */
201 exportExpression: new SyncBailHook(["statement", "declaration"]),
202 /** @type {SyncBailHook<[StatementNode, string, string, number | undefined], boolean | void>} */
203 exportSpecifier: new SyncBailHook([
204 "statement",
205 "identifierName",
206 "exportName",
207 "index"
208 ]),
209 /** @type {SyncBailHook<[StatementNode, ImportSource, string, string, number | undefined], boolean | void>} */
210 exportImportSpecifier: new SyncBailHook([
211 "statement",
212 "source",
213 "identifierName",
214 "exportName",
215 "index"
216 ]),
217 /** @type {SyncBailHook<[VariableDeclaratorNode, StatementNode], boolean | void>} */
218 preDeclarator: new SyncBailHook(["declarator", "statement"]),
219 /** @type {SyncBailHook<[VariableDeclaratorNode, StatementNode], boolean | void>} */
220 declarator: new SyncBailHook(["declarator", "statement"]),
221 /** @type {HookMap<SyncBailHook<[DeclarationNode], boolean | void>>} */
222 varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])),
223 /** @type {HookMap<SyncBailHook<[DeclarationNode], boolean | void>>} */
224 varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])),
225 /** @type {HookMap<SyncBailHook<[DeclarationNode], boolean | void>>} */
226 varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])),
227 /** @type {HookMap<SyncBailHook<[DeclarationNode], boolean | void>>} */
228 varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])),
229 /** @type {HookMap<SyncBailHook<[IdentifierNode], boolean | void>>} */
230 pattern: new HookMap(() => new SyncBailHook(["pattern"])),
231 /** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
232 canRename: new HookMap(() => new SyncBailHook(["initExpression"])),
233 /** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
234 rename: new HookMap(() => new SyncBailHook(["initExpression"])),
235 /** @type {HookMap<SyncBailHook<[import("estree").AssignmentExpression], boolean | void>>} */
236 assign: new HookMap(() => new SyncBailHook(["expression"])),
237 /** @type {HookMap<SyncBailHook<[import("estree").AssignmentExpression, string[]], boolean | void>>} */
238 assignMemberChain: new HookMap(
239 () => new SyncBailHook(["expression", "members"])
240 ),
241 /** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
242 typeof: new HookMap(() => new SyncBailHook(["expression"])),
243 /** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
244 importCall: new SyncBailHook(["expression"]),
245 /** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
246 topLevelAwait: new SyncBailHook(["expression"]),
247 /** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
248 call: new HookMap(() => new SyncBailHook(["expression"])),
249 /** Something like "a.b()" */
250 /** @type {HookMap<SyncBailHook<[CallExpressionNode, string[]], boolean | void>>} */
251 callMemberChain: new HookMap(
252 () => new SyncBailHook(["expression", "members"])
253 ),
254 /** Something like "a.b().c.d" */
255 /** @type {HookMap<SyncBailHook<[ExpressionNode, string[], CallExpressionNode, string[]], boolean | void>>} */
256 memberChainOfCallMemberChain: new HookMap(
257 () =>
258 new SyncBailHook([
259 "expression",
260 "calleeMembers",
261 "callExpression",
262 "members"
263 ])
264 ),
265 /** Something like "a.b().c.d()"" */
266 /** @type {HookMap<SyncBailHook<[ExpressionNode, string[], CallExpressionNode, string[]], boolean | void>>} */
267 callMemberChainOfCallMemberChain: new HookMap(
268 () =>
269 new SyncBailHook([
270 "expression",
271 "calleeMembers",
272 "innerCallExpression",
273 "members"
274 ])
275 ),
276 /** @type {SyncBailHook<[ChainExpressionNode], boolean | void>} */
277 optionalChaining: new SyncBailHook(["optionalChaining"]),
278 /** @type {HookMap<SyncBailHook<[NewExpressionNode], boolean | void>>} */
279 new: new HookMap(() => new SyncBailHook(["expression"])),
280 /** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
281 expression: new HookMap(() => new SyncBailHook(["expression"])),
282 /** @type {HookMap<SyncBailHook<[ExpressionNode, string[]], boolean | void>>} */
283 expressionMemberChain: new HookMap(
284 () => new SyncBailHook(["expression", "members"])
285 ),
286 /** @type {HookMap<SyncBailHook<[ExpressionNode, string[]], boolean | void>>} */
287 unhandledExpressionMemberChain: new HookMap(
288 () => new SyncBailHook(["expression", "members"])
289 ),
290 /** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
291 expressionConditionalOperator: new SyncBailHook(["expression"]),
292 /** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
293 expressionLogicalOperator: new SyncBailHook(["expression"]),
294 /** @type {SyncBailHook<[ProgramNode, CommentNode[]], boolean | void>} */
295 program: new SyncBailHook(["ast", "comments"]),
296 /** @type {SyncBailHook<[ProgramNode, CommentNode[]], boolean | void>} */
297 finish: new SyncBailHook(["ast", "comments"])
298 });
299 this.sourceType = sourceType;
300 /** @type {ScopeInfo} */
301 this.scope = undefined;
302 /** @type {ParserState} */
303 this.state = undefined;
304 this.comments = undefined;
305 this.semicolons = undefined;
306 /** @type {(StatementNode|ExpressionNode)[]} */
307 this.statementPath = undefined;
308 this.prevStatement = undefined;
309 this.currentTagData = undefined;
310 this._initializeEvaluating();
311 }
312
313 _initializeEvaluating() {
314 this.hooks.evaluate.for("Literal").tap("JavascriptParser", _expr => {
315 const expr = /** @type {LiteralNode} */ (_expr);
316
317 switch (typeof expr.value) {
318 case "number":
319 return new BasicEvaluatedExpression()
320 .setNumber(expr.value)
321 .setRange(expr.range);
322 case "bigint":
323 return new BasicEvaluatedExpression()
324 .setBigInt(expr.value)
325 .setRange(expr.range);
326 case "string":
327 return new BasicEvaluatedExpression()
328 .setString(expr.value)
329 .setRange(expr.range);
330 case "boolean":
331 return new BasicEvaluatedExpression()
332 .setBoolean(expr.value)
333 .setRange(expr.range);
334 }
335 if (expr.value === null) {
336 return new BasicEvaluatedExpression().setNull().setRange(expr.range);
337 }
338 if (expr.value instanceof RegExp) {
339 return new BasicEvaluatedExpression()
340 .setRegExp(expr.value)
341 .setRange(expr.range);
342 }
343 });
344 this.hooks.evaluate.for("NewExpression").tap("JavascriptParser", _expr => {
345 const expr = /** @type {NewExpressionNode} */ (_expr);
346 const callee = expr.callee;
347 if (
348 callee.type !== "Identifier" ||
349 callee.name !== "RegExp" ||
350 expr.arguments.length > 2 ||
351 this.getVariableInfo("RegExp") !== "RegExp"
352 )
353 return;
354
355 let regExp, flags;
356 const arg1 = expr.arguments[0];
357
358 if (arg1) {
359 if (arg1.type === "SpreadElement") return;
360
361 const evaluatedRegExp = this.evaluateExpression(arg1);
362
363 if (!evaluatedRegExp) return;
364
365 regExp = evaluatedRegExp.asString();
366
367 if (!regExp) return;
368 } else {
369 return new BasicEvaluatedExpression()
370 .setRegExp(new RegExp(""))
371 .setRange(expr.range);
372 }
373
374 const arg2 = expr.arguments[1];
375
376 if (arg2) {
377 if (arg2.type === "SpreadElement") return;
378
379 const evaluatedFlags = this.evaluateExpression(arg2);
380
381 if (!evaluatedFlags) return;
382
383 if (!evaluatedFlags.isUndefined()) {
384 flags = evaluatedFlags.asString();
385
386 if (
387 flags === undefined ||
388 !BasicEvaluatedExpression.isValidRegExpFlags(flags)
389 )
390 return;
391 }
392 }
393
394 return new BasicEvaluatedExpression()
395 .setRegExp(flags ? new RegExp(regExp, flags) : new RegExp(regExp))
396 .setRange(expr.range);
397 });
398 this.hooks.evaluate
399 .for("LogicalExpression")
400 .tap("JavascriptParser", _expr => {
401 const expr = /** @type {LogicalExpressionNode} */ (_expr);
402
403 const left = this.evaluateExpression(expr.left);
404 if (!left) return;
405 if (expr.operator === "&&") {
406 const leftAsBool = left.asBool();
407 if (leftAsBool === false) return left.setRange(expr.range);
408 if (leftAsBool !== true) return;
409 } else if (expr.operator === "||") {
410 const leftAsBool = left.asBool();
411 if (leftAsBool === true) return left.setRange(expr.range);
412 if (leftAsBool !== false) return;
413 } else if (expr.operator === "??") {
414 const leftAsNullish = left.asNullish();
415 if (leftAsNullish === false) return left.setRange(expr.range);
416 if (leftAsNullish !== true) return;
417 } else return;
418 const right = this.evaluateExpression(expr.right);
419 if (!right) return;
420 if (left.couldHaveSideEffects()) right.setSideEffects();
421 return right.setRange(expr.range);
422 });
423
424 const valueAsExpression = (value, expr, sideEffects) => {
425 switch (typeof value) {
426 case "boolean":
427 return new BasicEvaluatedExpression()
428 .setBoolean(value)
429 .setSideEffects(sideEffects)
430 .setRange(expr.range);
431 case "number":
432 return new BasicEvaluatedExpression()
433 .setNumber(value)
434 .setSideEffects(sideEffects)
435 .setRange(expr.range);
436 case "bigint":
437 return new BasicEvaluatedExpression()
438 .setBigInt(value)
439 .setSideEffects(sideEffects)
440 .setRange(expr.range);
441 case "string":
442 return new BasicEvaluatedExpression()
443 .setString(value)
444 .setSideEffects(sideEffects)
445 .setRange(expr.range);
446 }
447 };
448
449 this.hooks.evaluate
450 .for("BinaryExpression")
451 .tap("JavascriptParser", _expr => {
452 const expr = /** @type {BinaryExpressionNode} */ (_expr);
453
454 const handleConstOperation = fn => {
455 const left = this.evaluateExpression(expr.left);
456 if (!left || !left.isCompileTimeValue()) return;
457
458 const right = this.evaluateExpression(expr.right);
459 if (!right || !right.isCompileTimeValue()) return;
460
461 const result = fn(
462 left.asCompileTimeValue(),
463 right.asCompileTimeValue()
464 );
465 return valueAsExpression(
466 result,
467 expr,
468 left.couldHaveSideEffects() || right.couldHaveSideEffects()
469 );
470 };
471
472 const isAlwaysDifferent = (a, b) =>
473 (a === true && b === false) || (a === false && b === true);
474
475 const handleTemplateStringCompare = (left, right, res, eql) => {
476 const getPrefix = parts => {
477 let value = "";
478 for (const p of parts) {
479 const v = p.asString();
480 if (v !== undefined) value += v;
481 else break;
482 }
483 return value;
484 };
485 const getSuffix = parts => {
486 let value = "";
487 for (let i = parts.length - 1; i >= 0; i--) {
488 const v = parts[i].asString();
489 if (v !== undefined) value = v + value;
490 else break;
491 }
492 return value;
493 };
494 const leftPrefix = getPrefix(left.parts);
495 const rightPrefix = getPrefix(right.parts);
496 const leftSuffix = getSuffix(left.parts);
497 const rightSuffix = getSuffix(right.parts);
498 const lenPrefix = Math.min(leftPrefix.length, rightPrefix.length);
499 const lenSuffix = Math.min(leftSuffix.length, rightSuffix.length);
500 if (
501 leftPrefix.slice(0, lenPrefix) !==
502 rightPrefix.slice(0, lenPrefix) ||
503 leftSuffix.slice(-lenSuffix) !== rightSuffix.slice(-lenSuffix)
504 ) {
505 return res
506 .setBoolean(!eql)
507 .setSideEffects(
508 left.couldHaveSideEffects() || right.couldHaveSideEffects()
509 );
510 }
511 };
512
513 const handleStrictEqualityComparison = eql => {
514 const left = this.evaluateExpression(expr.left);
515 if (!left) return;
516 const right = this.evaluateExpression(expr.right);
517 if (!right) return;
518 const res = new BasicEvaluatedExpression();
519 res.setRange(expr.range);
520
521 const leftConst = left.isCompileTimeValue();
522 const rightConst = right.isCompileTimeValue();
523
524 if (leftConst && rightConst) {
525 return res
526 .setBoolean(
527 eql ===
528 (left.asCompileTimeValue() === right.asCompileTimeValue())
529 )
530 .setSideEffects(
531 left.couldHaveSideEffects() || right.couldHaveSideEffects()
532 );
533 }
534
535 if (left.isArray() && right.isArray()) {
536 return res
537 .setBoolean(!eql)
538 .setSideEffects(
539 left.couldHaveSideEffects() || right.couldHaveSideEffects()
540 );
541 }
542 if (left.isTemplateString() && right.isTemplateString()) {
543 return handleTemplateStringCompare(left, right, res, eql);
544 }
545
546 const leftPrimitive = left.isPrimitiveType();
547 const rightPrimitive = right.isPrimitiveType();
548
549 if (
550 // Primitive !== Object or
551 // compile-time object types are never equal to something at runtime
552 (leftPrimitive === false &&
553 (leftConst || rightPrimitive === true)) ||
554 (rightPrimitive === false &&
555 (rightConst || leftPrimitive === true)) ||
556 // Different nullish or boolish status also means not equal
557 isAlwaysDifferent(left.asBool(), right.asBool()) ||
558 isAlwaysDifferent(left.asNullish(), right.asNullish())
559 ) {
560 return res
561 .setBoolean(!eql)
562 .setSideEffects(
563 left.couldHaveSideEffects() || right.couldHaveSideEffects()
564 );
565 }
566 };
567
568 const handleAbstractEqualityComparison = eql => {
569 const left = this.evaluateExpression(expr.left);
570 if (!left) return;
571 const right = this.evaluateExpression(expr.right);
572 if (!right) return;
573 const res = new BasicEvaluatedExpression();
574 res.setRange(expr.range);
575
576 const leftConst = left.isCompileTimeValue();
577 const rightConst = right.isCompileTimeValue();
578
579 if (leftConst && rightConst) {
580 return res
581 .setBoolean(
582 eql ===
583 // eslint-disable-next-line eqeqeq
584 (left.asCompileTimeValue() == right.asCompileTimeValue())
585 )
586 .setSideEffects(
587 left.couldHaveSideEffects() || right.couldHaveSideEffects()
588 );
589 }
590
591 if (left.isArray() && right.isArray()) {
592 return res
593 .setBoolean(!eql)
594 .setSideEffects(
595 left.couldHaveSideEffects() || right.couldHaveSideEffects()
596 );
597 }
598 if (left.isTemplateString() && right.isTemplateString()) {
599 return handleTemplateStringCompare(left, right, res, eql);
600 }
601 };
602
603 if (expr.operator === "+") {
604 const left = this.evaluateExpression(expr.left);
605 if (!left) return;
606 const right = this.evaluateExpression(expr.right);
607 if (!right) return;
608 const res = new BasicEvaluatedExpression();
609 if (left.isString()) {
610 if (right.isString()) {
611 res.setString(left.string + right.string);
612 } else if (right.isNumber()) {
613 res.setString(left.string + right.number);
614 } else if (
615 right.isWrapped() &&
616 right.prefix &&
617 right.prefix.isString()
618 ) {
619 // "left" + ("prefix" + inner + "postfix")
620 // => ("leftPrefix" + inner + "postfix")
621 res.setWrapped(
622 new BasicEvaluatedExpression()
623 .setString(left.string + right.prefix.string)
624 .setRange(joinRanges(left.range, right.prefix.range)),
625 right.postfix,
626 right.wrappedInnerExpressions
627 );
628 } else if (right.isWrapped()) {
629 // "left" + ([null] + inner + "postfix")
630 // => ("left" + inner + "postfix")
631 res.setWrapped(
632 left,
633 right.postfix,
634 right.wrappedInnerExpressions
635 );
636 } else {
637 // "left" + expr
638 // => ("left" + expr + "")
639 res.setWrapped(left, null, [right]);
640 }
641 } else if (left.isNumber()) {
642 if (right.isString()) {
643 res.setString(left.number + right.string);
644 } else if (right.isNumber()) {
645 res.setNumber(left.number + right.number);
646 } else {
647 return;
648 }
649 } else if (left.isBigInt()) {
650 if (right.isBigInt()) {
651 res.setBigInt(left.bigint + right.bigint);
652 }
653 } else if (left.isWrapped()) {
654 if (left.postfix && left.postfix.isString() && right.isString()) {
655 // ("prefix" + inner + "postfix") + "right"
656 // => ("prefix" + inner + "postfixRight")
657 res.setWrapped(
658 left.prefix,
659 new BasicEvaluatedExpression()
660 .setString(left.postfix.string + right.string)
661 .setRange(joinRanges(left.postfix.range, right.range)),
662 left.wrappedInnerExpressions
663 );
664 } else if (
665 left.postfix &&
666 left.postfix.isString() &&
667 right.isNumber()
668 ) {
669 // ("prefix" + inner + "postfix") + 123
670 // => ("prefix" + inner + "postfix123")
671 res.setWrapped(
672 left.prefix,
673 new BasicEvaluatedExpression()
674 .setString(left.postfix.string + right.number)
675 .setRange(joinRanges(left.postfix.range, right.range)),
676 left.wrappedInnerExpressions
677 );
678 } else if (right.isString()) {
679 // ("prefix" + inner + [null]) + "right"
680 // => ("prefix" + inner + "right")
681 res.setWrapped(left.prefix, right, left.wrappedInnerExpressions);
682 } else if (right.isNumber()) {
683 // ("prefix" + inner + [null]) + 123
684 // => ("prefix" + inner + "123")
685 res.setWrapped(
686 left.prefix,
687 new BasicEvaluatedExpression()
688 .setString(right.number + "")
689 .setRange(right.range),
690 left.wrappedInnerExpressions
691 );
692 } else if (right.isWrapped()) {
693 // ("prefix1" + inner1 + "postfix1") + ("prefix2" + inner2 + "postfix2")
694 // ("prefix1" + inner1 + "postfix1" + "prefix2" + inner2 + "postfix2")
695 res.setWrapped(
696 left.prefix,
697 right.postfix,
698 left.wrappedInnerExpressions &&
699 right.wrappedInnerExpressions &&
700 left.wrappedInnerExpressions
701 .concat(left.postfix ? [left.postfix] : [])
702 .concat(right.prefix ? [right.prefix] : [])
703 .concat(right.wrappedInnerExpressions)
704 );
705 } else {
706 // ("prefix" + inner + postfix) + expr
707 // => ("prefix" + inner + postfix + expr + [null])
708 res.setWrapped(
709 left.prefix,
710 null,
711 left.wrappedInnerExpressions &&
712 left.wrappedInnerExpressions.concat(
713 left.postfix ? [left.postfix, right] : [right]
714 )
715 );
716 }
717 } else {
718 if (right.isString()) {
719 // left + "right"
720 // => ([null] + left + "right")
721 res.setWrapped(null, right, [left]);
722 } else if (right.isWrapped()) {
723 // left + (prefix + inner + "postfix")
724 // => ([null] + left + prefix + inner + "postfix")
725 res.setWrapped(
726 null,
727 right.postfix,
728 right.wrappedInnerExpressions &&
729 (right.prefix ? [left, right.prefix] : [left]).concat(
730 right.wrappedInnerExpressions
731 )
732 );
733 } else {
734 return;
735 }
736 }
737 if (left.couldHaveSideEffects() || right.couldHaveSideEffects())
738 res.setSideEffects();
739 res.setRange(expr.range);
740 return res;
741 } else if (expr.operator === "-") {
742 return handleConstOperation((l, r) => l - r);
743 } else if (expr.operator === "*") {
744 return handleConstOperation((l, r) => l * r);
745 } else if (expr.operator === "/") {
746 return handleConstOperation((l, r) => l / r);
747 } else if (expr.operator === "**") {
748 return handleConstOperation((l, r) => l ** r);
749 } else if (expr.operator === "===") {
750 return handleStrictEqualityComparison(true);
751 } else if (expr.operator === "==") {
752 return handleAbstractEqualityComparison(true);
753 } else if (expr.operator === "!==") {
754 return handleStrictEqualityComparison(false);
755 } else if (expr.operator === "!=") {
756 return handleAbstractEqualityComparison(false);
757 } else if (expr.operator === "&") {
758 return handleConstOperation((l, r) => l & r);
759 } else if (expr.operator === "|") {
760 return handleConstOperation((l, r) => l | r);
761 } else if (expr.operator === "^") {
762 return handleConstOperation((l, r) => l ^ r);
763 } else if (expr.operator === ">>>") {
764 return handleConstOperation((l, r) => l >>> r);
765 } else if (expr.operator === ">>") {
766 return handleConstOperation((l, r) => l >> r);
767 } else if (expr.operator === "<<") {
768 return handleConstOperation((l, r) => l << r);
769 } else if (expr.operator === "<") {
770 return handleConstOperation((l, r) => l < r);
771 } else if (expr.operator === ">") {
772 return handleConstOperation((l, r) => l > r);
773 } else if (expr.operator === "<=") {
774 return handleConstOperation((l, r) => l <= r);
775 } else if (expr.operator === ">=") {
776 return handleConstOperation((l, r) => l >= r);
777 }
778 });
779 this.hooks.evaluate
780 .for("UnaryExpression")
781 .tap("JavascriptParser", _expr => {
782 const expr = /** @type {UnaryExpressionNode} */ (_expr);
783
784 const handleConstOperation = fn => {
785 const argument = this.evaluateExpression(expr.argument);
786 if (!argument || !argument.isCompileTimeValue()) return;
787 const result = fn(argument.asCompileTimeValue());
788 return valueAsExpression(
789 result,
790 expr,
791 argument.couldHaveSideEffects()
792 );
793 };
794
795 if (expr.operator === "typeof") {
796 switch (expr.argument.type) {
797 case "Identifier": {
798 const res = this.callHooksForName(
799 this.hooks.evaluateTypeof,
800 expr.argument.name,
801 expr
802 );
803 if (res !== undefined) return res;
804 break;
805 }
806 case "MetaProperty": {
807 const res = this.callHooksForName(
808 this.hooks.evaluateTypeof,
809 getRootName(expr.argument),
810 expr
811 );
812 if (res !== undefined) return res;
813 break;
814 }
815 case "MemberExpression": {
816 const res = this.callHooksForExpression(
817 this.hooks.evaluateTypeof,
818 expr.argument,
819 expr
820 );
821 if (res !== undefined) return res;
822 break;
823 }
824 case "ChainExpression": {
825 const res = this.callHooksForExpression(
826 this.hooks.evaluateTypeof,
827 expr.argument.expression,
828 expr
829 );
830 if (res !== undefined) return res;
831 break;
832 }
833 case "FunctionExpression": {
834 return new BasicEvaluatedExpression()
835 .setString("function")
836 .setRange(expr.range);
837 }
838 }
839 const arg = this.evaluateExpression(expr.argument);
840 if (arg.isUnknown()) return;
841 if (arg.isString()) {
842 return new BasicEvaluatedExpression()
843 .setString("string")
844 .setRange(expr.range);
845 }
846 if (arg.isWrapped()) {
847 return new BasicEvaluatedExpression()
848 .setString("string")
849 .setSideEffects()
850 .setRange(expr.range);
851 }
852 if (arg.isUndefined()) {
853 return new BasicEvaluatedExpression()
854 .setString("undefined")
855 .setRange(expr.range);
856 }
857 if (arg.isNumber()) {
858 return new BasicEvaluatedExpression()
859 .setString("number")
860 .setRange(expr.range);
861 }
862 if (arg.isBigInt()) {
863 return new BasicEvaluatedExpression()
864 .setString("bigint")
865 .setRange(expr.range);
866 }
867 if (arg.isBoolean()) {
868 return new BasicEvaluatedExpression()
869 .setString("boolean")
870 .setRange(expr.range);
871 }
872 if (arg.isConstArray() || arg.isRegExp() || arg.isNull()) {
873 return new BasicEvaluatedExpression()
874 .setString("object")
875 .setRange(expr.range);
876 }
877 if (arg.isArray()) {
878 return new BasicEvaluatedExpression()
879 .setString("object")
880 .setSideEffects(arg.couldHaveSideEffects())
881 .setRange(expr.range);
882 }
883 } else if (expr.operator === "!") {
884 const argument = this.evaluateExpression(expr.argument);
885 if (!argument) return;
886 const bool = argument.asBool();
887 if (typeof bool !== "boolean") return;
888 return new BasicEvaluatedExpression()
889 .setBoolean(!bool)
890 .setSideEffects(argument.couldHaveSideEffects())
891 .setRange(expr.range);
892 } else if (expr.operator === "~") {
893 return handleConstOperation(v => ~v);
894 } else if (expr.operator === "+") {
895 return handleConstOperation(v => +v);
896 } else if (expr.operator === "-") {
897 return handleConstOperation(v => -v);
898 }
899 });
900 this.hooks.evaluateTypeof.for("undefined").tap("JavascriptParser", expr => {
901 return new BasicEvaluatedExpression()
902 .setString("undefined")
903 .setRange(expr.range);
904 });
905 /**
906 * @param {string} exprType expression type name
907 * @param {function(ExpressionNode): GetInfoResult | undefined} getInfo get info
908 * @returns {void}
909 */
910 const tapEvaluateWithVariableInfo = (exprType, getInfo) => {
911 /** @type {ExpressionNode | undefined} */
912 let cachedExpression = undefined;
913 /** @type {GetInfoResult | undefined} */
914 let cachedInfo = undefined;
915 this.hooks.evaluate.for(exprType).tap("JavascriptParser", expr => {
916 const expression = /** @type {MemberExpressionNode} */ (expr);
917
918 const info = getInfo(expr);
919 if (info !== undefined) {
920 return this.callHooksForInfoWithFallback(
921 this.hooks.evaluateIdentifier,
922 info.name,
923 name => {
924 cachedExpression = expression;
925 cachedInfo = info;
926 },
927 name => {
928 const hook = this.hooks.evaluateDefinedIdentifier.get(name);
929 if (hook !== undefined) {
930 return hook.call(expression);
931 }
932 },
933 expression
934 );
935 }
936 });
937 this.hooks.evaluate
938 .for(exprType)
939 .tap({ name: "JavascriptParser", stage: 100 }, expr => {
940 const info = cachedExpression === expr ? cachedInfo : getInfo(expr);
941 if (info !== undefined) {
942 return new BasicEvaluatedExpression()
943 .setIdentifier(info.name, info.rootInfo, info.getMembers)
944 .setRange(expr.range);
945 }
946 });
947 this.hooks.finish.tap("JavascriptParser", () => {
948 // Cleanup for GC
949 cachedExpression = cachedInfo = undefined;
950 });
951 };
952 tapEvaluateWithVariableInfo("Identifier", expr => {
953 const info = this.getVariableInfo(
954 /** @type {IdentifierNode} */ (expr).name
955 );
956 if (
957 typeof info === "string" ||
958 (info instanceof VariableInfo && typeof info.freeName === "string")
959 ) {
960 return { name: info, rootInfo: info, getMembers: () => [] };
961 }
962 });
963 tapEvaluateWithVariableInfo("ThisExpression", expr => {
964 const info = this.getVariableInfo("this");
965 if (
966 typeof info === "string" ||
967 (info instanceof VariableInfo && typeof info.freeName === "string")
968 ) {
969 return { name: info, rootInfo: info, getMembers: () => [] };
970 }
971 });
972 this.hooks.evaluate.for("MetaProperty").tap("JavascriptParser", expr => {
973 const metaProperty = /** @type {MetaPropertyNode} */ (expr);
974
975 return this.callHooksForName(
976 this.hooks.evaluateIdentifier,
977 getRootName(expr),
978 metaProperty
979 );
980 });
981 tapEvaluateWithVariableInfo("MemberExpression", expr =>
982 this.getMemberExpressionInfo(
983 /** @type {MemberExpressionNode} */ (expr),
984 ALLOWED_MEMBER_TYPES_EXPRESSION
985 )
986 );
987
988 this.hooks.evaluate.for("CallExpression").tap("JavascriptParser", _expr => {
989 const expr = /** @type {CallExpressionNode} */ (_expr);
990 if (
991 expr.callee.type !== "MemberExpression" ||
992 expr.callee.property.type !==
993 (expr.callee.computed ? "Literal" : "Identifier")
994 ) {
995 return;
996 }
997
998 // type Super also possible here
999 const param = this.evaluateExpression(
1000 /** @type {ExpressionNode} */ (expr.callee.object)
1001 );
1002 if (!param) return;
1003 const property =
1004 expr.callee.property.type === "Literal"
1005 ? `${expr.callee.property.value}`
1006 : expr.callee.property.name;
1007 const hook = this.hooks.evaluateCallExpressionMember.get(property);
1008 if (hook !== undefined) {
1009 return hook.call(expr, param);
1010 }
1011 });
1012 this.hooks.evaluateCallExpressionMember
1013 .for("indexOf")
1014 .tap("JavascriptParser", (expr, param) => {
1015 if (!param.isString()) return;
1016 if (expr.arguments.length === 0) return;
1017 const [arg1, arg2] = expr.arguments;
1018 if (arg1.type === "SpreadElement") return;
1019 const arg1Eval = this.evaluateExpression(arg1);
1020 if (!arg1Eval.isString()) return;
1021 const arg1Value = arg1Eval.string;
1022
1023 let result;
1024 if (arg2) {
1025 if (arg2.type === "SpreadElement") return;
1026 const arg2Eval = this.evaluateExpression(arg2);
1027 if (!arg2Eval.isNumber()) return;
1028 result = param.string.indexOf(arg1Value, arg2Eval.number);
1029 } else {
1030 result = param.string.indexOf(arg1Value);
1031 }
1032 return new BasicEvaluatedExpression()
1033 .setNumber(result)
1034 .setSideEffects(param.couldHaveSideEffects())
1035 .setRange(expr.range);
1036 });
1037 this.hooks.evaluateCallExpressionMember
1038 .for("replace")
1039 .tap("JavascriptParser", (expr, param) => {
1040 if (!param.isString()) return;
1041 if (expr.arguments.length !== 2) return;
1042 if (expr.arguments[0].type === "SpreadElement") return;
1043 if (expr.arguments[1].type === "SpreadElement") return;
1044 let arg1 = this.evaluateExpression(expr.arguments[0]);
1045 let arg2 = this.evaluateExpression(expr.arguments[1]);
1046 if (!arg1.isString() && !arg1.isRegExp()) return;
1047 const arg1Value = arg1.regExp || arg1.string;
1048 if (!arg2.isString()) return;
1049 const arg2Value = arg2.string;
1050 return new BasicEvaluatedExpression()
1051 .setString(param.string.replace(arg1Value, arg2Value))
1052 .setSideEffects(param.couldHaveSideEffects())
1053 .setRange(expr.range);
1054 });
1055 ["substr", "substring", "slice"].forEach(fn => {
1056 this.hooks.evaluateCallExpressionMember
1057 .for(fn)
1058 .tap("JavascriptParser", (expr, param) => {
1059 if (!param.isString()) return;
1060 let arg1;
1061 let result,
1062 str = param.string;
1063 switch (expr.arguments.length) {
1064 case 1:
1065 if (expr.arguments[0].type === "SpreadElement") return;
1066 arg1 = this.evaluateExpression(expr.arguments[0]);
1067 if (!arg1.isNumber()) return;
1068 result = str[fn](arg1.number);
1069 break;
1070 case 2: {
1071 if (expr.arguments[0].type === "SpreadElement") return;
1072 if (expr.arguments[1].type === "SpreadElement") return;
1073 arg1 = this.evaluateExpression(expr.arguments[0]);
1074 const arg2 = this.evaluateExpression(expr.arguments[1]);
1075 if (!arg1.isNumber()) return;
1076 if (!arg2.isNumber()) return;
1077 result = str[fn](arg1.number, arg2.number);
1078 break;
1079 }
1080 default:
1081 return;
1082 }
1083 return new BasicEvaluatedExpression()
1084 .setString(result)
1085 .setSideEffects(param.couldHaveSideEffects())
1086 .setRange(expr.range);
1087 });
1088 });
1089
1090 /**
1091 * @param {"cooked" | "raw"} kind kind of values to get
1092 * @param {TemplateLiteralNode} templateLiteralExpr TemplateLiteral expr
1093 * @returns {{quasis: BasicEvaluatedExpression[], parts: BasicEvaluatedExpression[]}} Simplified template
1094 */
1095 const getSimplifiedTemplateResult = (kind, templateLiteralExpr) => {
1096 /** @type {BasicEvaluatedExpression[]} */
1097 const quasis = [];
1098 /** @type {BasicEvaluatedExpression[]} */
1099 const parts = [];
1100
1101 for (let i = 0; i < templateLiteralExpr.quasis.length; i++) {
1102 const quasiExpr = templateLiteralExpr.quasis[i];
1103 const quasi = quasiExpr.value[kind];
1104
1105 if (i > 0) {
1106 const prevExpr = parts[parts.length - 1];
1107 const expr = this.evaluateExpression(
1108 templateLiteralExpr.expressions[i - 1]
1109 );
1110 const exprAsString = expr.asString();
1111 if (
1112 typeof exprAsString === "string" &&
1113 !expr.couldHaveSideEffects()
1114 ) {
1115 // We can merge quasi + expr + quasi when expr
1116 // is a const string
1117
1118 prevExpr.setString(prevExpr.string + exprAsString + quasi);
1119 prevExpr.setRange([prevExpr.range[0], quasiExpr.range[1]]);
1120 // We unset the expression as it doesn't match to a single expression
1121 prevExpr.setExpression(undefined);
1122 continue;
1123 }
1124 parts.push(expr);
1125 }
1126
1127 const part = new BasicEvaluatedExpression()
1128 .setString(quasi)
1129 .setRange(quasiExpr.range)
1130 .setExpression(quasiExpr);
1131 quasis.push(part);
1132 parts.push(part);
1133 }
1134 return {
1135 quasis,
1136 parts
1137 };
1138 };
1139
1140 this.hooks.evaluate
1141 .for("TemplateLiteral")
1142 .tap("JavascriptParser", _node => {
1143 const node = /** @type {TemplateLiteralNode} */ (_node);
1144
1145 const { quasis, parts } = getSimplifiedTemplateResult("cooked", node);
1146 if (parts.length === 1) {
1147 return parts[0].setRange(node.range);
1148 }
1149 return new BasicEvaluatedExpression()
1150 .setTemplateString(quasis, parts, "cooked")
1151 .setRange(node.range);
1152 });
1153 this.hooks.evaluate
1154 .for("TaggedTemplateExpression")
1155 .tap("JavascriptParser", _node => {
1156 const node = /** @type {TaggedTemplateExpressionNode} */ (_node);
1157 const tag = this.evaluateExpression(node.tag);
1158
1159 if (tag.isIdentifier() && tag.identifier !== "String.raw") return;
1160 const { quasis, parts } = getSimplifiedTemplateResult(
1161 "raw",
1162 node.quasi
1163 );
1164 return new BasicEvaluatedExpression()
1165 .setTemplateString(quasis, parts, "raw")
1166 .setRange(node.range);
1167 });
1168
1169 this.hooks.evaluateCallExpressionMember
1170 .for("concat")
1171 .tap("JavascriptParser", (expr, param) => {
1172 if (!param.isString() && !param.isWrapped()) return;
1173
1174 let stringSuffix = null;
1175 let hasUnknownParams = false;
1176 const innerExpressions = [];
1177 for (let i = expr.arguments.length - 1; i >= 0; i--) {
1178 const arg = expr.arguments[i];
1179 if (arg.type === "SpreadElement") return;
1180 const argExpr = this.evaluateExpression(arg);
1181 if (
1182 hasUnknownParams ||
1183 (!argExpr.isString() && !argExpr.isNumber())
1184 ) {
1185 hasUnknownParams = true;
1186 innerExpressions.push(argExpr);
1187 continue;
1188 }
1189
1190 const value = argExpr.isString()
1191 ? argExpr.string
1192 : "" + argExpr.number;
1193
1194 const newString = value + (stringSuffix ? stringSuffix.string : "");
1195 const newRange = [
1196 argExpr.range[0],
1197 (stringSuffix || argExpr).range[1]
1198 ];
1199 stringSuffix = new BasicEvaluatedExpression()
1200 .setString(newString)
1201 .setSideEffects(
1202 (stringSuffix && stringSuffix.couldHaveSideEffects()) ||
1203 argExpr.couldHaveSideEffects()
1204 )
1205 .setRange(newRange);
1206 }
1207
1208 if (hasUnknownParams) {
1209 const prefix = param.isString() ? param : param.prefix;
1210 const inner =
1211 param.isWrapped() && param.wrappedInnerExpressions
1212 ? param.wrappedInnerExpressions.concat(innerExpressions.reverse())
1213 : innerExpressions.reverse();
1214 return new BasicEvaluatedExpression()
1215 .setWrapped(prefix, stringSuffix, inner)
1216 .setRange(expr.range);
1217 } else if (param.isWrapped()) {
1218 const postfix = stringSuffix || param.postfix;
1219 const inner = param.wrappedInnerExpressions
1220 ? param.wrappedInnerExpressions.concat(innerExpressions.reverse())
1221 : innerExpressions.reverse();
1222 return new BasicEvaluatedExpression()
1223 .setWrapped(param.prefix, postfix, inner)
1224 .setRange(expr.range);
1225 } else {
1226 const newString =
1227 param.string + (stringSuffix ? stringSuffix.string : "");
1228 return new BasicEvaluatedExpression()
1229 .setString(newString)
1230 .setSideEffects(
1231 (stringSuffix && stringSuffix.couldHaveSideEffects()) ||
1232 param.couldHaveSideEffects()
1233 )
1234 .setRange(expr.range);
1235 }
1236 });
1237 this.hooks.evaluateCallExpressionMember
1238 .for("split")
1239 .tap("JavascriptParser", (expr, param) => {
1240 if (!param.isString()) return;
1241 if (expr.arguments.length !== 1) return;
1242 if (expr.arguments[0].type === "SpreadElement") return;
1243 let result;
1244 const arg = this.evaluateExpression(expr.arguments[0]);
1245 if (arg.isString()) {
1246 result = param.string.split(arg.string);
1247 } else if (arg.isRegExp()) {
1248 result = param.string.split(arg.regExp);
1249 } else {
1250 return;
1251 }
1252 return new BasicEvaluatedExpression()
1253 .setArray(result)
1254 .setSideEffects(param.couldHaveSideEffects())
1255 .setRange(expr.range);
1256 });
1257 this.hooks.evaluate
1258 .for("ConditionalExpression")
1259 .tap("JavascriptParser", _expr => {
1260 const expr = /** @type {ConditionalExpressionNode} */ (_expr);
1261
1262 const condition = this.evaluateExpression(expr.test);
1263 const conditionValue = condition.asBool();
1264 let res;
1265 if (conditionValue === undefined) {
1266 const consequent = this.evaluateExpression(expr.consequent);
1267 const alternate = this.evaluateExpression(expr.alternate);
1268 if (!consequent || !alternate) return;
1269 res = new BasicEvaluatedExpression();
1270 if (consequent.isConditional()) {
1271 res.setOptions(consequent.options);
1272 } else {
1273 res.setOptions([consequent]);
1274 }
1275 if (alternate.isConditional()) {
1276 res.addOptions(alternate.options);
1277 } else {
1278 res.addOptions([alternate]);
1279 }
1280 } else {
1281 res = this.evaluateExpression(
1282 conditionValue ? expr.consequent : expr.alternate
1283 );
1284 if (condition.couldHaveSideEffects()) res.setSideEffects();
1285 }
1286 res.setRange(expr.range);
1287 return res;
1288 });
1289 this.hooks.evaluate
1290 .for("ArrayExpression")
1291 .tap("JavascriptParser", _expr => {
1292 const expr = /** @type {ArrayExpressionNode} */ (_expr);
1293
1294 const items = expr.elements.map(element => {
1295 return (
1296 element !== null &&
1297 element.type !== "SpreadElement" &&
1298 this.evaluateExpression(element)
1299 );
1300 });
1301 if (!items.every(Boolean)) return;
1302 return new BasicEvaluatedExpression()
1303 .setItems(items)
1304 .setRange(expr.range);
1305 });
1306 this.hooks.evaluate
1307 .for("ChainExpression")
1308 .tap("JavascriptParser", _expr => {
1309 const expr = /** @type {ChainExpressionNode} */ (_expr);
1310 /** @type {ExpressionNode[]} */
1311 const optionalExpressionsStack = [];
1312 /** @type {ExpressionNode|SuperNode} */
1313 let next = expr.expression;
1314
1315 while (
1316 next.type === "MemberExpression" ||
1317 next.type === "CallExpression"
1318 ) {
1319 if (next.type === "MemberExpression") {
1320 if (next.optional) {
1321 // SuperNode can not be optional
1322 optionalExpressionsStack.push(
1323 /** @type {ExpressionNode} */ (next.object)
1324 );
1325 }
1326 next = next.object;
1327 } else {
1328 if (next.optional) {
1329 // SuperNode can not be optional
1330 optionalExpressionsStack.push(
1331 /** @type {ExpressionNode} */ (next.callee)
1332 );
1333 }
1334 next = next.callee;
1335 }
1336 }
1337
1338 while (optionalExpressionsStack.length > 0) {
1339 const expression = optionalExpressionsStack.pop();
1340 const evaluated = this.evaluateExpression(expression);
1341
1342 if (evaluated && evaluated.asNullish()) {
1343 return evaluated.setRange(_expr.range);
1344 }
1345 }
1346 return this.evaluateExpression(expr.expression);
1347 });
1348 }
1349
1350 getRenameIdentifier(expr) {
1351 const result = this.evaluateExpression(expr);
1352 if (result && result.isIdentifier()) {
1353 return result.identifier;
1354 }
1355 }
1356
1357 /**
1358 * @param {ClassExpressionNode | ClassDeclarationNode} classy a class node
1359 * @returns {void}
1360 */
1361 walkClass(classy) {
1362 if (classy.superClass) {
1363 if (!this.hooks.classExtendsExpression.call(classy.superClass, classy)) {
1364 this.walkExpression(classy.superClass);
1365 }
1366 }
1367 if (classy.body && classy.body.type === "ClassBody") {
1368 for (const classElement of /** @type {TODO} */ (classy.body.body)) {
1369 if (!this.hooks.classBodyElement.call(classElement, classy)) {
1370 if (classElement.computed && classElement.key) {
1371 this.walkExpression(classElement.key);
1372 }
1373 if (classElement.value) {
1374 if (
1375 !this.hooks.classBodyValue.call(
1376 classElement.value,
1377 classElement,
1378 classy
1379 )
1380 ) {
1381 const wasTopLevel = this.scope.topLevelScope;
1382 this.scope.topLevelScope = false;
1383 this.walkExpression(classElement.value);
1384 this.scope.topLevelScope = wasTopLevel;
1385 }
1386 }
1387 }
1388 }
1389 }
1390 }
1391
1392 // Pre walking iterates the scope for variable declarations
1393 preWalkStatements(statements) {
1394 for (let index = 0, len = statements.length; index < len; index++) {
1395 const statement = statements[index];
1396 this.preWalkStatement(statement);
1397 }
1398 }
1399
1400 // Block pre walking iterates the scope for block variable declarations
1401 blockPreWalkStatements(statements) {
1402 for (let index = 0, len = statements.length; index < len; index++) {
1403 const statement = statements[index];
1404 this.blockPreWalkStatement(statement);
1405 }
1406 }
1407
1408 // Walking iterates the statements and expressions and processes them
1409 walkStatements(statements) {
1410 for (let index = 0, len = statements.length; index < len; index++) {
1411 const statement = statements[index];
1412 this.walkStatement(statement);
1413 }
1414 }
1415
1416 preWalkStatement(statement) {
1417 this.statementPath.push(statement);
1418 if (this.hooks.preStatement.call(statement)) {
1419 this.prevStatement = this.statementPath.pop();
1420 return;
1421 }
1422 switch (statement.type) {
1423 case "BlockStatement":
1424 this.preWalkBlockStatement(statement);
1425 break;
1426 case "DoWhileStatement":
1427 this.preWalkDoWhileStatement(statement);
1428 break;
1429 case "ForInStatement":
1430 this.preWalkForInStatement(statement);
1431 break;
1432 case "ForOfStatement":
1433 this.preWalkForOfStatement(statement);
1434 break;
1435 case "ForStatement":
1436 this.preWalkForStatement(statement);
1437 break;
1438 case "FunctionDeclaration":
1439 this.preWalkFunctionDeclaration(statement);
1440 break;
1441 case "IfStatement":
1442 this.preWalkIfStatement(statement);
1443 break;
1444 case "LabeledStatement":
1445 this.preWalkLabeledStatement(statement);
1446 break;
1447 case "SwitchStatement":
1448 this.preWalkSwitchStatement(statement);
1449 break;
1450 case "TryStatement":
1451 this.preWalkTryStatement(statement);
1452 break;
1453 case "VariableDeclaration":
1454 this.preWalkVariableDeclaration(statement);
1455 break;
1456 case "WhileStatement":
1457 this.preWalkWhileStatement(statement);
1458 break;
1459 case "WithStatement":
1460 this.preWalkWithStatement(statement);
1461 break;
1462 }
1463 this.prevStatement = this.statementPath.pop();
1464 }
1465
1466 blockPreWalkStatement(statement) {
1467 this.statementPath.push(statement);
1468 if (this.hooks.blockPreStatement.call(statement)) {
1469 this.prevStatement = this.statementPath.pop();
1470 return;
1471 }
1472 switch (statement.type) {
1473 case "ImportDeclaration":
1474 this.blockPreWalkImportDeclaration(statement);
1475 break;
1476 case "ExportAllDeclaration":
1477 this.blockPreWalkExportAllDeclaration(statement);
1478 break;
1479 case "ExportDefaultDeclaration":
1480 this.blockPreWalkExportDefaultDeclaration(statement);
1481 break;
1482 case "ExportNamedDeclaration":
1483 this.blockPreWalkExportNamedDeclaration(statement);
1484 break;
1485 case "VariableDeclaration":
1486 this.blockPreWalkVariableDeclaration(statement);
1487 break;
1488 case "ClassDeclaration":
1489 this.blockPreWalkClassDeclaration(statement);
1490 break;
1491 }
1492 this.prevStatement = this.statementPath.pop();
1493 }
1494
1495 walkStatement(statement) {
1496 this.statementPath.push(statement);
1497 if (this.hooks.statement.call(statement) !== undefined) {
1498 this.prevStatement = this.statementPath.pop();
1499 return;
1500 }
1501 switch (statement.type) {
1502 case "BlockStatement":
1503 this.walkBlockStatement(statement);
1504 break;
1505 case "ClassDeclaration":
1506 this.walkClassDeclaration(statement);
1507 break;
1508 case "DoWhileStatement":
1509 this.walkDoWhileStatement(statement);
1510 break;
1511 case "ExportDefaultDeclaration":
1512 this.walkExportDefaultDeclaration(statement);
1513 break;
1514 case "ExportNamedDeclaration":
1515 this.walkExportNamedDeclaration(statement);
1516 break;
1517 case "ExpressionStatement":
1518 this.walkExpressionStatement(statement);
1519 break;
1520 case "ForInStatement":
1521 this.walkForInStatement(statement);
1522 break;
1523 case "ForOfStatement":
1524 this.walkForOfStatement(statement);
1525 break;
1526 case "ForStatement":
1527 this.walkForStatement(statement);
1528 break;
1529 case "FunctionDeclaration":
1530 this.walkFunctionDeclaration(statement);
1531 break;
1532 case "IfStatement":
1533 this.walkIfStatement(statement);
1534 break;
1535 case "LabeledStatement":
1536 this.walkLabeledStatement(statement);
1537 break;
1538 case "ReturnStatement":
1539 this.walkReturnStatement(statement);
1540 break;
1541 case "SwitchStatement":
1542 this.walkSwitchStatement(statement);
1543 break;
1544 case "ThrowStatement":
1545 this.walkThrowStatement(statement);
1546 break;
1547 case "TryStatement":
1548 this.walkTryStatement(statement);
1549 break;
1550 case "VariableDeclaration":
1551 this.walkVariableDeclaration(statement);
1552 break;
1553 case "WhileStatement":
1554 this.walkWhileStatement(statement);
1555 break;
1556 case "WithStatement":
1557 this.walkWithStatement(statement);
1558 break;
1559 }
1560 this.prevStatement = this.statementPath.pop();
1561 }
1562
1563 /**
1564 * Walks a statements that is nested within a parent statement
1565 * and can potentially be a non-block statement.
1566 * This enforces the nested statement to never be in ASI position.
1567 * @param {StatementNode} statement the nested statement
1568 * @returns {void}
1569 */
1570 walkNestedStatement(statement) {
1571 this.prevStatement = undefined;
1572 this.walkStatement(statement);
1573 }
1574
1575 // Real Statements
1576 preWalkBlockStatement(statement) {
1577 this.preWalkStatements(statement.body);
1578 }
1579
1580 walkBlockStatement(statement) {
1581 this.inBlockScope(() => {
1582 const body = statement.body;
1583 const prev = this.prevStatement;
1584 this.blockPreWalkStatements(body);
1585 this.prevStatement = prev;
1586 this.walkStatements(body);
1587 });
1588 }
1589
1590 walkExpressionStatement(statement) {
1591 this.walkExpression(statement.expression);
1592 }
1593
1594 preWalkIfStatement(statement) {
1595 this.preWalkStatement(statement.consequent);
1596 if (statement.alternate) {
1597 this.preWalkStatement(statement.alternate);
1598 }
1599 }
1600
1601 walkIfStatement(statement) {
1602 const result = this.hooks.statementIf.call(statement);
1603 if (result === undefined) {
1604 this.walkExpression(statement.test);
1605 this.walkNestedStatement(statement.consequent);
1606 if (statement.alternate) {
1607 this.walkNestedStatement(statement.alternate);
1608 }
1609 } else {
1610 if (result) {
1611 this.walkNestedStatement(statement.consequent);
1612 } else if (statement.alternate) {
1613 this.walkNestedStatement(statement.alternate);
1614 }
1615 }
1616 }
1617
1618 preWalkLabeledStatement(statement) {
1619 this.preWalkStatement(statement.body);
1620 }
1621
1622 walkLabeledStatement(statement) {
1623 const hook = this.hooks.label.get(statement.label.name);
1624 if (hook !== undefined) {
1625 const result = hook.call(statement);
1626 if (result === true) return;
1627 }
1628 this.walkNestedStatement(statement.body);
1629 }
1630
1631 preWalkWithStatement(statement) {
1632 this.preWalkStatement(statement.body);
1633 }
1634
1635 walkWithStatement(statement) {
1636 this.walkExpression(statement.object);
1637 this.walkNestedStatement(statement.body);
1638 }
1639
1640 preWalkSwitchStatement(statement) {
1641 this.preWalkSwitchCases(statement.cases);
1642 }
1643
1644 walkSwitchStatement(statement) {
1645 this.walkExpression(statement.discriminant);
1646 this.walkSwitchCases(statement.cases);
1647 }
1648
1649 walkTerminatingStatement(statement) {
1650 if (statement.argument) this.walkExpression(statement.argument);
1651 }
1652
1653 walkReturnStatement(statement) {
1654 this.walkTerminatingStatement(statement);
1655 }
1656
1657 walkThrowStatement(statement) {
1658 this.walkTerminatingStatement(statement);
1659 }
1660
1661 preWalkTryStatement(statement) {
1662 this.preWalkStatement(statement.block);
1663 if (statement.handler) this.preWalkCatchClause(statement.handler);
1664 if (statement.finializer) this.preWalkStatement(statement.finializer);
1665 }
1666
1667 walkTryStatement(statement) {
1668 if (this.scope.inTry) {
1669 this.walkStatement(statement.block);
1670 } else {
1671 this.scope.inTry = true;
1672 this.walkStatement(statement.block);
1673 this.scope.inTry = false;
1674 }
1675 if (statement.handler) this.walkCatchClause(statement.handler);
1676 if (statement.finalizer) this.walkStatement(statement.finalizer);
1677 }
1678
1679 preWalkWhileStatement(statement) {
1680 this.preWalkStatement(statement.body);
1681 }
1682
1683 walkWhileStatement(statement) {
1684 this.walkExpression(statement.test);
1685 this.walkNestedStatement(statement.body);
1686 }
1687
1688 preWalkDoWhileStatement(statement) {
1689 this.preWalkStatement(statement.body);
1690 }
1691
1692 walkDoWhileStatement(statement) {
1693 this.walkNestedStatement(statement.body);
1694 this.walkExpression(statement.test);
1695 }
1696
1697 preWalkForStatement(statement) {
1698 if (statement.init) {
1699 if (statement.init.type === "VariableDeclaration") {
1700 this.preWalkStatement(statement.init);
1701 }
1702 }
1703 this.preWalkStatement(statement.body);
1704 }
1705
1706 walkForStatement(statement) {
1707 this.inBlockScope(() => {
1708 if (statement.init) {
1709 if (statement.init.type === "VariableDeclaration") {
1710 this.blockPreWalkVariableDeclaration(statement.init);
1711 this.prevStatement = undefined;
1712 this.walkStatement(statement.init);
1713 } else {
1714 this.walkExpression(statement.init);
1715 }
1716 }
1717 if (statement.test) {
1718 this.walkExpression(statement.test);
1719 }
1720 if (statement.update) {
1721 this.walkExpression(statement.update);
1722 }
1723 const body = statement.body;
1724 if (body.type === "BlockStatement") {
1725 // no need to add additional scope
1726 const prev = this.prevStatement;
1727 this.blockPreWalkStatements(body.body);
1728 this.prevStatement = prev;
1729 this.walkStatements(body.body);
1730 } else {
1731 this.walkNestedStatement(body);
1732 }
1733 });
1734 }
1735
1736 preWalkForInStatement(statement) {
1737 if (statement.left.type === "VariableDeclaration") {
1738 this.preWalkVariableDeclaration(statement.left);
1739 }
1740 this.preWalkStatement(statement.body);
1741 }
1742
1743 walkForInStatement(statement) {
1744 this.inBlockScope(() => {
1745 if (statement.left.type === "VariableDeclaration") {
1746 this.blockPreWalkVariableDeclaration(statement.left);
1747 this.walkVariableDeclaration(statement.left);
1748 } else {
1749 this.walkPattern(statement.left);
1750 }
1751 this.walkExpression(statement.right);
1752 const body = statement.body;
1753 if (body.type === "BlockStatement") {
1754 // no need to add additional scope
1755 const prev = this.prevStatement;
1756 this.blockPreWalkStatements(body.body);
1757 this.prevStatement = prev;
1758 this.walkStatements(body.body);
1759 } else {
1760 this.walkNestedStatement(body);
1761 }
1762 });
1763 }
1764
1765 preWalkForOfStatement(statement) {
1766 if (statement.await && this.scope.topLevelScope === true) {
1767 this.hooks.topLevelAwait.call(statement);
1768 }
1769 if (statement.left.type === "VariableDeclaration") {
1770 this.preWalkVariableDeclaration(statement.left);
1771 }
1772 this.preWalkStatement(statement.body);
1773 }
1774
1775 walkForOfStatement(statement) {
1776 this.inBlockScope(() => {
1777 if (statement.left.type === "VariableDeclaration") {
1778 this.blockPreWalkVariableDeclaration(statement.left);
1779 this.walkVariableDeclaration(statement.left);
1780 } else {
1781 this.walkPattern(statement.left);
1782 }
1783 this.walkExpression(statement.right);
1784 const body = statement.body;
1785 if (body.type === "BlockStatement") {
1786 // no need to add additional scope
1787 const prev = this.prevStatement;
1788 this.blockPreWalkStatements(body.body);
1789 this.prevStatement = prev;
1790 this.walkStatements(body.body);
1791 } else {
1792 this.walkNestedStatement(body);
1793 }
1794 });
1795 }
1796
1797 // Declarations
1798 preWalkFunctionDeclaration(statement) {
1799 if (statement.id) {
1800 this.defineVariable(statement.id.name);
1801 }
1802 }
1803
1804 walkFunctionDeclaration(statement) {
1805 const wasTopLevel = this.scope.topLevelScope;
1806 this.scope.topLevelScope = false;
1807 this.inFunctionScope(true, statement.params, () => {
1808 for (const param of statement.params) {
1809 this.walkPattern(param);
1810 }
1811 if (statement.body.type === "BlockStatement") {
1812 this.detectMode(statement.body.body);
1813 const prev = this.prevStatement;
1814 this.preWalkStatement(statement.body);
1815 this.prevStatement = prev;
1816 this.walkStatement(statement.body);
1817 } else {
1818 this.walkExpression(statement.body);
1819 }
1820 });
1821 this.scope.topLevelScope = wasTopLevel;
1822 }
1823
1824 blockPreWalkImportDeclaration(statement) {
1825 const source = statement.source.value;
1826 this.hooks.import.call(statement, source);
1827 for (const specifier of statement.specifiers) {
1828 const name = specifier.local.name;
1829 switch (specifier.type) {
1830 case "ImportDefaultSpecifier":
1831 if (
1832 !this.hooks.importSpecifier.call(statement, source, "default", name)
1833 ) {
1834 this.defineVariable(name);
1835 }
1836 break;
1837 case "ImportSpecifier":
1838 if (
1839 !this.hooks.importSpecifier.call(
1840 statement,
1841 source,
1842 specifier.imported.name,
1843 name
1844 )
1845 ) {
1846 this.defineVariable(name);
1847 }
1848 break;
1849 case "ImportNamespaceSpecifier":
1850 if (!this.hooks.importSpecifier.call(statement, source, null, name)) {
1851 this.defineVariable(name);
1852 }
1853 break;
1854 default:
1855 this.defineVariable(name);
1856 }
1857 }
1858 }
1859
1860 enterDeclaration(declaration, onIdent) {
1861 switch (declaration.type) {
1862 case "VariableDeclaration":
1863 for (const declarator of declaration.declarations) {
1864 switch (declarator.type) {
1865 case "VariableDeclarator": {
1866 this.enterPattern(declarator.id, onIdent);
1867 break;
1868 }
1869 }
1870 }
1871 break;
1872 case "FunctionDeclaration":
1873 this.enterPattern(declaration.id, onIdent);
1874 break;
1875 case "ClassDeclaration":
1876 this.enterPattern(declaration.id, onIdent);
1877 break;
1878 }
1879 }
1880
1881 blockPreWalkExportNamedDeclaration(statement) {
1882 let source;
1883 if (statement.source) {
1884 source = statement.source.value;
1885 this.hooks.exportImport.call(statement, source);
1886 } else {
1887 this.hooks.export.call(statement);
1888 }
1889 if (statement.declaration) {
1890 if (
1891 !this.hooks.exportDeclaration.call(statement, statement.declaration)
1892 ) {
1893 const prev = this.prevStatement;
1894 this.preWalkStatement(statement.declaration);
1895 this.prevStatement = prev;
1896 this.blockPreWalkStatement(statement.declaration);
1897 let index = 0;
1898 this.enterDeclaration(statement.declaration, def => {
1899 this.hooks.exportSpecifier.call(statement, def, def, index++);
1900 });
1901 }
1902 }
1903 if (statement.specifiers) {
1904 for (
1905 let specifierIndex = 0;
1906 specifierIndex < statement.specifiers.length;
1907 specifierIndex++
1908 ) {
1909 const specifier = statement.specifiers[specifierIndex];
1910 switch (specifier.type) {
1911 case "ExportSpecifier": {
1912 const name = specifier.exported.name;
1913 if (source) {
1914 this.hooks.exportImportSpecifier.call(
1915 statement,
1916 source,
1917 specifier.local.name,
1918 name,
1919 specifierIndex
1920 );
1921 } else {
1922 this.hooks.exportSpecifier.call(
1923 statement,
1924 specifier.local.name,
1925 name,
1926 specifierIndex
1927 );
1928 }
1929 break;
1930 }
1931 }
1932 }
1933 }
1934 }
1935
1936 walkExportNamedDeclaration(statement) {
1937 if (statement.declaration) {
1938 this.walkStatement(statement.declaration);
1939 }
1940 }
1941
1942 blockPreWalkExportDefaultDeclaration(statement) {
1943 const prev = this.prevStatement;
1944 this.preWalkStatement(statement.declaration);
1945 this.prevStatement = prev;
1946 this.blockPreWalkStatement(statement.declaration);
1947 if (
1948 statement.declaration.id &&
1949 statement.declaration.type !== "FunctionExpression" &&
1950 statement.declaration.type !== "ClassExpression"
1951 ) {
1952 this.hooks.exportSpecifier.call(
1953 statement,
1954 statement.declaration.id.name,
1955 "default",
1956 undefined
1957 );
1958 }
1959 }
1960
1961 walkExportDefaultDeclaration(statement) {
1962 this.hooks.export.call(statement);
1963 if (
1964 statement.declaration.id &&
1965 statement.declaration.type !== "FunctionExpression" &&
1966 statement.declaration.type !== "ClassExpression"
1967 ) {
1968 if (
1969 !this.hooks.exportDeclaration.call(statement, statement.declaration)
1970 ) {
1971 this.walkStatement(statement.declaration);
1972 }
1973 } else {
1974 // Acorn parses `export default function() {}` as `FunctionDeclaration` and
1975 // `export default class {}` as `ClassDeclaration`, both with `id = null`.
1976 // These nodes must be treated as expressions.
1977 if (
1978 statement.declaration.type === "FunctionDeclaration" ||
1979 statement.declaration.type === "ClassDeclaration"
1980 ) {
1981 this.walkStatement(statement.declaration);
1982 } else {
1983 this.walkExpression(statement.declaration);
1984 }
1985 if (!this.hooks.exportExpression.call(statement, statement.declaration)) {
1986 this.hooks.exportSpecifier.call(
1987 statement,
1988 statement.declaration,
1989 "default",
1990 undefined
1991 );
1992 }
1993 }
1994 }
1995
1996 blockPreWalkExportAllDeclaration(statement) {
1997 const source = statement.source.value;
1998 const name = statement.exported ? statement.exported.name : null;
1999 this.hooks.exportImport.call(statement, source);
2000 this.hooks.exportImportSpecifier.call(statement, source, null, name, 0);
2001 }
2002
2003 preWalkVariableDeclaration(statement) {
2004 if (statement.kind !== "var") return;
2005 this._preWalkVariableDeclaration(statement, this.hooks.varDeclarationVar);
2006 }
2007
2008 blockPreWalkVariableDeclaration(statement) {
2009 if (statement.kind === "var") return;
2010 const hookMap =
2011 statement.kind === "const"
2012 ? this.hooks.varDeclarationConst
2013 : this.hooks.varDeclarationLet;
2014 this._preWalkVariableDeclaration(statement, hookMap);
2015 }
2016
2017 _preWalkVariableDeclaration(statement, hookMap) {
2018 for (const declarator of statement.declarations) {
2019 switch (declarator.type) {
2020 case "VariableDeclarator": {
2021 if (!this.hooks.preDeclarator.call(declarator, statement)) {
2022 this.enterPattern(declarator.id, (name, decl) => {
2023 let hook = hookMap.get(name);
2024 if (hook === undefined || !hook.call(decl)) {
2025 hook = this.hooks.varDeclaration.get(name);
2026 if (hook === undefined || !hook.call(decl)) {
2027 this.defineVariable(name);
2028 }
2029 }
2030 });
2031 }
2032 break;
2033 }
2034 }
2035 }
2036 }
2037
2038 walkVariableDeclaration(statement) {
2039 for (const declarator of statement.declarations) {
2040 switch (declarator.type) {
2041 case "VariableDeclarator": {
2042 const renameIdentifier =
2043 declarator.init && this.getRenameIdentifier(declarator.init);
2044 if (renameIdentifier && declarator.id.type === "Identifier") {
2045 const hook = this.hooks.canRename.get(renameIdentifier);
2046 if (hook !== undefined && hook.call(declarator.init)) {
2047 // renaming with "var a = b;"
2048 const hook = this.hooks.rename.get(renameIdentifier);
2049 if (hook === undefined || !hook.call(declarator.init)) {
2050 this.setVariable(declarator.id.name, renameIdentifier);
2051 }
2052 break;
2053 }
2054 }
2055 if (!this.hooks.declarator.call(declarator, statement)) {
2056 this.walkPattern(declarator.id);
2057 if (declarator.init) this.walkExpression(declarator.init);
2058 }
2059 break;
2060 }
2061 }
2062 }
2063 }
2064
2065 blockPreWalkClassDeclaration(statement) {
2066 if (statement.id) {
2067 this.defineVariable(statement.id.name);
2068 }
2069 }
2070
2071 walkClassDeclaration(statement) {
2072 this.walkClass(statement);
2073 }
2074
2075 preWalkSwitchCases(switchCases) {
2076 for (let index = 0, len = switchCases.length; index < len; index++) {
2077 const switchCase = switchCases[index];
2078 this.preWalkStatements(switchCase.consequent);
2079 }
2080 }
2081
2082 walkSwitchCases(switchCases) {
2083 this.inBlockScope(() => {
2084 const len = switchCases.length;
2085
2086 // we need to pre walk all statements first since we can have invalid code
2087 // import A from "module";
2088 // switch(1) {
2089 // case 1:
2090 // console.log(A); // should fail at runtime
2091 // case 2:
2092 // const A = 1;
2093 // }
2094 for (let index = 0; index < len; index++) {
2095 const switchCase = switchCases[index];
2096
2097 if (switchCase.consequent.length > 0) {
2098 const prev = this.prevStatement;
2099 this.blockPreWalkStatements(switchCase.consequent);
2100 this.prevStatement = prev;
2101 }
2102 }
2103
2104 for (let index = 0; index < len; index++) {
2105 const switchCase = switchCases[index];
2106
2107 if (switchCase.test) {
2108 this.walkExpression(switchCase.test);
2109 }
2110 if (switchCase.consequent.length > 0) {
2111 this.walkStatements(switchCase.consequent);
2112 }
2113 }
2114 });
2115 }
2116
2117 preWalkCatchClause(catchClause) {
2118 this.preWalkStatement(catchClause.body);
2119 }
2120
2121 walkCatchClause(catchClause) {
2122 this.inBlockScope(() => {
2123 // Error binding is optional in catch clause since ECMAScript 2019
2124 if (catchClause.param !== null) {
2125 this.enterPattern(catchClause.param, ident => {
2126 this.defineVariable(ident);
2127 });
2128 this.walkPattern(catchClause.param);
2129 }
2130 const prev = this.prevStatement;
2131 this.blockPreWalkStatement(catchClause.body);
2132 this.prevStatement = prev;
2133 this.walkStatement(catchClause.body);
2134 });
2135 }
2136
2137 walkPattern(pattern) {
2138 switch (pattern.type) {
2139 case "ArrayPattern":
2140 this.walkArrayPattern(pattern);
2141 break;
2142 case "AssignmentPattern":
2143 this.walkAssignmentPattern(pattern);
2144 break;
2145 case "MemberExpression":
2146 this.walkMemberExpression(pattern);
2147 break;
2148 case "ObjectPattern":
2149 this.walkObjectPattern(pattern);
2150 break;
2151 case "RestElement":
2152 this.walkRestElement(pattern);
2153 break;
2154 }
2155 }
2156
2157 walkAssignmentPattern(pattern) {
2158 this.walkExpression(pattern.right);
2159 this.walkPattern(pattern.left);
2160 }
2161
2162 walkObjectPattern(pattern) {
2163 for (let i = 0, len = pattern.properties.length; i < len; i++) {
2164 const prop = pattern.properties[i];
2165 if (prop) {
2166 if (prop.computed) this.walkExpression(prop.key);
2167 if (prop.value) this.walkPattern(prop.value);
2168 }
2169 }
2170 }
2171
2172 walkArrayPattern(pattern) {
2173 for (let i = 0, len = pattern.elements.length; i < len; i++) {
2174 const element = pattern.elements[i];
2175 if (element) this.walkPattern(element);
2176 }
2177 }
2178
2179 walkRestElement(pattern) {
2180 this.walkPattern(pattern.argument);
2181 }
2182
2183 walkExpressions(expressions) {
2184 for (const expression of expressions) {
2185 if (expression) {
2186 this.walkExpression(expression);
2187 }
2188 }
2189 }
2190
2191 walkExpression(expression) {
2192 switch (expression.type) {
2193 case "ArrayExpression":
2194 this.walkArrayExpression(expression);
2195 break;
2196 case "ArrowFunctionExpression":
2197 this.walkArrowFunctionExpression(expression);
2198 break;
2199 case "AssignmentExpression":
2200 this.walkAssignmentExpression(expression);
2201 break;
2202 case "AwaitExpression":
2203 this.walkAwaitExpression(expression);
2204 break;
2205 case "BinaryExpression":
2206 this.walkBinaryExpression(expression);
2207 break;
2208 case "CallExpression":
2209 this.walkCallExpression(expression);
2210 break;
2211 case "ChainExpression":
2212 this.walkChainExpression(expression);
2213 break;
2214 case "ClassExpression":
2215 this.walkClassExpression(expression);
2216 break;
2217 case "ConditionalExpression":
2218 this.walkConditionalExpression(expression);
2219 break;
2220 case "FunctionExpression":
2221 this.walkFunctionExpression(expression);
2222 break;
2223 case "Identifier":
2224 this.walkIdentifier(expression);
2225 break;
2226 case "ImportExpression":
2227 this.walkImportExpression(expression);
2228 break;
2229 case "LogicalExpression":
2230 this.walkLogicalExpression(expression);
2231 break;
2232 case "MetaProperty":
2233 this.walkMetaProperty(expression);
2234 break;
2235 case "MemberExpression":
2236 this.walkMemberExpression(expression);
2237 break;
2238 case "NewExpression":
2239 this.walkNewExpression(expression);
2240 break;
2241 case "ObjectExpression":
2242 this.walkObjectExpression(expression);
2243 break;
2244 case "SequenceExpression":
2245 this.walkSequenceExpression(expression);
2246 break;
2247 case "SpreadElement":
2248 this.walkSpreadElement(expression);
2249 break;
2250 case "TaggedTemplateExpression":
2251 this.walkTaggedTemplateExpression(expression);
2252 break;
2253 case "TemplateLiteral":
2254 this.walkTemplateLiteral(expression);
2255 break;
2256 case "ThisExpression":
2257 this.walkThisExpression(expression);
2258 break;
2259 case "UnaryExpression":
2260 this.walkUnaryExpression(expression);
2261 break;
2262 case "UpdateExpression":
2263 this.walkUpdateExpression(expression);
2264 break;
2265 case "YieldExpression":
2266 this.walkYieldExpression(expression);
2267 break;
2268 }
2269 }
2270
2271 walkAwaitExpression(expression) {
2272 if (this.scope.topLevelScope === true)
2273 this.hooks.topLevelAwait.call(expression);
2274 this.walkExpression(expression.argument);
2275 }
2276
2277 walkArrayExpression(expression) {
2278 if (expression.elements) {
2279 this.walkExpressions(expression.elements);
2280 }
2281 }
2282
2283 walkSpreadElement(expression) {
2284 if (expression.argument) {
2285 this.walkExpression(expression.argument);
2286 }
2287 }
2288
2289 walkObjectExpression(expression) {
2290 for (
2291 let propIndex = 0, len = expression.properties.length;
2292 propIndex < len;
2293 propIndex++
2294 ) {
2295 const prop = expression.properties[propIndex];
2296 this.walkProperty(prop);
2297 }
2298 }
2299
2300 walkProperty(prop) {
2301 if (prop.type === "SpreadElement") {
2302 this.walkExpression(prop.argument);
2303 return;
2304 }
2305 if (prop.computed) {
2306 this.walkExpression(prop.key);
2307 }
2308 if (prop.shorthand && prop.value && prop.value.type === "Identifier") {
2309 this.scope.inShorthand = prop.value.name;
2310 this.walkIdentifier(prop.value);
2311 this.scope.inShorthand = false;
2312 } else {
2313 this.walkExpression(prop.value);
2314 }
2315 }
2316
2317 walkFunctionExpression(expression) {
2318 const wasTopLevel = this.scope.topLevelScope;
2319 this.scope.topLevelScope = false;
2320 const scopeParams = expression.params;
2321
2322 // Add function name in scope for recursive calls
2323 if (expression.id) {
2324 scopeParams.push(expression.id.name);
2325 }
2326
2327 this.inFunctionScope(true, scopeParams, () => {
2328 for (const param of expression.params) {
2329 this.walkPattern(param);
2330 }
2331 if (expression.body.type === "BlockStatement") {
2332 this.detectMode(expression.body.body);
2333 const prev = this.prevStatement;
2334 this.preWalkStatement(expression.body);
2335 this.prevStatement = prev;
2336 this.walkStatement(expression.body);
2337 } else {
2338 this.walkExpression(expression.body);
2339 }
2340 });
2341 this.scope.topLevelScope = wasTopLevel;
2342 }
2343
2344 walkArrowFunctionExpression(expression) {
2345 const wasTopLevel = this.scope.topLevelScope;
2346 this.scope.topLevelScope = wasTopLevel ? "arrow" : false;
2347 this.inFunctionScope(false, expression.params, () => {
2348 for (const param of expression.params) {
2349 this.walkPattern(param);
2350 }
2351 if (expression.body.type === "BlockStatement") {
2352 this.detectMode(expression.body.body);
2353 const prev = this.prevStatement;
2354 this.preWalkStatement(expression.body);
2355 this.prevStatement = prev;
2356 this.walkStatement(expression.body);
2357 } else {
2358 this.walkExpression(expression.body);
2359 }
2360 });
2361 this.scope.topLevelScope = wasTopLevel;
2362 }
2363
2364 /**
2365 * @param {SequenceExpressionNode} expression the sequence
2366 */
2367 walkSequenceExpression(expression) {
2368 if (!expression.expressions) return;
2369 // We treat sequence expressions like statements when they are one statement level
2370 // This has some benefits for optimizations that only work on statement level
2371 const currentStatement = this.statementPath[this.statementPath.length - 1];
2372 if (
2373 currentStatement === expression ||
2374 (currentStatement.type === "ExpressionStatement" &&
2375 currentStatement.expression === expression)
2376 ) {
2377 const old = this.statementPath.pop();
2378 for (const expr of expression.expressions) {
2379 this.statementPath.push(expr);
2380 this.walkExpression(expr);
2381 this.statementPath.pop();
2382 }
2383 this.statementPath.push(old);
2384 } else {
2385 this.walkExpressions(expression.expressions);
2386 }
2387 }
2388
2389 walkUpdateExpression(expression) {
2390 this.walkExpression(expression.argument);
2391 }
2392
2393 walkUnaryExpression(expression) {
2394 if (expression.operator === "typeof") {
2395 const result = this.callHooksForExpression(
2396 this.hooks.typeof,
2397 expression.argument,
2398 expression
2399 );
2400 if (result === true) return;
2401 if (expression.argument.type === "ChainExpression") {
2402 const result = this.callHooksForExpression(
2403 this.hooks.typeof,
2404 expression.argument.expression,
2405 expression
2406 );
2407 if (result === true) return;
2408 }
2409 }
2410 this.walkExpression(expression.argument);
2411 }
2412
2413 walkLeftRightExpression(expression) {
2414 this.walkExpression(expression.left);
2415 this.walkExpression(expression.right);
2416 }
2417
2418 walkBinaryExpression(expression) {
2419 this.walkLeftRightExpression(expression);
2420 }
2421
2422 walkLogicalExpression(expression) {
2423 const result = this.hooks.expressionLogicalOperator.call(expression);
2424 if (result === undefined) {
2425 this.walkLeftRightExpression(expression);
2426 } else {
2427 if (result) {
2428 this.walkExpression(expression.right);
2429 }
2430 }
2431 }
2432
2433 walkAssignmentExpression(expression) {
2434 if (expression.left.type === "Identifier") {
2435 const renameIdentifier = this.getRenameIdentifier(expression.right);
2436 if (renameIdentifier) {
2437 if (
2438 this.callHooksForInfo(
2439 this.hooks.canRename,
2440 renameIdentifier,
2441 expression.right
2442 )
2443 ) {
2444 // renaming "a = b;"
2445 if (
2446 !this.callHooksForInfo(
2447 this.hooks.rename,
2448 renameIdentifier,
2449 expression.right
2450 )
2451 ) {
2452 this.setVariable(
2453 expression.left.name,
2454 this.getVariableInfo(renameIdentifier)
2455 );
2456 }
2457 return;
2458 }
2459 }
2460 this.walkExpression(expression.right);
2461 this.enterPattern(expression.left, (name, decl) => {
2462 if (!this.callHooksForName(this.hooks.assign, name, expression)) {
2463 this.walkExpression(expression.left);
2464 }
2465 });
2466 return;
2467 }
2468 if (expression.left.type.endsWith("Pattern")) {
2469 this.walkExpression(expression.right);
2470 this.enterPattern(expression.left, (name, decl) => {
2471 if (!this.callHooksForName(this.hooks.assign, name, expression)) {
2472 this.defineVariable(name);
2473 }
2474 });
2475 this.walkPattern(expression.left);
2476 } else if (expression.left.type === "MemberExpression") {
2477 const exprName = this.getMemberExpressionInfo(
2478 expression.left,
2479 ALLOWED_MEMBER_TYPES_EXPRESSION
2480 );
2481 if (exprName) {
2482 if (
2483 this.callHooksForInfo(
2484 this.hooks.assignMemberChain,
2485 exprName.rootInfo,
2486 expression,
2487 exprName.getMembers()
2488 )
2489 ) {
2490 return;
2491 }
2492 }
2493 this.walkExpression(expression.right);
2494 this.walkExpression(expression.left);
2495 } else {
2496 this.walkExpression(expression.right);
2497 this.walkExpression(expression.left);
2498 }
2499 }
2500
2501 walkConditionalExpression(expression) {
2502 const result = this.hooks.expressionConditionalOperator.call(expression);
2503 if (result === undefined) {
2504 this.walkExpression(expression.test);
2505 this.walkExpression(expression.consequent);
2506 if (expression.alternate) {
2507 this.walkExpression(expression.alternate);
2508 }
2509 } else {
2510 if (result) {
2511 this.walkExpression(expression.consequent);
2512 } else if (expression.alternate) {
2513 this.walkExpression(expression.alternate);
2514 }
2515 }
2516 }
2517
2518 walkNewExpression(expression) {
2519 const result = this.callHooksForExpression(
2520 this.hooks.new,
2521 expression.callee,
2522 expression
2523 );
2524 if (result === true) return;
2525 this.walkExpression(expression.callee);
2526 if (expression.arguments) {
2527 this.walkExpressions(expression.arguments);
2528 }
2529 }
2530
2531 walkYieldExpression(expression) {
2532 if (expression.argument) {
2533 this.walkExpression(expression.argument);
2534 }
2535 }
2536
2537 walkTemplateLiteral(expression) {
2538 if (expression.expressions) {
2539 this.walkExpressions(expression.expressions);
2540 }
2541 }
2542
2543 walkTaggedTemplateExpression(expression) {
2544 if (expression.tag) {
2545 this.walkExpression(expression.tag);
2546 }
2547 if (expression.quasi && expression.quasi.expressions) {
2548 this.walkExpressions(expression.quasi.expressions);
2549 }
2550 }
2551
2552 walkClassExpression(expression) {
2553 this.walkClass(expression);
2554 }
2555
2556 /**
2557 * @param {ChainExpressionNode} expression expression
2558 */
2559 walkChainExpression(expression) {
2560 const result = this.hooks.optionalChaining.call(expression);
2561
2562 if (result === undefined) {
2563 if (expression.expression.type === "CallExpression") {
2564 this.walkCallExpression(expression.expression);
2565 } else {
2566 this.walkMemberExpression(expression.expression);
2567 }
2568 }
2569 }
2570
2571 _walkIIFE(functionExpression, options, currentThis) {
2572 const getVarInfo = argOrThis => {
2573 const renameIdentifier = this.getRenameIdentifier(argOrThis);
2574 if (renameIdentifier) {
2575 if (
2576 this.callHooksForInfo(
2577 this.hooks.canRename,
2578 renameIdentifier,
2579 argOrThis
2580 )
2581 ) {
2582 if (
2583 !this.callHooksForInfo(
2584 this.hooks.rename,
2585 renameIdentifier,
2586 argOrThis
2587 )
2588 ) {
2589 return this.getVariableInfo(renameIdentifier);
2590 }
2591 }
2592 }
2593 this.walkExpression(argOrThis);
2594 };
2595 const { params, type } = functionExpression;
2596 const arrow = type === "ArrowFunctionExpression";
2597 const renameThis = currentThis ? getVarInfo(currentThis) : null;
2598 const varInfoForArgs = options.map(getVarInfo);
2599 const wasTopLevel = this.scope.topLevelScope;
2600 this.scope.topLevelScope = wasTopLevel && arrow ? "arrow" : false;
2601 const scopeParams = params.filter(
2602 (identifier, idx) => !varInfoForArgs[idx]
2603 );
2604
2605 // Add function name in scope for recursive calls
2606 if (functionExpression.id) {
2607 scopeParams.push(functionExpression.id.name);
2608 }
2609
2610 this.inFunctionScope(true, scopeParams, () => {
2611 if (renameThis && !arrow) {
2612 this.setVariable("this", renameThis);
2613 }
2614 for (let i = 0; i < varInfoForArgs.length; i++) {
2615 const varInfo = varInfoForArgs[i];
2616 if (!varInfo) continue;
2617 if (!params[i] || params[i].type !== "Identifier") continue;
2618 this.setVariable(params[i].name, varInfo);
2619 }
2620 if (functionExpression.body.type === "BlockStatement") {
2621 this.detectMode(functionExpression.body.body);
2622 const prev = this.prevStatement;
2623 this.preWalkStatement(functionExpression.body);
2624 this.prevStatement = prev;
2625 this.walkStatement(functionExpression.body);
2626 } else {
2627 this.walkExpression(functionExpression.body);
2628 }
2629 });
2630 this.scope.topLevelScope = wasTopLevel;
2631 }
2632
2633 walkImportExpression(expression) {
2634 let result = this.hooks.importCall.call(expression);
2635 if (result === true) return;
2636
2637 this.walkExpression(expression.source);
2638 }
2639
2640 walkCallExpression(expression) {
2641 const isSimpleFunction = fn => {
2642 return fn.params.every(p => p.type === "Identifier");
2643 };
2644 if (
2645 expression.callee.type === "MemberExpression" &&
2646 expression.callee.object.type.endsWith("FunctionExpression") &&
2647 !expression.callee.computed &&
2648 (expression.callee.property.name === "call" ||
2649 expression.callee.property.name === "bind") &&
2650 expression.arguments.length > 0 &&
2651 isSimpleFunction(expression.callee.object)
2652 ) {
2653 // (function(…) { }.call/bind(?, …))
2654 this._walkIIFE(
2655 expression.callee.object,
2656 expression.arguments.slice(1),
2657 expression.arguments[0]
2658 );
2659 } else if (
2660 expression.callee.type.endsWith("FunctionExpression") &&
2661 isSimpleFunction(expression.callee)
2662 ) {
2663 // (function(…) { }(…))
2664 this._walkIIFE(expression.callee, expression.arguments, null);
2665 } else {
2666 if (expression.callee.type === "MemberExpression") {
2667 const exprInfo = this.getMemberExpressionInfo(
2668 expression.callee,
2669 ALLOWED_MEMBER_TYPES_CALL_EXPRESSION
2670 );
2671 if (exprInfo && exprInfo.type === "call") {
2672 const result = this.callHooksForInfo(
2673 this.hooks.callMemberChainOfCallMemberChain,
2674 exprInfo.rootInfo,
2675 expression,
2676 exprInfo.getCalleeMembers(),
2677 exprInfo.call,
2678 exprInfo.getMembers()
2679 );
2680 if (result === true) return;
2681 }
2682 }
2683 const callee = this.evaluateExpression(expression.callee);
2684 if (callee.isIdentifier()) {
2685 const result1 = this.callHooksForInfo(
2686 this.hooks.callMemberChain,
2687 callee.rootInfo,
2688 expression,
2689 callee.getMembers()
2690 );
2691 if (result1 === true) return;
2692 const result2 = this.callHooksForInfo(
2693 this.hooks.call,
2694 callee.identifier,
2695 expression
2696 );
2697 if (result2 === true) return;
2698 }
2699
2700 if (expression.callee) {
2701 if (expression.callee.type === "MemberExpression") {
2702 // because of call context we need to walk the call context as expression
2703 this.walkExpression(expression.callee.object);
2704 if (expression.callee.computed === true)
2705 this.walkExpression(expression.callee.property);
2706 } else {
2707 this.walkExpression(expression.callee);
2708 }
2709 }
2710 if (expression.arguments) this.walkExpressions(expression.arguments);
2711 }
2712 }
2713
2714 walkMemberExpression(expression) {
2715 const exprInfo = this.getMemberExpressionInfo(
2716 expression,
2717 ALLOWED_MEMBER_TYPES_ALL
2718 );
2719 if (exprInfo) {
2720 switch (exprInfo.type) {
2721 case "expression": {
2722 const result1 = this.callHooksForInfo(
2723 this.hooks.expression,
2724 exprInfo.name,
2725 expression
2726 );
2727 if (result1 === true) return;
2728 const members = exprInfo.getMembers();
2729 const result2 = this.callHooksForInfo(
2730 this.hooks.expressionMemberChain,
2731 exprInfo.rootInfo,
2732 expression,
2733 members
2734 );
2735 if (result2 === true) return;
2736 this.walkMemberExpressionWithExpressionName(
2737 expression,
2738 exprInfo.name,
2739 exprInfo.rootInfo,
2740 members.slice(),
2741 () =>
2742 this.callHooksForInfo(
2743 this.hooks.unhandledExpressionMemberChain,
2744 exprInfo.rootInfo,
2745 expression,
2746 members
2747 )
2748 );
2749 return;
2750 }
2751 case "call": {
2752 const result = this.callHooksForInfo(
2753 this.hooks.memberChainOfCallMemberChain,
2754 exprInfo.rootInfo,
2755 expression,
2756 exprInfo.getCalleeMembers(),
2757 exprInfo.call,
2758 exprInfo.getMembers()
2759 );
2760 if (result === true) return;
2761 // Fast skip over the member chain as we already called memberChainOfCallMemberChain
2762 // and call computed property are literals anyway
2763 this.walkExpression(exprInfo.call);
2764 return;
2765 }
2766 }
2767 }
2768 this.walkExpression(expression.object);
2769 if (expression.computed === true) this.walkExpression(expression.property);
2770 }
2771
2772 walkMemberExpressionWithExpressionName(
2773 expression,
2774 name,
2775 rootInfo,
2776 members,
2777 onUnhandled
2778 ) {
2779 if (expression.object.type === "MemberExpression") {
2780 // optimize the case where expression.object is a MemberExpression too.
2781 // we can keep info here when calling walkMemberExpression directly
2782 const property =
2783 expression.property.name || `${expression.property.value}`;
2784 name = name.slice(0, -property.length - 1);
2785 members.pop();
2786 const result = this.callHooksForInfo(
2787 this.hooks.expression,
2788 name,
2789 expression.object
2790 );
2791 if (result === true) return;
2792 this.walkMemberExpressionWithExpressionName(
2793 expression.object,
2794 name,
2795 rootInfo,
2796 members,
2797 onUnhandled
2798 );
2799 } else if (!onUnhandled || !onUnhandled()) {
2800 this.walkExpression(expression.object);
2801 }
2802 if (expression.computed === true) this.walkExpression(expression.property);
2803 }
2804
2805 walkThisExpression(expression) {
2806 this.callHooksForName(this.hooks.expression, "this", expression);
2807 }
2808
2809 walkIdentifier(expression) {
2810 this.callHooksForName(this.hooks.expression, expression.name, expression);
2811 }
2812
2813 /**
2814 * @param {MetaPropertyNode} metaProperty meta property
2815 */
2816 walkMetaProperty(metaProperty) {
2817 this.hooks.expression.for(getRootName(metaProperty)).call(metaProperty);
2818 }
2819
2820 callHooksForExpression(hookMap, expr, ...args) {
2821 return this.callHooksForExpressionWithFallback(
2822 hookMap,
2823 expr,
2824 undefined,
2825 undefined,
2826 ...args
2827 );
2828 }
2829
2830 /**
2831 * @template T
2832 * @template R
2833 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
2834 * @param {MemberExpressionNode} expr expression info
2835 * @param {function(string, string | ScopeInfo | VariableInfo, function(): string[]): any} fallback callback when variable in not handled by hooks
2836 * @param {function(string): any} defined callback when variable is defined
2837 * @param {AsArray<T>} args args for the hook
2838 * @returns {R} result of hook
2839 */
2840 callHooksForExpressionWithFallback(
2841 hookMap,
2842 expr,
2843 fallback,
2844 defined,
2845 ...args
2846 ) {
2847 const exprName = this.getMemberExpressionInfo(
2848 expr,
2849 ALLOWED_MEMBER_TYPES_EXPRESSION
2850 );
2851 if (exprName !== undefined) {
2852 const members = exprName.getMembers();
2853 return this.callHooksForInfoWithFallback(
2854 hookMap,
2855 members.length === 0 ? exprName.rootInfo : exprName.name,
2856 fallback &&
2857 (name => fallback(name, exprName.rootInfo, exprName.getMembers)),
2858 defined && (() => defined(exprName.name)),
2859 ...args
2860 );
2861 }
2862 }
2863
2864 /**
2865 * @template T
2866 * @template R
2867 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
2868 * @param {string} name key in map
2869 * @param {AsArray<T>} args args for the hook
2870 * @returns {R} result of hook
2871 */
2872 callHooksForName(hookMap, name, ...args) {
2873 return this.callHooksForNameWithFallback(
2874 hookMap,
2875 name,
2876 undefined,
2877 undefined,
2878 ...args
2879 );
2880 }
2881
2882 /**
2883 * @template T
2884 * @template R
2885 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks that should be called
2886 * @param {ExportedVariableInfo} info variable info
2887 * @param {AsArray<T>} args args for the hook
2888 * @returns {R} result of hook
2889 */
2890 callHooksForInfo(hookMap, info, ...args) {
2891 return this.callHooksForInfoWithFallback(
2892 hookMap,
2893 info,
2894 undefined,
2895 undefined,
2896 ...args
2897 );
2898 }
2899
2900 /**
2901 * @template T
2902 * @template R
2903 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
2904 * @param {ExportedVariableInfo} info variable info
2905 * @param {function(string): any} fallback callback when variable in not handled by hooks
2906 * @param {function(): any} defined callback when variable is defined
2907 * @param {AsArray<T>} args args for the hook
2908 * @returns {R} result of hook
2909 */
2910 callHooksForInfoWithFallback(hookMap, info, fallback, defined, ...args) {
2911 let name;
2912 if (typeof info === "string") {
2913 name = info;
2914 } else {
2915 if (!(info instanceof VariableInfo)) {
2916 if (defined !== undefined) {
2917 return defined();
2918 }
2919 return;
2920 }
2921 let tagInfo = info.tagInfo;
2922 while (tagInfo !== undefined) {
2923 const hook = hookMap.get(tagInfo.tag);
2924 if (hook !== undefined) {
2925 this.currentTagData = tagInfo.data;
2926 const result = hook.call(...args);
2927 this.currentTagData = undefined;
2928 if (result !== undefined) return result;
2929 }
2930 tagInfo = tagInfo.next;
2931 }
2932 if (info.freeName === true) {
2933 if (defined !== undefined) {
2934 return defined();
2935 }
2936 return;
2937 }
2938 name = info.freeName;
2939 }
2940 const hook = hookMap.get(name);
2941 if (hook !== undefined) {
2942 const result = hook.call(...args);
2943 if (result !== undefined) return result;
2944 }
2945 if (fallback !== undefined) {
2946 return fallback(name);
2947 }
2948 }
2949
2950 /**
2951 * @template T
2952 * @template R
2953 * @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
2954 * @param {string} name key in map
2955 * @param {function(string): any} fallback callback when variable in not handled by hooks
2956 * @param {function(): any} defined callback when variable is defined
2957 * @param {AsArray<T>} args args for the hook
2958 * @returns {R} result of hook
2959 */
2960 callHooksForNameWithFallback(hookMap, name, fallback, defined, ...args) {
2961 return this.callHooksForInfoWithFallback(
2962 hookMap,
2963 this.getVariableInfo(name),
2964 fallback,
2965 defined,
2966 ...args
2967 );
2968 }
2969
2970 /**
2971 * @deprecated
2972 * @param {any} params scope params
2973 * @param {function(): void} fn inner function
2974 * @returns {void}
2975 */
2976 inScope(params, fn) {
2977 const oldScope = this.scope;
2978 this.scope = {
2979 topLevelScope: oldScope.topLevelScope,
2980 inTry: false,
2981 inShorthand: false,
2982 isStrict: oldScope.isStrict,
2983 isAsmJs: oldScope.isAsmJs,
2984 definitions: oldScope.definitions.createChild()
2985 };
2986
2987 this.undefineVariable("this");
2988
2989 this.enterPatterns(params, (ident, pattern) => {
2990 this.defineVariable(ident);
2991 });
2992
2993 fn();
2994
2995 this.scope = oldScope;
2996 }
2997
2998 inFunctionScope(hasThis, params, fn) {
2999 const oldScope = this.scope;
3000 this.scope = {
3001 topLevelScope: oldScope.topLevelScope,
3002 inTry: false,
3003 inShorthand: false,
3004 isStrict: oldScope.isStrict,
3005 isAsmJs: oldScope.isAsmJs,
3006 definitions: oldScope.definitions.createChild()
3007 };
3008
3009 if (hasThis) {
3010 this.undefineVariable("this");
3011 }
3012
3013 this.enterPatterns(params, (ident, pattern) => {
3014 this.defineVariable(ident);
3015 });
3016
3017 fn();
3018
3019 this.scope = oldScope;
3020 }
3021
3022 inBlockScope(fn) {
3023 const oldScope = this.scope;
3024 this.scope = {
3025 topLevelScope: oldScope.topLevelScope,
3026 inTry: oldScope.inTry,
3027 inShorthand: false,
3028 isStrict: oldScope.isStrict,
3029 isAsmJs: oldScope.isAsmJs,
3030 definitions: oldScope.definitions.createChild()
3031 };
3032
3033 fn();
3034
3035 this.scope = oldScope;
3036 }
3037
3038 detectMode(statements) {
3039 const isLiteral =
3040 statements.length >= 1 &&
3041 statements[0].type === "ExpressionStatement" &&
3042 statements[0].expression.type === "Literal";
3043 if (isLiteral && statements[0].expression.value === "use strict") {
3044 this.scope.isStrict = true;
3045 }
3046 if (isLiteral && statements[0].expression.value === "use asm") {
3047 this.scope.isAsmJs = true;
3048 }
3049 }
3050
3051 enterPatterns(patterns, onIdent) {
3052 for (const pattern of patterns) {
3053 if (typeof pattern !== "string") {
3054 this.enterPattern(pattern, onIdent);
3055 } else if (pattern) {
3056 onIdent(pattern);
3057 }
3058 }
3059 }
3060
3061 enterPattern(pattern, onIdent) {
3062 if (!pattern) return;
3063 switch (pattern.type) {
3064 case "ArrayPattern":
3065 this.enterArrayPattern(pattern, onIdent);
3066 break;
3067 case "AssignmentPattern":
3068 this.enterAssignmentPattern(pattern, onIdent);
3069 break;
3070 case "Identifier":
3071 this.enterIdentifier(pattern, onIdent);
3072 break;
3073 case "ObjectPattern":
3074 this.enterObjectPattern(pattern, onIdent);
3075 break;
3076 case "RestElement":
3077 this.enterRestElement(pattern, onIdent);
3078 break;
3079 case "Property":
3080 if (pattern.shorthand && pattern.value.type === "Identifier") {
3081 this.scope.inShorthand = pattern.value.name;
3082 this.enterIdentifier(pattern.value, onIdent);
3083 this.scope.inShorthand = false;
3084 } else {
3085 this.enterPattern(pattern.value, onIdent);
3086 }
3087 break;
3088 }
3089 }
3090
3091 enterIdentifier(pattern, onIdent) {
3092 if (!this.callHooksForName(this.hooks.pattern, pattern.name, pattern)) {
3093 onIdent(pattern.name, pattern);
3094 }
3095 }
3096
3097 enterObjectPattern(pattern, onIdent) {
3098 for (
3099 let propIndex = 0, len = pattern.properties.length;
3100 propIndex < len;
3101 propIndex++
3102 ) {
3103 const prop = pattern.properties[propIndex];
3104 this.enterPattern(prop, onIdent);
3105 }
3106 }
3107
3108 enterArrayPattern(pattern, onIdent) {
3109 for (
3110 let elementIndex = 0, len = pattern.elements.length;
3111 elementIndex < len;
3112 elementIndex++
3113 ) {
3114 const element = pattern.elements[elementIndex];
3115 this.enterPattern(element, onIdent);
3116 }
3117 }
3118
3119 enterRestElement(pattern, onIdent) {
3120 this.enterPattern(pattern.argument, onIdent);
3121 }
3122
3123 enterAssignmentPattern(pattern, onIdent) {
3124 this.enterPattern(pattern.left, onIdent);
3125 }
3126
3127 /**
3128 * @param {ExpressionNode} expression expression node
3129 * @returns {BasicEvaluatedExpression | undefined} evaluation result
3130 */
3131 evaluateExpression(expression) {
3132 try {
3133 const hook = this.hooks.evaluate.get(expression.type);
3134 if (hook !== undefined) {
3135 const result = hook.call(expression);
3136 if (result !== undefined) {
3137 if (result) {
3138 result.setExpression(expression);
3139 }
3140 return result;
3141 }
3142 }
3143 } catch (e) {
3144 console.warn(e);
3145 // ignore error
3146 }
3147 return new BasicEvaluatedExpression()
3148 .setRange(expression.range)
3149 .setExpression(expression);
3150 }
3151
3152 parseString(expression) {
3153 switch (expression.type) {
3154 case "BinaryExpression":
3155 if (expression.operator === "+") {
3156 return (
3157 this.parseString(expression.left) +
3158 this.parseString(expression.right)
3159 );
3160 }
3161 break;
3162 case "Literal":
3163 return expression.value + "";
3164 }
3165 throw new Error(
3166 expression.type + " is not supported as parameter for require"
3167 );
3168 }
3169
3170 parseCalculatedString(expression) {
3171 switch (expression.type) {
3172 case "BinaryExpression":
3173 if (expression.operator === "+") {
3174 const left = this.parseCalculatedString(expression.left);
3175 const right = this.parseCalculatedString(expression.right);
3176 if (left.code) {
3177 return {
3178 range: left.range,
3179 value: left.value,
3180 code: true,
3181 conditional: false
3182 };
3183 } else if (right.code) {
3184 return {
3185 range: [
3186 left.range[0],
3187 right.range ? right.range[1] : left.range[1]
3188 ],
3189 value: left.value + right.value,
3190 code: true,
3191 conditional: false
3192 };
3193 } else {
3194 return {
3195 range: [left.range[0], right.range[1]],
3196 value: left.value + right.value,
3197 code: false,
3198 conditional: false
3199 };
3200 }
3201 }
3202 break;
3203 case "ConditionalExpression": {
3204 const consequent = this.parseCalculatedString(expression.consequent);
3205 const alternate = this.parseCalculatedString(expression.alternate);
3206 const items = [];
3207 if (consequent.conditional) {
3208 items.push(...consequent.conditional);
3209 } else if (!consequent.code) {
3210 items.push(consequent);
3211 } else {
3212 break;
3213 }
3214 if (alternate.conditional) {
3215 items.push(...alternate.conditional);
3216 } else if (!alternate.code) {
3217 items.push(alternate);
3218 } else {
3219 break;
3220 }
3221 return {
3222 range: undefined,
3223 value: "",
3224 code: true,
3225 conditional: items
3226 };
3227 }
3228 case "Literal":
3229 return {
3230 range: expression.range,
3231 value: expression.value + "",
3232 code: false,
3233 conditional: false
3234 };
3235 }
3236 return {
3237 range: undefined,
3238 value: "",
3239 code: true,
3240 conditional: false
3241 };
3242 }
3243
3244 /**
3245 * @param {string | Buffer | PreparsedAst} source the source to parse
3246 * @param {ParserState} state the parser state
3247 * @returns {ParserState} the parser state
3248 */
3249 parse(source, state) {
3250 let ast;
3251 let comments;
3252 const semicolons = new Set();
3253 if (source === null) {
3254 throw new Error("source must not be null");
3255 }
3256 if (Buffer.isBuffer(source)) {
3257 source = source.toString("utf-8");
3258 }
3259 if (typeof source === "object") {
3260 ast = /** @type {ProgramNode} */ (source);
3261 comments = source.comments;
3262 } else {
3263 comments = [];
3264 ast = JavascriptParser._parse(source, {
3265 sourceType: this.sourceType,
3266 onComment: comments,
3267 onInsertedSemicolon: pos => semicolons.add(pos)
3268 });
3269 }
3270
3271 const oldScope = this.scope;
3272 const oldState = this.state;
3273 const oldComments = this.comments;
3274 const oldSemicolons = this.semicolons;
3275 const oldStatementPath = this.statementPath;
3276 const oldPrevStatement = this.prevStatement;
3277 this.scope = {
3278 topLevelScope: true,
3279 inTry: false,
3280 inShorthand: false,
3281 isStrict: false,
3282 isAsmJs: false,
3283 definitions: new StackedMap()
3284 };
3285 /** @type {ParserState} */
3286 this.state = state;
3287 this.comments = comments;
3288 this.semicolons = semicolons;
3289 this.statementPath = [];
3290 this.prevStatement = undefined;
3291 if (this.hooks.program.call(ast, comments) === undefined) {
3292 this.detectMode(ast.body);
3293 this.preWalkStatements(ast.body);
3294 this.prevStatement = undefined;
3295 this.blockPreWalkStatements(ast.body);
3296 this.prevStatement = undefined;
3297 this.walkStatements(ast.body);
3298 }
3299 this.hooks.finish.call(ast, comments);
3300 this.scope = oldScope;
3301 /** @type {ParserState} */
3302 this.state = oldState;
3303 this.comments = oldComments;
3304 this.semicolons = oldSemicolons;
3305 this.statementPath = oldStatementPath;
3306 this.prevStatement = oldPrevStatement;
3307 return state;
3308 }
3309
3310 evaluate(source) {
3311 const ast = JavascriptParser._parse("(" + source + ")", {
3312 sourceType: this.sourceType,
3313 locations: false
3314 });
3315 if (ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement") {
3316 throw new Error("evaluate: Source is not a expression");
3317 }
3318 return this.evaluateExpression(ast.body[0].expression);
3319 }
3320
3321 /**
3322 * @param {ExpressionNode | DeclarationNode | PrivateIdentifierNode | null | undefined} expr an expression
3323 * @param {number} commentsStartPos source position from which annotation comments are checked
3324 * @returns {boolean} true, when the expression is pure
3325 */
3326 isPure(expr, commentsStartPos) {
3327 if (!expr) return true;
3328 const result = this.hooks.isPure
3329 .for(expr.type)
3330 .call(expr, commentsStartPos);
3331 if (typeof result === "boolean") return result;
3332 switch (expr.type) {
3333 case "ClassDeclaration":
3334 case "ClassExpression": {
3335 if (expr.body.type !== "ClassBody") return false;
3336 if (expr.superClass && !this.isPure(expr.superClass, expr.range[0])) {
3337 return false;
3338 }
3339 const items = /** @type {(MethodDefinitionNode | PropertyDefinitionNode)[]} */ (expr
3340 .body.body);
3341 return items.every(
3342 item =>
3343 (!item.computed ||
3344 !item.key ||
3345 this.isPure(item.key, item.range[0])) &&
3346 (!item.static ||
3347 !item.value ||
3348 this.isPure(
3349 item.value,
3350 item.key ? item.key.range[1] : item.range[0]
3351 ))
3352 );
3353 }
3354
3355 case "FunctionDeclaration":
3356 case "FunctionExpression":
3357 case "ArrowFunctionExpression":
3358 case "Literal":
3359 case "PrivateIdentifier":
3360 return true;
3361
3362 case "VariableDeclaration":
3363 return expr.declarations.every(decl =>
3364 this.isPure(decl.init, decl.range[0])
3365 );
3366
3367 case "ConditionalExpression":
3368 return (
3369 this.isPure(expr.test, commentsStartPos) &&
3370 this.isPure(expr.consequent, expr.test.range[1]) &&
3371 this.isPure(expr.alternate, expr.consequent.range[1])
3372 );
3373
3374 case "SequenceExpression":
3375 return expr.expressions.every(expr => {
3376 const pureFlag = this.isPure(expr, commentsStartPos);
3377 commentsStartPos = expr.range[1];
3378 return pureFlag;
3379 });
3380
3381 case "CallExpression": {
3382 const pureFlag =
3383 expr.range[0] - commentsStartPos > 12 &&
3384 this.getComments([commentsStartPos, expr.range[0]]).some(
3385 comment =>
3386 comment.type === "Block" &&
3387 /^\s*(#|@)__PURE__\s*$/.test(comment.value)
3388 );
3389 if (!pureFlag) return false;
3390 commentsStartPos = expr.callee.range[1];
3391 return expr.arguments.every(arg => {
3392 if (arg.type === "SpreadElement") return false;
3393 const pureFlag = this.isPure(arg, commentsStartPos);
3394 commentsStartPos = arg.range[1];
3395 return pureFlag;
3396 });
3397 }
3398 }
3399 const evaluated = this.evaluateExpression(expr);
3400 return !evaluated.couldHaveSideEffects();
3401 }
3402
3403 getComments(range) {
3404 const [rangeStart, rangeEnd] = range;
3405 const compare = (comment, needle) => comment.range[0] - needle;
3406 let idx = binarySearchBounds.ge(this.comments, rangeStart, compare);
3407 let commentsInRange = [];
3408 while (this.comments[idx] && this.comments[idx].range[1] <= rangeEnd) {
3409 commentsInRange.push(this.comments[idx]);
3410 idx++;
3411 }
3412
3413 return commentsInRange;
3414 }
3415
3416 /**
3417 * @param {number} pos source code position
3418 * @returns {boolean} true when a semicolon has been inserted before this position, false if not
3419 */
3420 isAsiPosition(pos) {
3421 const currentStatement = this.statementPath[this.statementPath.length - 1];
3422 if (currentStatement === undefined) throw new Error("Not in statement");
3423 return (
3424 // Either asking directly for the end position of the current statement
3425 (currentStatement.range[1] === pos && this.semicolons.has(pos)) ||
3426 // Or asking for the start position of the current statement,
3427 // here we have to check multiple things
3428 (currentStatement.range[0] === pos &&
3429 // is there a previous statement which might be relevant?
3430 this.prevStatement !== undefined &&
3431 // is the end position of the previous statement an ASI position?
3432 this.semicolons.has(this.prevStatement.range[1]))
3433 );
3434 }
3435
3436 /**
3437 * @param {number} pos source code position
3438 * @returns {void}
3439 */
3440 unsetAsiPosition(pos) {
3441 this.semicolons.delete(pos);
3442 }
3443
3444 isStatementLevelExpression(expr) {
3445 const currentStatement = this.statementPath[this.statementPath.length - 1];
3446 return (
3447 expr === currentStatement ||
3448 (currentStatement.type === "ExpressionStatement" &&
3449 currentStatement.expression === expr)
3450 );
3451 }
3452
3453 getTagData(name, tag) {
3454 const info = this.scope.definitions.get(name);
3455 if (info instanceof VariableInfo) {
3456 let tagInfo = info.tagInfo;
3457 while (tagInfo !== undefined) {
3458 if (tagInfo.tag === tag) return tagInfo.data;
3459 tagInfo = tagInfo.next;
3460 }
3461 }
3462 }
3463
3464 tagVariable(name, tag, data) {
3465 const oldInfo = this.scope.definitions.get(name);
3466 /** @type {VariableInfo} */
3467 let newInfo;
3468 if (oldInfo === undefined) {
3469 newInfo = new VariableInfo(this.scope, name, {
3470 tag,
3471 data,
3472 next: undefined
3473 });
3474 } else if (oldInfo instanceof VariableInfo) {
3475 newInfo = new VariableInfo(oldInfo.declaredScope, oldInfo.freeName, {
3476 tag,
3477 data,
3478 next: oldInfo.tagInfo
3479 });
3480 } else {
3481 newInfo = new VariableInfo(oldInfo, true, {
3482 tag,
3483 data,
3484 next: undefined
3485 });
3486 }
3487 this.scope.definitions.set(name, newInfo);
3488 }
3489
3490 defineVariable(name) {
3491 const oldInfo = this.scope.definitions.get(name);
3492 // Don't redefine variable in same scope to keep existing tags
3493 if (oldInfo instanceof VariableInfo && oldInfo.declaredScope === this.scope)
3494 return;
3495 this.scope.definitions.set(name, this.scope);
3496 }
3497
3498 undefineVariable(name) {
3499 this.scope.definitions.delete(name);
3500 }
3501
3502 isVariableDefined(name) {
3503 const info = this.scope.definitions.get(name);
3504 if (info === undefined) return false;
3505 if (info instanceof VariableInfo) {
3506 return info.freeName === true;
3507 }
3508 return true;
3509 }
3510
3511 /**
3512 * @param {string} name variable name
3513 * @returns {ExportedVariableInfo} info for this variable
3514 */
3515 getVariableInfo(name) {
3516 const value = this.scope.definitions.get(name);
3517 if (value === undefined) {
3518 return name;
3519 } else {
3520 return value;
3521 }
3522 }
3523
3524 /**
3525 * @param {string} name variable name
3526 * @param {ExportedVariableInfo} variableInfo new info for this variable
3527 * @returns {void}
3528 */
3529 setVariable(name, variableInfo) {
3530 if (typeof variableInfo === "string") {
3531 if (variableInfo === name) {
3532 this.scope.definitions.delete(name);
3533 } else {
3534 this.scope.definitions.set(
3535 name,
3536 new VariableInfo(this.scope, variableInfo, undefined)
3537 );
3538 }
3539 } else {
3540 this.scope.definitions.set(name, variableInfo);
3541 }
3542 }
3543
3544 parseCommentOptions(range) {
3545 const comments = this.getComments(range);
3546 if (comments.length === 0) {
3547 return EMPTY_COMMENT_OPTIONS;
3548 }
3549 let options = {};
3550 let errors = [];
3551 for (const comment of comments) {
3552 const { value } = comment;
3553 if (value && webpackCommentRegExp.test(value)) {
3554 // try compile only if webpack options comment is present
3555 try {
3556 const val = vm.runInNewContext(`(function(){return {${value}};})()`);
3557 Object.assign(options, val);
3558 } catch (e) {
3559 e.comment = comment;
3560 errors.push(e);
3561 }
3562 }
3563 }
3564 return { options, errors };
3565 }
3566
3567 /**
3568 * @param {MemberExpressionNode} expression a member expression
3569 * @returns {{ members: string[], object: ExpressionNode | SuperNode }} member names (reverse order) and remaining object
3570 */
3571 extractMemberExpressionChain(expression) {
3572 /** @type {AnyNode} */
3573 let expr = expression;
3574 const members = [];
3575 while (expr.type === "MemberExpression") {
3576 if (expr.computed) {
3577 if (expr.property.type !== "Literal") break;
3578 members.push(`${expr.property.value}`);
3579 } else {
3580 if (expr.property.type !== "Identifier") break;
3581 members.push(expr.property.name);
3582 }
3583 expr = expr.object;
3584 }
3585 return {
3586 members,
3587 object: expr
3588 };
3589 }
3590
3591 /**
3592 * @param {string} varName variable name
3593 * @returns {{name: string, info: VariableInfo | string}} name of the free variable and variable info for that
3594 */
3595 getFreeInfoFromVariable(varName) {
3596 const info = this.getVariableInfo(varName);
3597 let name;
3598 if (info instanceof VariableInfo) {
3599 name = info.freeName;
3600 if (typeof name !== "string") return undefined;
3601 } else if (typeof info !== "string") {
3602 return undefined;
3603 } else {
3604 name = info;
3605 }
3606 return { info, name };
3607 }
3608
3609 /** @typedef {{ type: "call", call: CallExpressionNode, calleeName: string, rootInfo: string | VariableInfo, getCalleeMembers: () => string[], name: string, getMembers: () => string[]}} CallExpressionInfo */
3610 /** @typedef {{ type: "expression", rootInfo: string | VariableInfo, name: string, getMembers: () => string[]}} ExpressionExpressionInfo */
3611
3612 /**
3613 * @param {MemberExpressionNode} expression a member expression
3614 * @param {number} allowedTypes which types should be returned, presented in bit mask
3615 * @returns {CallExpressionInfo | ExpressionExpressionInfo | undefined} expression info
3616 */
3617 getMemberExpressionInfo(expression, allowedTypes) {
3618 const { object, members } = this.extractMemberExpressionChain(expression);
3619 switch (object.type) {
3620 case "CallExpression": {
3621 if ((allowedTypes & ALLOWED_MEMBER_TYPES_CALL_EXPRESSION) === 0)
3622 return undefined;
3623 let callee = object.callee;
3624 let rootMembers = EMPTY_ARRAY;
3625 if (callee.type === "MemberExpression") {
3626 ({
3627 object: callee,
3628 members: rootMembers
3629 } = this.extractMemberExpressionChain(callee));
3630 }
3631 const rootName = getRootName(callee);
3632 if (!rootName) return undefined;
3633 const result = this.getFreeInfoFromVariable(rootName);
3634 if (!result) return undefined;
3635 const { info: rootInfo, name: resolvedRoot } = result;
3636 const calleeName = objectAndMembersToName(resolvedRoot, rootMembers);
3637 return {
3638 type: "call",
3639 call: object,
3640 calleeName,
3641 rootInfo,
3642 getCalleeMembers: memoize(() => rootMembers.reverse()),
3643 name: objectAndMembersToName(`${calleeName}()`, members),
3644 getMembers: memoize(() => members.reverse())
3645 };
3646 }
3647 case "Identifier":
3648 case "MetaProperty":
3649 case "ThisExpression": {
3650 if ((allowedTypes & ALLOWED_MEMBER_TYPES_EXPRESSION) === 0)
3651 return undefined;
3652 const rootName = getRootName(object);
3653 if (!rootName) return undefined;
3654
3655 const result = this.getFreeInfoFromVariable(rootName);
3656 if (!result) return undefined;
3657 const { info: rootInfo, name: resolvedRoot } = result;
3658 return {
3659 type: "expression",
3660 name: objectAndMembersToName(resolvedRoot, members),
3661 rootInfo,
3662 getMembers: memoize(() => members.reverse())
3663 };
3664 }
3665 }
3666 }
3667
3668 /**
3669 * @param {MemberExpressionNode} expression an expression
3670 * @returns {{ name: string, rootInfo: ExportedVariableInfo, getMembers: () => string[]}} name info
3671 */
3672 getNameForExpression(expression) {
3673 return this.getMemberExpressionInfo(
3674 expression,
3675 ALLOWED_MEMBER_TYPES_EXPRESSION
3676 );
3677 }
3678
3679 /**
3680 * @param {string} code source code
3681 * @param {ParseOptions} options parsing options
3682 * @returns {ProgramNode} parsed ast
3683 */
3684 static _parse(code, options) {
3685 const type = options ? options.sourceType : "module";
3686 /** @type {AcornOptions} */
3687 const parserOptions = {
3688 ...defaultParserOptions,
3689 allowReturnOutsideFunction: type === "script",
3690 ...options,
3691 sourceType: type === "auto" ? "module" : type
3692 };
3693
3694 /** @type {AnyNode} */
3695 let ast;
3696 let error;
3697 let threw = false;
3698 try {
3699 ast = /** @type {AnyNode} */ (parser.parse(code, parserOptions));
3700 } catch (e) {
3701 error = e;
3702 threw = true;
3703 }
3704
3705 if (threw && type === "auto") {
3706 parserOptions.sourceType = "script";
3707 if (!("allowReturnOutsideFunction" in options)) {
3708 parserOptions.allowReturnOutsideFunction = true;
3709 }
3710 if (Array.isArray(parserOptions.onComment)) {
3711 parserOptions.onComment.length = 0;
3712 }
3713 try {
3714 ast = /** @type {AnyNode} */ (parser.parse(code, parserOptions));
3715 threw = false;
3716 } catch (e) {
3717 // we use the error from first parse try
3718 // so nothing to do here
3719 }
3720 }
3721
3722 if (threw) {
3723 throw error;
3724 }
3725
3726 return /** @type {ProgramNode} */ (ast);
3727 }
3728}
3729
3730module.exports = JavascriptParser;
3731module.exports.ALLOWED_MEMBER_TYPES_ALL = ALLOWED_MEMBER_TYPES_ALL;
3732module.exports.ALLOWED_MEMBER_TYPES_EXPRESSION = ALLOWED_MEMBER_TYPES_EXPRESSION;
3733module.exports.ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = ALLOWED_MEMBER_TYPES_CALL_EXPRESSION;