UNPKG

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