source: trip-planner-front/node_modules/webpack/lib/javascript/JavascriptParser.js@ 59329aa

Last change on this file since 59329aa was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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