source: frontend/node_modules/@eslint-community/eslint-utils/index.js

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

Fix frontend appearance

  • Property mode set to 100644
File size: 86.3 KB
Line 
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var eslintVisitorKeys = require('eslint-visitor-keys');
6
7/** @typedef {import("eslint").Scope.Scope} Scope */
8/** @typedef {import("estree").Node} Node */
9
10/**
11 * Get the innermost scope which contains a given location.
12 * @param {Scope} initialScope The initial scope to search.
13 * @param {Node} node The location to search.
14 * @returns {Scope} The innermost scope.
15 */
16function getInnermostScope(initialScope, node) {
17 const location = /** @type {[number, number]} */ (node.range)[0];
18
19 let scope = initialScope;
20 let found = false;
21 do {
22 found = false;
23 for (const childScope of scope.childScopes) {
24 const range = /** @type {[number, number]} */ (
25 childScope.block.range
26 );
27
28 if (range[0] <= location && location < range[1]) {
29 scope = childScope;
30 found = true;
31 break
32 }
33 }
34 } while (found)
35
36 return scope
37}
38
39/** @typedef {import("eslint").Scope.Scope} Scope */
40/** @typedef {import("eslint").Scope.Variable} Variable */
41/** @typedef {import("estree").Identifier} Identifier */
42
43/**
44 * Find the variable of a given name.
45 * @param {Scope} initialScope The scope to start finding.
46 * @param {string|Identifier} nameOrNode The variable name to find. If this is a Node object then it should be an Identifier node.
47 * @returns {Variable|null} The found variable or null.
48 */
49function findVariable(initialScope, nameOrNode) {
50 let name = "";
51 /** @type {Scope|null} */
52 let scope = initialScope;
53
54 if (typeof nameOrNode === "string") {
55 name = nameOrNode;
56 } else {
57 name = nameOrNode.name;
58 scope = getInnermostScope(scope, nameOrNode);
59 }
60
61 while (scope != null) {
62 const variable = scope.set.get(name);
63 if (variable != null) {
64 return variable
65 }
66 scope = scope.upper;
67 }
68
69 return null
70}
71
72/** @typedef {import("eslint").AST.Token} Token */
73/** @typedef {import("estree").Comment} Comment */
74/** @typedef {import("./types.mjs").ArrowToken} ArrowToken */
75/** @typedef {import("./types.mjs").CommaToken} CommaToken */
76/** @typedef {import("./types.mjs").SemicolonToken} SemicolonToken */
77/** @typedef {import("./types.mjs").ColonToken} ColonToken */
78/** @typedef {import("./types.mjs").OpeningParenToken} OpeningParenToken */
79/** @typedef {import("./types.mjs").ClosingParenToken} ClosingParenToken */
80/** @typedef {import("./types.mjs").OpeningBracketToken} OpeningBracketToken */
81/** @typedef {import("./types.mjs").ClosingBracketToken} ClosingBracketToken */
82/** @typedef {import("./types.mjs").OpeningBraceToken} OpeningBraceToken */
83/** @typedef {import("./types.mjs").ClosingBraceToken} ClosingBraceToken */
84/**
85 * @template {string} Value
86 * @typedef {import("./types.mjs").PunctuatorToken<Value>} PunctuatorToken
87 */
88
89/** @typedef {Comment | Token} CommentOrToken */
90
91/**
92 * Creates the negate function of the given function.
93 * @param {function(CommentOrToken):boolean} f - The function to negate.
94 * @returns {function(CommentOrToken):boolean} Negated function.
95 */
96function negate(f) {
97 return (token) => !f(token)
98}
99
100/**
101 * Checks if the given token is a PunctuatorToken with the given value
102 * @template {string} Value
103 * @param {CommentOrToken} token - The token to check.
104 * @param {Value} value - The value to check.
105 * @returns {token is PunctuatorToken<Value>} `true` if the token is a PunctuatorToken with the given value.
106 */
107function isPunctuatorTokenWithValue(token, value) {
108 return token.type === "Punctuator" && token.value === value
109}
110
111/**
112 * Checks if the given token is an arrow token or not.
113 * @param {CommentOrToken} token - The token to check.
114 * @returns {token is ArrowToken} `true` if the token is an arrow token.
115 */
116function isArrowToken(token) {
117 return isPunctuatorTokenWithValue(token, "=>")
118}
119
120/**
121 * Checks if the given token is a comma token or not.
122 * @param {CommentOrToken} token - The token to check.
123 * @returns {token is CommaToken} `true` if the token is a comma token.
124 */
125function isCommaToken(token) {
126 return isPunctuatorTokenWithValue(token, ",")
127}
128
129/**
130 * Checks if the given token is a semicolon token or not.
131 * @param {CommentOrToken} token - The token to check.
132 * @returns {token is SemicolonToken} `true` if the token is a semicolon token.
133 */
134function isSemicolonToken(token) {
135 return isPunctuatorTokenWithValue(token, ";")
136}
137
138/**
139 * Checks if the given token is a colon token or not.
140 * @param {CommentOrToken} token - The token to check.
141 * @returns {token is ColonToken} `true` if the token is a colon token.
142 */
143function isColonToken(token) {
144 return isPunctuatorTokenWithValue(token, ":")
145}
146
147/**
148 * Checks if the given token is an opening parenthesis token or not.
149 * @param {CommentOrToken} token - The token to check.
150 * @returns {token is OpeningParenToken} `true` if the token is an opening parenthesis token.
151 */
152function isOpeningParenToken(token) {
153 return isPunctuatorTokenWithValue(token, "(")
154}
155
156/**
157 * Checks if the given token is a closing parenthesis token or not.
158 * @param {CommentOrToken} token - The token to check.
159 * @returns {token is ClosingParenToken} `true` if the token is a closing parenthesis token.
160 */
161function isClosingParenToken(token) {
162 return isPunctuatorTokenWithValue(token, ")")
163}
164
165/**
166 * Checks if the given token is an opening square bracket token or not.
167 * @param {CommentOrToken} token - The token to check.
168 * @returns {token is OpeningBracketToken} `true` if the token is an opening square bracket token.
169 */
170function isOpeningBracketToken(token) {
171 return isPunctuatorTokenWithValue(token, "[")
172}
173
174/**
175 * Checks if the given token is a closing square bracket token or not.
176 * @param {CommentOrToken} token - The token to check.
177 * @returns {token is ClosingBracketToken} `true` if the token is a closing square bracket token.
178 */
179function isClosingBracketToken(token) {
180 return isPunctuatorTokenWithValue(token, "]")
181}
182
183/**
184 * Checks if the given token is an opening brace token or not.
185 * @param {CommentOrToken} token - The token to check.
186 * @returns {token is OpeningBraceToken} `true` if the token is an opening brace token.
187 */
188function isOpeningBraceToken(token) {
189 return isPunctuatorTokenWithValue(token, "{")
190}
191
192/**
193 * Checks if the given token is a closing brace token or not.
194 * @param {CommentOrToken} token - The token to check.
195 * @returns {token is ClosingBraceToken} `true` if the token is a closing brace token.
196 */
197function isClosingBraceToken(token) {
198 return isPunctuatorTokenWithValue(token, "}")
199}
200
201/**
202 * Checks if the given token is a comment token or not.
203 * @param {CommentOrToken} token - The token to check.
204 * @returns {token is Comment} `true` if the token is a comment token.
205 */
206function isCommentToken(token) {
207 return ["Block", "Line", "Shebang"].includes(token.type)
208}
209
210const isNotArrowToken = negate(isArrowToken);
211const isNotCommaToken = negate(isCommaToken);
212const isNotSemicolonToken = negate(isSemicolonToken);
213const isNotColonToken = negate(isColonToken);
214const isNotOpeningParenToken = negate(isOpeningParenToken);
215const isNotClosingParenToken = negate(isClosingParenToken);
216const isNotOpeningBracketToken = negate(isOpeningBracketToken);
217const isNotClosingBracketToken = negate(isClosingBracketToken);
218const isNotOpeningBraceToken = negate(isOpeningBraceToken);
219const isNotClosingBraceToken = negate(isClosingBraceToken);
220const isNotCommentToken = negate(isCommentToken);
221
222/** @typedef {import("eslint").Rule.Node} RuleNode */
223/** @typedef {import("eslint").SourceCode} SourceCode */
224/** @typedef {import("eslint").AST.Token} Token */
225/** @typedef {import("estree").Function} FunctionNode */
226/** @typedef {import("estree").FunctionDeclaration} FunctionDeclaration */
227/** @typedef {import("estree").FunctionExpression} FunctionExpression */
228/** @typedef {import("estree").SourceLocation} SourceLocation */
229/** @typedef {import("estree").Position} Position */
230
231/**
232 * Get the `(` token of the given function node.
233 * @param {FunctionExpression | FunctionDeclaration} node - The function node to get.
234 * @param {SourceCode} sourceCode - The source code object to get tokens.
235 * @returns {Token} `(` token.
236 */
237function getOpeningParenOfParams(node, sourceCode) {
238 return node.id
239 ? /** @type {Token} */ (
240 sourceCode.getTokenAfter(node.id, isOpeningParenToken)
241 )
242 : /** @type {Token} */ (
243 sourceCode.getFirstToken(node, isOpeningParenToken)
244 )
245}
246
247/**
248 * Get the location of the given function node for reporting.
249 * @param {FunctionNode} node - The function node to get.
250 * @param {SourceCode} sourceCode - The source code object to get tokens.
251 * @returns {SourceLocation|null} The location of the function node for reporting.
252 */
253function getFunctionHeadLocation(node, sourceCode) {
254 const parent = /** @type {RuleNode} */ (node).parent;
255
256 /** @type {Position|null} */
257 let start = null;
258 /** @type {Position|null} */
259 let end = null;
260
261 if (node.type === "ArrowFunctionExpression") {
262 const arrowToken = /** @type {Token} */ (
263 sourceCode.getTokenBefore(node.body, isArrowToken)
264 );
265
266 start = arrowToken.loc.start;
267 end = arrowToken.loc.end;
268 } else if (
269 parent &&
270 (parent.type === "Property" ||
271 parent.type === "MethodDefinition" ||
272 parent.type === "PropertyDefinition")
273 ) {
274 start = /** @type {SourceLocation} */ (parent.loc).start;
275 end = getOpeningParenOfParams(node, sourceCode).loc.start;
276 } else {
277 start = /** @type {SourceLocation} */ (node.loc).start;
278 end = getOpeningParenOfParams(node, sourceCode).loc.start;
279 }
280
281 return {
282 start: { ...start },
283 end: { ...end },
284 }
285}
286
287/* globals globalThis, global, self, window */
288/** @typedef {import("./types.mjs").StaticValue} StaticValue */
289/** @typedef {import("eslint").Scope.Scope} Scope */
290/** @typedef {import("eslint").Scope.Variable} Variable */
291/** @typedef {import("estree").Node} Node */
292/** @typedef {import("@typescript-eslint/types").TSESTree.Node} TSESTreeNode */
293/** @typedef {import("@typescript-eslint/types").TSESTree.AST_NODE_TYPES} TSESTreeNodeTypes */
294/** @typedef {import("@typescript-eslint/types").TSESTree.MemberExpression} MemberExpression */
295/** @typedef {import("@typescript-eslint/types").TSESTree.Property} Property */
296/** @typedef {import("@typescript-eslint/types").TSESTree.RegExpLiteral} RegExpLiteral */
297/** @typedef {import("@typescript-eslint/types").TSESTree.BigIntLiteral} BigIntLiteral */
298/** @typedef {import("@typescript-eslint/types").TSESTree.Literal} Literal */
299
300const globalObject =
301 typeof globalThis !== "undefined"
302 ? globalThis
303 : // @ts-ignore
304 typeof self !== "undefined"
305 ? // @ts-ignore
306 self
307 : // @ts-ignore
308 typeof window !== "undefined"
309 ? // @ts-ignore
310 window
311 : typeof global !== "undefined"
312 ? global
313 : {};
314
315const builtinNames = Object.freeze(
316 new Set([
317 "Array",
318 "ArrayBuffer",
319 "BigInt",
320 "BigInt64Array",
321 "BigUint64Array",
322 "Boolean",
323 "DataView",
324 "Date",
325 "decodeURI",
326 "decodeURIComponent",
327 "encodeURI",
328 "encodeURIComponent",
329 "escape",
330 "Float32Array",
331 "Float64Array",
332 "Function",
333 "Infinity",
334 "Int16Array",
335 "Int32Array",
336 "Int8Array",
337 "isFinite",
338 "isNaN",
339 "isPrototypeOf",
340 "JSON",
341 "Map",
342 "Math",
343 "NaN",
344 "Number",
345 "Object",
346 "parseFloat",
347 "parseInt",
348 "Promise",
349 "Proxy",
350 "Reflect",
351 "RegExp",
352 "Set",
353 "String",
354 "Symbol",
355 "Uint16Array",
356 "Uint32Array",
357 "Uint8Array",
358 "Uint8ClampedArray",
359 "undefined",
360 "unescape",
361 "WeakMap",
362 "WeakSet",
363 ]),
364);
365const callAllowed = new Set(
366 [
367 Array.isArray,
368 Array.of,
369 Array.prototype.at,
370 Array.prototype.concat,
371 Array.prototype.entries,
372 Array.prototype.every,
373 Array.prototype.filter,
374 Array.prototype.find,
375 Array.prototype.findIndex,
376 Array.prototype.flat,
377 Array.prototype.includes,
378 Array.prototype.indexOf,
379 Array.prototype.join,
380 Array.prototype.keys,
381 Array.prototype.lastIndexOf,
382 Array.prototype.slice,
383 Array.prototype.some,
384 Array.prototype.toString,
385 Array.prototype.values,
386 typeof BigInt === "function" ? BigInt : undefined,
387 Boolean,
388 Date,
389 Date.parse,
390 decodeURI,
391 decodeURIComponent,
392 encodeURI,
393 encodeURIComponent,
394 escape,
395 isFinite,
396 isNaN,
397 // @ts-ignore
398 isPrototypeOf,
399 Map,
400 Map.prototype.entries,
401 Map.prototype.get,
402 Map.prototype.has,
403 Map.prototype.keys,
404 Map.prototype.values,
405 .../** @type {(keyof typeof Math)[]} */ (
406 Object.getOwnPropertyNames(Math)
407 )
408 .filter((k) => k !== "random")
409 .map((k) => Math[k])
410 .filter((f) => typeof f === "function"),
411 Number,
412 Number.isFinite,
413 Number.isNaN,
414 Number.parseFloat,
415 Number.parseInt,
416 Number.prototype.toExponential,
417 Number.prototype.toFixed,
418 Number.prototype.toPrecision,
419 Number.prototype.toString,
420 Object,
421 Object.entries,
422 Object.is,
423 Object.isExtensible,
424 Object.isFrozen,
425 Object.isSealed,
426 Object.keys,
427 Object.values,
428 parseFloat,
429 parseInt,
430 RegExp,
431 Set,
432 Set.prototype.entries,
433 Set.prototype.has,
434 Set.prototype.keys,
435 Set.prototype.values,
436 String,
437 String.fromCharCode,
438 String.fromCodePoint,
439 String.raw,
440 String.prototype.at,
441 String.prototype.charAt,
442 String.prototype.charCodeAt,
443 String.prototype.codePointAt,
444 String.prototype.concat,
445 String.prototype.endsWith,
446 String.prototype.includes,
447 String.prototype.indexOf,
448 String.prototype.lastIndexOf,
449 String.prototype.normalize,
450 String.prototype.padEnd,
451 String.prototype.padStart,
452 String.prototype.slice,
453 String.prototype.startsWith,
454 String.prototype.substr,
455 String.prototype.substring,
456 String.prototype.toLowerCase,
457 String.prototype.toString,
458 String.prototype.toUpperCase,
459 String.prototype.trim,
460 String.prototype.trimEnd,
461 String.prototype.trimLeft,
462 String.prototype.trimRight,
463 String.prototype.trimStart,
464 Symbol.for,
465 Symbol.keyFor,
466 unescape,
467 ].filter((f) => typeof f === "function"),
468);
469const callPassThrough = new Set([
470 Object.freeze,
471 Object.preventExtensions,
472 Object.seal,
473]);
474
475/** @type {ReadonlyArray<readonly [Function, ReadonlySet<string>]>} */
476const getterAllowed = [
477 [Map, new Set(["size"])],
478 [
479 RegExp,
480 new Set([
481 "dotAll",
482 "flags",
483 "global",
484 "hasIndices",
485 "ignoreCase",
486 "multiline",
487 "source",
488 "sticky",
489 "unicode",
490 ]),
491 ],
492 [Set, new Set(["size"])],
493];
494
495/**
496 * Get the property descriptor.
497 * @param {object} object The object to get.
498 * @param {string|number|symbol} name The property name to get.
499 */
500function getPropertyDescriptor(object, name) {
501 let x = object;
502 while ((typeof x === "object" || typeof x === "function") && x !== null) {
503 const d = Object.getOwnPropertyDescriptor(x, name);
504 if (d) {
505 return d
506 }
507 x = Object.getPrototypeOf(x);
508 }
509 return null
510}
511
512/**
513 * Check if a property is getter or not.
514 * @param {object} object The object to check.
515 * @param {string|number|symbol} name The property name to check.
516 */
517function isGetter(object, name) {
518 const d = getPropertyDescriptor(object, name);
519 return d != null && d.get != null
520}
521
522/**
523 * Get the element values of a given node list.
524 * @param {(Node|TSESTreeNode|null)[]} nodeList The node list to get values.
525 * @param {Scope|undefined|null} initialScope The initial scope to find variables.
526 * @returns {any[]|null} The value list if all nodes are constant. Otherwise, null.
527 */
528function getElementValues(nodeList, initialScope) {
529 const valueList = [];
530
531 for (let i = 0; i < nodeList.length; ++i) {
532 const elementNode = nodeList[i];
533
534 if (elementNode == null) {
535 valueList.length = i + 1;
536 } else if (elementNode.type === "SpreadElement") {
537 const argument = getStaticValueR(elementNode.argument, initialScope);
538 if (argument == null) {
539 return null
540 }
541 valueList.push(.../** @type {Iterable<any>} */ (argument.value));
542 } else {
543 const element = getStaticValueR(elementNode, initialScope);
544 if (element == null) {
545 return null
546 }
547 valueList.push(element.value);
548 }
549 }
550
551 return valueList
552}
553
554/**
555 * Checks if a variable is a built-in global.
556 * @param {Variable|null} variable The variable to check.
557 * @returns {variable is Variable & {defs:[]}}
558 */
559function isBuiltinGlobal(variable) {
560 return (
561 variable != null &&
562 variable.defs.length === 0 &&
563 builtinNames.has(variable.name) &&
564 variable.name in globalObject
565 )
566}
567
568/**
569 * Checks if a variable can be considered as a constant.
570 * @param {Variable} variable
571 * @returns {variable is Variable & {defs: [import("eslint").Scope.Definition & { type: "Variable" }]}} True if the variable can be considered as a constant.
572 */
573function canBeConsideredConst(variable) {
574 if (variable.defs.length !== 1) {
575 return false
576 }
577 const def = variable.defs[0];
578 return Boolean(
579 def.parent &&
580 def.type === "Variable" &&
581 (def.parent.kind === "const" || isEffectivelyConst(variable)),
582 )
583}
584
585/**
586 * Returns whether the given variable is never written to after initialization.
587 * @param {Variable} variable
588 * @returns {boolean}
589 */
590function isEffectivelyConst(variable) {
591 const refs = variable.references;
592
593 const inits = refs.filter((r) => r.init).length;
594 const reads = refs.filter((r) => r.isReadOnly()).length;
595 if (inits === 1 && reads + inits === refs.length) {
596 // there is only one init and all other references only read
597 return true
598 }
599 return false
600}
601
602/**
603 * Checks if a variable has mutation in its property.
604 * @param {Variable} variable The variable to check.
605 * @param {Scope|null} initialScope The scope to start finding variable. Optional. If the node is a computed property node and this scope was given, this checks the computed property name by the `getStringIfConstant` function with the scope, and returns the value of it.
606 * @returns {boolean} True if the variable has mutation in its property.
607 */
608function hasMutationInProperty(variable, initialScope) {
609 for (const ref of variable.references) {
610 let node = /** @type {TSESTreeNode} */ (ref.identifier);
611 while (node && node.parent && node.parent.type === "MemberExpression") {
612 node = node.parent;
613 }
614 if (!node || !node.parent) {
615 continue
616 }
617 if (
618 (node.parent.type === "AssignmentExpression" &&
619 node.parent.left === node) ||
620 (node.parent.type === "UpdateExpression" &&
621 node.parent.argument === node)
622 ) {
623 // This is a mutation.
624 return true
625 }
626 if (
627 node.parent.type === "CallExpression" &&
628 node.parent.callee === node &&
629 node.type === "MemberExpression"
630 ) {
631 const methodName = getStaticPropertyNameValue(node, initialScope);
632 if (isNameOfMutationArrayMethod(methodName)) {
633 // This is a mutation.
634 return true
635 }
636 }
637 }
638 return false
639
640 /**
641 * Checks if a method name is one of the mutation array methods.
642 * @param {StaticValue|null} methodName The method name to check.
643 * @returns {boolean} True if the method name is a mutation array method.
644 */
645 function isNameOfMutationArrayMethod(methodName) {
646 if (methodName == null || methodName.value == null) {
647 return false
648 }
649 const name = methodName.value;
650 return (
651 name === "copyWithin" ||
652 name === "fill" ||
653 name === "pop" ||
654 name === "push" ||
655 name === "reverse" ||
656 name === "shift" ||
657 name === "sort" ||
658 name === "splice" ||
659 name === "unshift"
660 )
661 }
662}
663
664/**
665 * @template {TSESTreeNodeTypes} T
666 * @callback VisitorCallback
667 * @param {TSESTreeNode & { type: T }} node
668 * @param {Scope|undefined|null} initialScope
669 * @returns {StaticValue | null}
670 */
671/**
672 * @typedef { { [K in TSESTreeNodeTypes]?: VisitorCallback<K> } } Operations
673 */
674/**
675 * @type {Operations}
676 */
677const operations = Object.freeze({
678 ArrayExpression(node, initialScope) {
679 const elements = getElementValues(node.elements, initialScope);
680 return elements != null ? { value: elements } : null
681 },
682
683 AssignmentExpression(node, initialScope) {
684 if (node.operator === "=") {
685 return getStaticValueR(node.right, initialScope)
686 }
687 return null
688 },
689
690 //eslint-disable-next-line complexity
691 BinaryExpression(node, initialScope) {
692 if (node.operator === "in" || node.operator === "instanceof") {
693 // Not supported.
694 return null
695 }
696
697 const left = getStaticValueR(node.left, initialScope);
698 const right = getStaticValueR(node.right, initialScope);
699 if (left != null && right != null) {
700 switch (node.operator) {
701 case "==":
702 return { value: left.value == right.value } //eslint-disable-line eqeqeq
703 case "!=":
704 return { value: left.value != right.value } //eslint-disable-line eqeqeq
705 case "===":
706 return { value: left.value === right.value }
707 case "!==":
708 return { value: left.value !== right.value }
709 case "<":
710 return {
711 value:
712 /** @type {any} */ (left.value) <
713 /** @type {any} */ (right.value),
714 }
715 case "<=":
716 return {
717 value:
718 /** @type {any} */ (left.value) <=
719 /** @type {any} */ (right.value),
720 }
721 case ">":
722 return {
723 value:
724 /** @type {any} */ (left.value) >
725 /** @type {any} */ (right.value),
726 }
727 case ">=":
728 return {
729 value:
730 /** @type {any} */ (left.value) >=
731 /** @type {any} */ (right.value),
732 }
733 case "<<":
734 return {
735 value:
736 /** @type {any} */ (left.value) <<
737 /** @type {any} */ (right.value),
738 }
739 case ">>":
740 return {
741 value:
742 /** @type {any} */ (left.value) >>
743 /** @type {any} */ (right.value),
744 }
745 case ">>>":
746 return {
747 value:
748 /** @type {any} */ (left.value) >>>
749 /** @type {any} */ (right.value),
750 }
751 case "+":
752 return {
753 value:
754 /** @type {any} */ (left.value) +
755 /** @type {any} */ (right.value),
756 }
757 case "-":
758 return {
759 value:
760 /** @type {any} */ (left.value) -
761 /** @type {any} */ (right.value),
762 }
763 case "*":
764 return {
765 value:
766 /** @type {any} */ (left.value) *
767 /** @type {any} */ (right.value),
768 }
769 case "/":
770 return {
771 value:
772 /** @type {any} */ (left.value) /
773 /** @type {any} */ (right.value),
774 }
775 case "%":
776 return {
777 value:
778 /** @type {any} */ (left.value) %
779 /** @type {any} */ (right.value),
780 }
781 case "**":
782 return {
783 value:
784 /** @type {any} */ (left.value) **
785 /** @type {any} */ (right.value),
786 }
787 case "|":
788 return {
789 value:
790 /** @type {any} */ (left.value) |
791 /** @type {any} */ (right.value),
792 }
793 case "^":
794 return {
795 value:
796 /** @type {any} */ (left.value) ^
797 /** @type {any} */ (right.value),
798 }
799 case "&":
800 return {
801 value:
802 /** @type {any} */ (left.value) &
803 /** @type {any} */ (right.value),
804 }
805
806 // no default
807 }
808 }
809
810 return null
811 },
812
813 CallExpression(node, initialScope) {
814 const calleeNode = node.callee;
815 const args = getElementValues(node.arguments, initialScope);
816
817 if (args != null) {
818 if (calleeNode.type === "MemberExpression") {
819 if (calleeNode.property.type === "PrivateIdentifier") {
820 return null
821 }
822 const object = getStaticValueR(calleeNode.object, initialScope);
823 if (object != null) {
824 if (
825 object.value == null &&
826 (object.optional || node.optional)
827 ) {
828 return { value: undefined, optional: true }
829 }
830 const property = getStaticPropertyNameValue(
831 calleeNode,
832 initialScope,
833 );
834
835 if (property != null) {
836 const receiver =
837 /** @type {Record<PropertyKey, (...args: any[]) => any>} */ (
838 object.value
839 );
840 const methodName = /** @type {PropertyKey} */ (
841 property.value
842 );
843 if (callAllowed.has(receiver[methodName])) {
844 return {
845 value: receiver[methodName](...args),
846 }
847 }
848 if (callPassThrough.has(receiver[methodName])) {
849 return { value: args[0] }
850 }
851 }
852 }
853 } else {
854 const callee = getStaticValueR(calleeNode, initialScope);
855 if (callee != null) {
856 if (callee.value == null && node.optional) {
857 return { value: undefined, optional: true }
858 }
859 const func = /** @type {(...args: any[]) => any} */ (
860 callee.value
861 );
862 if (callAllowed.has(func)) {
863 return { value: func(...args) }
864 }
865 if (callPassThrough.has(func)) {
866 return { value: args[0] }
867 }
868 }
869 }
870 }
871
872 return null
873 },
874
875 ConditionalExpression(node, initialScope) {
876 const test = getStaticValueR(node.test, initialScope);
877 if (test != null) {
878 return test.value
879 ? getStaticValueR(node.consequent, initialScope)
880 : getStaticValueR(node.alternate, initialScope)
881 }
882 return null
883 },
884
885 ExpressionStatement(node, initialScope) {
886 return getStaticValueR(node.expression, initialScope)
887 },
888
889 Identifier(node, initialScope) {
890 if (initialScope != null) {
891 const variable = findVariable(initialScope, node);
892
893 if (variable != null) {
894 // Built-in globals.
895 if (isBuiltinGlobal(variable)) {
896 return { value: globalObject[variable.name] }
897 }
898
899 // Constants.
900 if (canBeConsideredConst(variable)) {
901 const def = variable.defs[0];
902 if (
903 // TODO(mysticatea): don't support destructuring here.
904 def.node.id.type === "Identifier"
905 ) {
906 const init = getStaticValueR(
907 def.node.init,
908 initialScope,
909 );
910 if (
911 init &&
912 typeof init.value === "object" &&
913 init.value !== null
914 ) {
915 if (hasMutationInProperty(variable, initialScope)) {
916 // This variable has mutation in its property.
917 return null
918 }
919 }
920 return init
921 }
922 }
923 }
924 }
925 return null
926 },
927
928 Literal(node) {
929 const literal =
930 /** @type {Partial<Literal> & Partial<RegExpLiteral> & Partial<BigIntLiteral>} */ (
931 node
932 );
933 //istanbul ignore if : this is implementation-specific behavior.
934 if (
935 (literal.regex != null || literal.bigint != null) &&
936 literal.value == null
937 ) {
938 // It was a RegExp/BigInt literal, but Node.js didn't support it.
939 return null
940 }
941 return { value: literal.value }
942 },
943
944 LogicalExpression(node, initialScope) {
945 const left = getStaticValueR(node.left, initialScope);
946 if (left != null) {
947 if (
948 (node.operator === "||" && Boolean(left.value) === true) ||
949 (node.operator === "&&" && Boolean(left.value) === false) ||
950 (node.operator === "??" && left.value != null)
951 ) {
952 return left
953 }
954
955 const right = getStaticValueR(node.right, initialScope);
956 if (right != null) {
957 return right
958 }
959 }
960
961 return null
962 },
963
964 MemberExpression(node, initialScope) {
965 if (node.property.type === "PrivateIdentifier") {
966 return null
967 }
968 const object = getStaticValueR(node.object, initialScope);
969 if (object != null) {
970 if (object.value == null && (object.optional || node.optional)) {
971 return { value: undefined, optional: true }
972 }
973 const property = getStaticPropertyNameValue(node, initialScope);
974
975 if (property != null) {
976 if (
977 !isGetter(
978 /** @type {object} */ (object.value),
979 /** @type {PropertyKey} */ (property.value),
980 )
981 ) {
982 return {
983 value: /** @type {Record<PropertyKey, unknown>} */ (
984 object.value
985 )[/** @type {PropertyKey} */ (property.value)],
986 }
987 }
988
989 for (const [classFn, allowed] of getterAllowed) {
990 if (
991 object.value instanceof classFn &&
992 allowed.has(/** @type {string} */ (property.value))
993 ) {
994 return {
995 value: /** @type {Record<PropertyKey, unknown>} */ (
996 object.value
997 )[/** @type {PropertyKey} */ (property.value)],
998 }
999 }
1000 }
1001 }
1002 }
1003 return null
1004 },
1005
1006 ChainExpression(node, initialScope) {
1007 const expression = getStaticValueR(node.expression, initialScope);
1008 if (expression != null) {
1009 return { value: expression.value }
1010 }
1011 return null
1012 },
1013
1014 NewExpression(node, initialScope) {
1015 const callee = getStaticValueR(node.callee, initialScope);
1016 const args = getElementValues(node.arguments, initialScope);
1017
1018 if (callee != null && args != null) {
1019 const Func = /** @type {new (...args: any[]) => any} */ (
1020 callee.value
1021 );
1022 if (callAllowed.has(Func)) {
1023 return { value: new Func(...args) }
1024 }
1025 }
1026
1027 return null
1028 },
1029
1030 ObjectExpression(node, initialScope) {
1031 /** @type {Record<PropertyKey, unknown>} */
1032 const object = {};
1033
1034 for (const propertyNode of node.properties) {
1035 if (propertyNode.type === "Property") {
1036 if (propertyNode.kind !== "init") {
1037 return null
1038 }
1039 const key = getStaticPropertyNameValue(
1040 propertyNode,
1041 initialScope,
1042 );
1043 const value = getStaticValueR(propertyNode.value, initialScope);
1044 if (key == null || value == null) {
1045 return null
1046 }
1047 object[/** @type {PropertyKey} */ (key.value)] = value.value;
1048 } else if (
1049 propertyNode.type === "SpreadElement" ||
1050 // @ts-expect-error -- Backward compatibility
1051 propertyNode.type === "ExperimentalSpreadProperty"
1052 ) {
1053 const argument = getStaticValueR(
1054 propertyNode.argument,
1055 initialScope,
1056 );
1057 if (argument == null) {
1058 return null
1059 }
1060 Object.assign(object, argument.value);
1061 } else {
1062 return null
1063 }
1064 }
1065
1066 return { value: object }
1067 },
1068
1069 SequenceExpression(node, initialScope) {
1070 const last = node.expressions[node.expressions.length - 1];
1071 return getStaticValueR(last, initialScope)
1072 },
1073
1074 TaggedTemplateExpression(node, initialScope) {
1075 const tag = getStaticValueR(node.tag, initialScope);
1076 const expressions = getElementValues(
1077 node.quasi.expressions,
1078 initialScope,
1079 );
1080
1081 if (tag != null && expressions != null) {
1082 const func = /** @type {(...args: any[]) => any} */ (tag.value);
1083 /** @type {any[] & { raw?: string[] }} */
1084 const strings = node.quasi.quasis.map((q) => q.value.cooked);
1085 strings.raw = node.quasi.quasis.map((q) => q.value.raw);
1086
1087 if (func === String.raw) {
1088 return { value: func(strings, ...expressions) }
1089 }
1090 }
1091
1092 return null
1093 },
1094
1095 TemplateLiteral(node, initialScope) {
1096 const expressions = getElementValues(node.expressions, initialScope);
1097 if (expressions != null) {
1098 let value = node.quasis[0].value.cooked;
1099 for (let i = 0; i < expressions.length; ++i) {
1100 value += expressions[i];
1101 value += /** @type {string} */ (node.quasis[i + 1].value.cooked);
1102 }
1103 return { value }
1104 }
1105 return null
1106 },
1107
1108 UnaryExpression(node, initialScope) {
1109 if (node.operator === "delete") {
1110 // Not supported.
1111 return null
1112 }
1113 if (node.operator === "void") {
1114 return { value: undefined }
1115 }
1116
1117 const arg = getStaticValueR(node.argument, initialScope);
1118 if (arg != null) {
1119 switch (node.operator) {
1120 case "-":
1121 return { value: -(/** @type {any} */ (arg.value)) }
1122 case "+":
1123 return { value: +(/** @type {any} */ (arg.value)) } //eslint-disable-line no-implicit-coercion
1124 case "!":
1125 return { value: !arg.value }
1126 case "~":
1127 return { value: ~(/** @type {any} */ (arg.value)) }
1128 case "typeof":
1129 return { value: typeof arg.value }
1130
1131 // no default
1132 }
1133 }
1134
1135 return null
1136 },
1137 TSAsExpression(node, initialScope) {
1138 return getStaticValueR(node.expression, initialScope)
1139 },
1140 TSSatisfiesExpression(node, initialScope) {
1141 return getStaticValueR(node.expression, initialScope)
1142 },
1143 TSTypeAssertion(node, initialScope) {
1144 return getStaticValueR(node.expression, initialScope)
1145 },
1146 TSNonNullExpression(node, initialScope) {
1147 return getStaticValueR(node.expression, initialScope)
1148 },
1149 TSInstantiationExpression(node, initialScope) {
1150 return getStaticValueR(node.expression, initialScope)
1151 },
1152});
1153
1154/**
1155 * Get the value of a given node if it's a static value.
1156 * @param {Node|TSESTreeNode|null|undefined} node The node to get.
1157 * @param {Scope|undefined|null} initialScope The scope to start finding variable.
1158 * @returns {StaticValue|null} The static value of the node, or `null`.
1159 */
1160function getStaticValueR(node, initialScope) {
1161 if (node != null && Object.hasOwnProperty.call(operations, node.type)) {
1162 return /** @type {VisitorCallback<any>} */ (operations[node.type])(
1163 /** @type {TSESTreeNode} */ (node),
1164 initialScope,
1165 )
1166 }
1167 return null
1168}
1169
1170/**
1171 * Get the static value of property name from a MemberExpression node or a Property node.
1172 * @param {MemberExpression|Property} node The node to get.
1173 * @param {Scope|null} [initialScope] The scope to start finding variable. Optional. If the node is a computed property node and this scope was given, this checks the computed property name by the `getStringIfConstant` function with the scope, and returns the value of it.
1174 * @returns {StaticValue|null} The static value of the property name of the node, or `null`.
1175 */
1176function getStaticPropertyNameValue(node, initialScope) {
1177 const nameNode = node.type === "Property" ? node.key : node.property;
1178
1179 if (node.computed) {
1180 return getStaticValueR(nameNode, initialScope)
1181 }
1182
1183 if (nameNode.type === "Identifier") {
1184 return { value: nameNode.name }
1185 }
1186
1187 if (nameNode.type === "Literal") {
1188 if (/** @type {Partial<BigIntLiteral>} */ (nameNode).bigint) {
1189 return { value: /** @type {BigIntLiteral} */ (nameNode).bigint }
1190 }
1191 return { value: String(nameNode.value) }
1192 }
1193
1194 return null
1195}
1196
1197/**
1198 * Get the value of a given node if it's a static value.
1199 * @param {Node} node The node to get.
1200 * @param {Scope|null} [initialScope] The scope to start finding variable. Optional. If this scope was given, this tries to resolve identifier references which are in the given node as much as possible.
1201 * @returns {StaticValue | null} The static value of the node, or `null`.
1202 */
1203function getStaticValue(node, initialScope = null) {
1204 try {
1205 return getStaticValueR(node, initialScope)
1206 } catch (_error) {
1207 return null
1208 }
1209}
1210
1211/** @typedef {import("eslint").Scope.Scope} Scope */
1212/** @typedef {import("estree").Node} Node */
1213/** @typedef {import("estree").RegExpLiteral} RegExpLiteral */
1214/** @typedef {import("estree").BigIntLiteral} BigIntLiteral */
1215/** @typedef {import("estree").SimpleLiteral} SimpleLiteral */
1216
1217/**
1218 * Get the value of a given node if it's a literal or a template literal.
1219 * @param {Node} node The node to get.
1220 * @param {Scope|null} [initialScope] The scope to start finding variable. Optional. If the node is an Identifier node and this scope was given, this checks the variable of the identifier, and returns the value of it if the variable is a constant.
1221 * @returns {string|null} The value of the node, or `null`.
1222 */
1223function getStringIfConstant(node, initialScope = null) {
1224 // Handle the literals that the platform doesn't support natively.
1225 if (node && node.type === "Literal" && node.value === null) {
1226 const literal =
1227 /** @type {Partial<SimpleLiteral> & Partial<RegExpLiteral> & Partial<BigIntLiteral>} */ (
1228 node
1229 );
1230 if (literal.regex) {
1231 return `/${literal.regex.pattern}/${literal.regex.flags}`
1232 }
1233 if (literal.bigint) {
1234 return literal.bigint
1235 }
1236 }
1237
1238 const evaluated = getStaticValue(node, initialScope);
1239
1240 if (evaluated) {
1241 // `String(Symbol.prototype)` throws error
1242 try {
1243 return String(evaluated.value)
1244 } catch {
1245 // No op
1246 }
1247 }
1248
1249 return null
1250}
1251
1252/** @typedef {import("eslint").Scope.Scope} Scope */
1253/** @typedef {import("estree").MemberExpression} MemberExpression */
1254/** @typedef {import("estree").MethodDefinition} MethodDefinition */
1255/** @typedef {import("estree").Property} Property */
1256/** @typedef {import("estree").PropertyDefinition} PropertyDefinition */
1257/** @typedef {import("estree").Identifier} Identifier */
1258
1259/**
1260 * Get the property name from a MemberExpression node or a Property node.
1261 * @param {MemberExpression | MethodDefinition | Property | PropertyDefinition} node The node to get.
1262 * @param {Scope} [initialScope] The scope to start finding variable. Optional. If the node is a computed property node and this scope was given, this checks the computed property name by the `getStringIfConstant` function with the scope, and returns the value of it.
1263 * @returns {string|null|undefined} The property name of the node.
1264 */
1265function getPropertyName(node, initialScope) {
1266 switch (node.type) {
1267 case "MemberExpression":
1268 if (node.computed) {
1269 return getStringIfConstant(node.property, initialScope)
1270 }
1271 if (node.property.type === "PrivateIdentifier") {
1272 return null
1273 }
1274 return /** @type {Partial<Identifier>} */ (node.property).name
1275
1276 case "Property":
1277 case "MethodDefinition":
1278 case "PropertyDefinition":
1279 if (node.computed) {
1280 return getStringIfConstant(node.key, initialScope)
1281 }
1282 if (node.key.type === "Literal") {
1283 return String(node.key.value)
1284 }
1285 if (node.key.type === "PrivateIdentifier") {
1286 return null
1287 }
1288 return /** @type {Partial<Identifier>} */ (node.key).name
1289 }
1290
1291 return null
1292}
1293
1294/** @typedef {import("eslint").Rule.Node} RuleNode */
1295/** @typedef {import("eslint").SourceCode} SourceCode */
1296/** @typedef {import("estree").Function} FunctionNode */
1297/** @typedef {import("estree").FunctionDeclaration} FunctionDeclaration */
1298/** @typedef {import("estree").FunctionExpression} FunctionExpression */
1299/** @typedef {import("estree").Identifier} Identifier */
1300
1301/**
1302 * Get the name and kind of the given function node.
1303 * @param {FunctionNode} node - The function node to get.
1304 * @param {SourceCode} [sourceCode] The source code object to get the code of computed property keys.
1305 * @returns {string} The name and kind of the function node.
1306 */
1307// eslint-disable-next-line complexity
1308function getFunctionNameWithKind(node, sourceCode) {
1309 const parent = /** @type {RuleNode} */ (node).parent;
1310
1311 if (!parent) {
1312 return ""
1313 }
1314
1315 const tokens = [];
1316 const isObjectMethod = parent.type === "Property" && parent.value === node;
1317 const isClassMethod =
1318 parent.type === "MethodDefinition" && parent.value === node;
1319 const isClassFieldMethod =
1320 parent.type === "PropertyDefinition" && parent.value === node;
1321
1322 // Modifiers.
1323 if (isClassMethod || isClassFieldMethod) {
1324 if (parent.static) {
1325 tokens.push("static");
1326 }
1327 if (parent.key.type === "PrivateIdentifier") {
1328 tokens.push("private");
1329 }
1330 }
1331 if (node.async) {
1332 tokens.push("async");
1333 }
1334 if (node.generator) {
1335 tokens.push("generator");
1336 }
1337
1338 // Kinds.
1339 if (isObjectMethod || isClassMethod) {
1340 if (parent.kind === "constructor") {
1341 return "constructor"
1342 }
1343 if (parent.kind === "get") {
1344 tokens.push("getter");
1345 } else if (parent.kind === "set") {
1346 tokens.push("setter");
1347 } else {
1348 tokens.push("method");
1349 }
1350 } else if (isClassFieldMethod) {
1351 tokens.push("method");
1352 } else {
1353 if (node.type === "ArrowFunctionExpression") {
1354 tokens.push("arrow");
1355 }
1356 tokens.push("function");
1357 }
1358
1359 // Names.
1360 if (isObjectMethod || isClassMethod || isClassFieldMethod) {
1361 if (parent.key.type === "PrivateIdentifier") {
1362 tokens.push(`#${parent.key.name}`);
1363 } else {
1364 const name = getPropertyName(parent);
1365 if (name) {
1366 tokens.push(`'${name}'`);
1367 } else if (sourceCode) {
1368 const keyText = sourceCode.getText(parent.key);
1369 if (!keyText.includes("\n")) {
1370 tokens.push(`[${keyText}]`);
1371 }
1372 }
1373 }
1374 } else if (hasId(node)) {
1375 tokens.push(`'${node.id.name}'`);
1376 } else if (
1377 parent.type === "VariableDeclarator" &&
1378 parent.id &&
1379 parent.id.type === "Identifier"
1380 ) {
1381 tokens.push(`'${parent.id.name}'`);
1382 } else if (
1383 (parent.type === "AssignmentExpression" ||
1384 parent.type === "AssignmentPattern") &&
1385 parent.left &&
1386 parent.left.type === "Identifier"
1387 ) {
1388 tokens.push(`'${parent.left.name}'`);
1389 } else if (
1390 parent.type === "ExportDefaultDeclaration" &&
1391 parent.declaration === node
1392 ) {
1393 tokens.push("'default'");
1394 }
1395
1396 return tokens.join(" ")
1397}
1398
1399/**
1400 * @param {FunctionNode} node
1401 * @returns {node is FunctionDeclaration | FunctionExpression & { id: Identifier }}
1402 */
1403function hasId(node) {
1404 return Boolean(
1405 /** @type {Partial<FunctionDeclaration | FunctionExpression>} */ (node)
1406 .id,
1407 )
1408}
1409
1410/** @typedef {import("estree").Node} Node */
1411/** @typedef {import("eslint").SourceCode} SourceCode */
1412/** @typedef {import("./types.mjs").HasSideEffectOptions} HasSideEffectOptions */
1413/** @typedef {import("estree").BinaryExpression} BinaryExpression */
1414/** @typedef {import("estree").MemberExpression} MemberExpression */
1415/** @typedef {import("estree").MethodDefinition} MethodDefinition */
1416/** @typedef {import("estree").Property} Property */
1417/** @typedef {import("estree").PropertyDefinition} PropertyDefinition */
1418/** @typedef {import("estree").UnaryExpression} UnaryExpression */
1419
1420const typeConversionBinaryOps = Object.freeze(
1421 new Set([
1422 "==",
1423 "!=",
1424 "<",
1425 "<=",
1426 ">",
1427 ">=",
1428 "<<",
1429 ">>",
1430 ">>>",
1431 "+",
1432 "-",
1433 "*",
1434 "/",
1435 "%",
1436 "|",
1437 "^",
1438 "&",
1439 "in",
1440 ]),
1441);
1442const typeConversionUnaryOps = Object.freeze(new Set(["-", "+", "!", "~"]));
1443
1444/**
1445 * Check whether the given value is an ASTNode or not.
1446 * @param {any} x The value to check.
1447 * @returns {x is Node} `true` if the value is an ASTNode.
1448 */
1449function isNode(x) {
1450 return x !== null && typeof x === "object" && typeof x.type === "string"
1451}
1452
1453const visitor = Object.freeze(
1454 Object.assign(Object.create(null), {
1455 /**
1456 * @param {Node} node
1457 * @param {HasSideEffectOptions} options
1458 * @param {Record<string, string[]>} visitorKeys
1459 */
1460 $visit(node, options, visitorKeys) {
1461 const { type } = node;
1462
1463 if (typeof (/** @type {any} */ (this)[type]) === "function") {
1464 return /** @type {any} */ (this)[type](
1465 node,
1466 options,
1467 visitorKeys,
1468 )
1469 }
1470
1471 return this.$visitChildren(node, options, visitorKeys)
1472 },
1473
1474 /**
1475 * @param {Node} node
1476 * @param {HasSideEffectOptions} options
1477 * @param {Record<string, string[]>} visitorKeys
1478 */
1479 $visitChildren(node, options, visitorKeys) {
1480 const { type } = node;
1481
1482 for (const key of /** @type {(keyof Node)[]} */ (
1483 visitorKeys[type] || eslintVisitorKeys.getKeys(node)
1484 )) {
1485 const value = node[key];
1486
1487 if (Array.isArray(value)) {
1488 for (const element of value) {
1489 if (
1490 isNode(element) &&
1491 this.$visit(element, options, visitorKeys)
1492 ) {
1493 return true
1494 }
1495 }
1496 } else if (
1497 isNode(value) &&
1498 this.$visit(value, options, visitorKeys)
1499 ) {
1500 return true
1501 }
1502 }
1503
1504 return false
1505 },
1506
1507 ArrowFunctionExpression() {
1508 return false
1509 },
1510 AssignmentExpression() {
1511 return true
1512 },
1513 AwaitExpression() {
1514 return true
1515 },
1516 /**
1517 * @param {BinaryExpression} node
1518 * @param {HasSideEffectOptions} options
1519 * @param {Record<string, string[]>} visitorKeys
1520 */
1521 BinaryExpression(node, options, visitorKeys) {
1522 if (
1523 options.considerImplicitTypeConversion &&
1524 typeConversionBinaryOps.has(node.operator) &&
1525 (node.left.type !== "Literal" || node.right.type !== "Literal")
1526 ) {
1527 return true
1528 }
1529 return this.$visitChildren(node, options, visitorKeys)
1530 },
1531 CallExpression() {
1532 return true
1533 },
1534 FunctionExpression() {
1535 return false
1536 },
1537 ImportExpression() {
1538 return true
1539 },
1540 /**
1541 * @param {MemberExpression} node
1542 * @param {HasSideEffectOptions} options
1543 * @param {Record<string, string[]>} visitorKeys
1544 */
1545 MemberExpression(node, options, visitorKeys) {
1546 if (options.considerGetters) {
1547 return true
1548 }
1549 if (
1550 options.considerImplicitTypeConversion &&
1551 node.computed &&
1552 node.property.type !== "Literal"
1553 ) {
1554 return true
1555 }
1556 return this.$visitChildren(node, options, visitorKeys)
1557 },
1558 /**
1559 * @param {MethodDefinition} node
1560 * @param {HasSideEffectOptions} options
1561 * @param {Record<string, string[]>} visitorKeys
1562 */
1563 MethodDefinition(node, options, visitorKeys) {
1564 if (
1565 options.considerImplicitTypeConversion &&
1566 node.computed &&
1567 node.key.type !== "Literal"
1568 ) {
1569 return true
1570 }
1571 return this.$visitChildren(node, options, visitorKeys)
1572 },
1573 NewExpression() {
1574 return true
1575 },
1576 /**
1577 * @param {Property} node
1578 * @param {HasSideEffectOptions} options
1579 * @param {Record<string, string[]>} visitorKeys
1580 */
1581 Property(node, options, visitorKeys) {
1582 if (
1583 options.considerImplicitTypeConversion &&
1584 node.computed &&
1585 node.key.type !== "Literal"
1586 ) {
1587 return true
1588 }
1589 return this.$visitChildren(node, options, visitorKeys)
1590 },
1591 /**
1592 * @param {PropertyDefinition} node
1593 * @param {HasSideEffectOptions} options
1594 * @param {Record<string, string[]>} visitorKeys
1595 */
1596 PropertyDefinition(node, options, visitorKeys) {
1597 if (
1598 options.considerImplicitTypeConversion &&
1599 node.computed &&
1600 node.key.type !== "Literal"
1601 ) {
1602 return true
1603 }
1604 return this.$visitChildren(node, options, visitorKeys)
1605 },
1606 /**
1607 * @param {UnaryExpression} node
1608 * @param {HasSideEffectOptions} options
1609 * @param {Record<string, string[]>} visitorKeys
1610 */
1611 UnaryExpression(node, options, visitorKeys) {
1612 if (node.operator === "delete") {
1613 return true
1614 }
1615 if (
1616 options.considerImplicitTypeConversion &&
1617 typeConversionUnaryOps.has(node.operator) &&
1618 node.argument.type !== "Literal"
1619 ) {
1620 return true
1621 }
1622 return this.$visitChildren(node, options, visitorKeys)
1623 },
1624 UpdateExpression() {
1625 return true
1626 },
1627 YieldExpression() {
1628 return true
1629 },
1630 }),
1631);
1632
1633/**
1634 * Check whether a given node has any side effect or not.
1635 * @param {Node} node The node to get.
1636 * @param {SourceCode} sourceCode The source code object.
1637 * @param {HasSideEffectOptions} [options] The option object.
1638 * @returns {boolean} `true` if the node has a certain side effect.
1639 */
1640function hasSideEffect(node, sourceCode, options = {}) {
1641 const { considerGetters = false, considerImplicitTypeConversion = false } =
1642 options;
1643 return visitor.$visit(
1644 node,
1645 { considerGetters, considerImplicitTypeConversion },
1646 sourceCode.visitorKeys || eslintVisitorKeys.KEYS,
1647 )
1648}
1649
1650/** @typedef {import("estree").Node} Node */
1651/** @typedef {import("@typescript-eslint/types").TSESTree.NewExpression} TSNewExpression */
1652/** @typedef {import("@typescript-eslint/types").TSESTree.CallExpression} TSCallExpression */
1653/** @typedef {import("eslint").SourceCode} SourceCode */
1654/** @typedef {import("eslint").AST.Token} Token */
1655/** @typedef {import("eslint").Rule.Node} RuleNode */
1656
1657/**
1658 * Get the left parenthesis of the parent node syntax if it exists.
1659 * E.g., `if (a) {}` then the `(`.
1660 * @param {Node} node The AST node to check.
1661 * @param {SourceCode} sourceCode The source code object to get tokens.
1662 * @returns {Token|null} The left parenthesis of the parent node syntax
1663 */
1664// eslint-disable-next-line complexity
1665function getParentSyntaxParen(node, sourceCode) {
1666 const parent = /** @type {RuleNode} */ (node).parent;
1667
1668 if (!parent) {
1669 return null
1670 }
1671
1672 switch (parent.type) {
1673 case "CallExpression":
1674 case "NewExpression":
1675 if (parent.arguments.length === 1 && parent.arguments[0] === node) {
1676 return sourceCode.getTokenAfter(
1677 // @ts-expect-error https://github.com/typescript-eslint/typescript-eslint/pull/5384
1678 parent.typeArguments ||
1679 /** @type {RuleNode} */ (
1680 /** @type {unknown} */ (
1681 /** @type {TSNewExpression | TSCallExpression} */ (
1682 parent
1683 ).typeParameters
1684 )
1685 ) ||
1686 parent.callee,
1687 isOpeningParenToken,
1688 )
1689 }
1690 return null
1691
1692 case "DoWhileStatement":
1693 if (parent.test === node) {
1694 return sourceCode.getTokenAfter(
1695 parent.body,
1696 isOpeningParenToken,
1697 )
1698 }
1699 return null
1700
1701 case "IfStatement":
1702 case "WhileStatement":
1703 if (parent.test === node) {
1704 return sourceCode.getFirstToken(parent, 1)
1705 }
1706 return null
1707
1708 case "ImportExpression":
1709 if (parent.source === node) {
1710 return sourceCode.getFirstToken(parent, 1)
1711 }
1712 return null
1713
1714 case "SwitchStatement":
1715 if (parent.discriminant === node) {
1716 return sourceCode.getFirstToken(parent, 1)
1717 }
1718 return null
1719
1720 case "WithStatement":
1721 if (parent.object === node) {
1722 return sourceCode.getFirstToken(parent, 1)
1723 }
1724 return null
1725
1726 default:
1727 return null
1728 }
1729}
1730
1731/**
1732 * Check whether a given node is parenthesized or not.
1733 * @param {number} times The number of parantheses.
1734 * @param {Node} node The AST node to check.
1735 * @param {SourceCode} sourceCode The source code object to get tokens.
1736 * @returns {boolean} `true` if the node is parenthesized the given times.
1737 */
1738/**
1739 * Check whether a given node is parenthesized or not.
1740 * @param {Node} node The AST node to check.
1741 * @param {SourceCode} sourceCode The source code object to get tokens.
1742 * @returns {boolean} `true` if the node is parenthesized.
1743 */
1744/**
1745 * Check whether a given node is parenthesized or not.
1746 * @param {Node|number} timesOrNode The first parameter.
1747 * @param {Node|SourceCode} nodeOrSourceCode The second parameter.
1748 * @param {SourceCode} [optionalSourceCode] The third parameter.
1749 * @returns {boolean} `true` if the node is parenthesized.
1750 */
1751function isParenthesized(
1752 timesOrNode,
1753 nodeOrSourceCode,
1754 optionalSourceCode,
1755) {
1756 /** @type {number} */
1757 let times,
1758 /** @type {RuleNode} */
1759 node,
1760 /** @type {SourceCode} */
1761 sourceCode,
1762 maybeLeftParen,
1763 maybeRightParen;
1764 if (typeof timesOrNode === "number") {
1765 times = timesOrNode | 0;
1766 node = /** @type {RuleNode} */ (nodeOrSourceCode);
1767 sourceCode = /** @type {SourceCode} */ (optionalSourceCode);
1768 if (!(times >= 1)) {
1769 throw new TypeError("'times' should be a positive integer.")
1770 }
1771 } else {
1772 times = 1;
1773 node = /** @type {RuleNode} */ (timesOrNode);
1774 sourceCode = /** @type {SourceCode} */ (nodeOrSourceCode);
1775 }
1776
1777 if (
1778 node == null ||
1779 // `Program` can't be parenthesized
1780 node.parent == null ||
1781 // `CatchClause.param` can't be parenthesized, example `try {} catch (error) {}`
1782 (node.parent.type === "CatchClause" && node.parent.param === node)
1783 ) {
1784 return false
1785 }
1786
1787 maybeLeftParen = maybeRightParen = node;
1788 do {
1789 maybeLeftParen = sourceCode.getTokenBefore(maybeLeftParen);
1790 maybeRightParen = sourceCode.getTokenAfter(maybeRightParen);
1791 } while (
1792 maybeLeftParen != null &&
1793 maybeRightParen != null &&
1794 isOpeningParenToken(maybeLeftParen) &&
1795 isClosingParenToken(maybeRightParen) &&
1796 // Avoid false positive such as `if (a) {}`
1797 maybeLeftParen !== getParentSyntaxParen(node, sourceCode) &&
1798 --times > 0
1799 )
1800
1801 return times === 0
1802}
1803
1804/**
1805 * @author Toru Nagashima <https://github.com/mysticatea>
1806 * See LICENSE file in root directory for full license.
1807 */
1808
1809const placeholder = /\$(?:[$&`']|[1-9][0-9]?)/gu;
1810
1811/** @type {WeakMap<PatternMatcher, {pattern:RegExp,escaped:boolean}>} */
1812const internal = new WeakMap();
1813
1814/**
1815 * Check whether a given character is escaped or not.
1816 * @param {string} str The string to check.
1817 * @param {number} index The location of the character to check.
1818 * @returns {boolean} `true` if the character is escaped.
1819 */
1820function isEscaped(str, index) {
1821 let escaped = false;
1822 for (let i = index - 1; i >= 0 && str.charCodeAt(i) === 0x5c; --i) {
1823 escaped = !escaped;
1824 }
1825 return escaped
1826}
1827
1828/**
1829 * Replace a given string by a given matcher.
1830 * @param {PatternMatcher} matcher The pattern matcher.
1831 * @param {string} str The string to be replaced.
1832 * @param {string} replacement The new substring to replace each matched part.
1833 * @returns {string} The replaced string.
1834 */
1835function replaceS(matcher, str, replacement) {
1836 const chunks = [];
1837 let index = 0;
1838
1839 /**
1840 * @param {string} key The placeholder.
1841 * @param {RegExpExecArray} match The matched information.
1842 * @returns {string} The replaced string.
1843 */
1844 function replacer(key, match) {
1845 switch (key) {
1846 case "$$":
1847 return "$"
1848 case "$&":
1849 return match[0]
1850 case "$`":
1851 return str.slice(0, match.index)
1852 case "$'":
1853 return str.slice(match.index + match[0].length)
1854 default: {
1855 const i = key.slice(1);
1856 if (i in match) {
1857 return match[/** @type {any} */ (i)]
1858 }
1859 return key
1860 }
1861 }
1862 }
1863
1864 for (const match of matcher.execAll(str)) {
1865 chunks.push(str.slice(index, match.index));
1866 chunks.push(
1867 replacement.replace(placeholder, (key) => replacer(key, match)),
1868 );
1869 index = match.index + match[0].length;
1870 }
1871 chunks.push(str.slice(index));
1872
1873 return chunks.join("")
1874}
1875
1876/**
1877 * Replace a given string by a given matcher.
1878 * @param {PatternMatcher} matcher The pattern matcher.
1879 * @param {string} str The string to be replaced.
1880 * @param {(substring: string, ...args: any[]) => string} replace The function to replace each matched part.
1881 * @returns {string} The replaced string.
1882 */
1883function replaceF(matcher, str, replace) {
1884 const chunks = [];
1885 let index = 0;
1886
1887 for (const match of matcher.execAll(str)) {
1888 chunks.push(str.slice(index, match.index));
1889 chunks.push(
1890 String(
1891 replace(
1892 .../** @type {[string, ...string[]]} */ (
1893 /** @type {string[]} */ (match)
1894 ),
1895 match.index,
1896 match.input,
1897 ),
1898 ),
1899 );
1900 index = match.index + match[0].length;
1901 }
1902 chunks.push(str.slice(index));
1903
1904 return chunks.join("")
1905}
1906
1907/**
1908 * The class to find patterns as considering escape sequences.
1909 */
1910class PatternMatcher {
1911 /**
1912 * Initialize this matcher.
1913 * @param {RegExp} pattern The pattern to match.
1914 * @param {{escaped?:boolean}} [options] The options.
1915 */
1916 constructor(pattern, options = {}) {
1917 const { escaped = false } = options;
1918 if (!(pattern instanceof RegExp)) {
1919 throw new TypeError("'pattern' should be a RegExp instance.")
1920 }
1921 if (!pattern.flags.includes("g")) {
1922 throw new Error("'pattern' should contains 'g' flag.")
1923 }
1924
1925 internal.set(this, {
1926 pattern: new RegExp(pattern.source, pattern.flags),
1927 escaped: Boolean(escaped),
1928 });
1929 }
1930
1931 /**
1932 * Find the pattern in a given string.
1933 * @param {string} str The string to find.
1934 * @returns {IterableIterator<RegExpExecArray>} The iterator which iterate the matched information.
1935 */
1936 *execAll(str) {
1937 const { pattern, escaped } =
1938 /** @type {{pattern:RegExp,escaped:boolean}} */ (internal.get(this));
1939 let match = null;
1940 let lastIndex = 0;
1941
1942 pattern.lastIndex = 0;
1943 while ((match = pattern.exec(str)) != null) {
1944 if (escaped || !isEscaped(str, match.index)) {
1945 lastIndex = pattern.lastIndex;
1946 yield match;
1947 pattern.lastIndex = lastIndex;
1948 }
1949 }
1950 }
1951
1952 /**
1953 * Check whether the pattern is found in a given string.
1954 * @param {string} str The string to check.
1955 * @returns {boolean} `true` if the pattern was found in the string.
1956 */
1957 test(str) {
1958 const it = this.execAll(str);
1959 const ret = it.next();
1960 return !ret.done
1961 }
1962
1963 /**
1964 * Replace a given string.
1965 * @param {string} str The string to be replaced.
1966 * @param {(string|((...strs:string[])=>string))} replacer The string or function to replace. This is the same as the 2nd argument of `String.prototype.replace`.
1967 * @returns {string} The replaced string.
1968 */
1969 [Symbol.replace](str, replacer) {
1970 return typeof replacer === "function"
1971 ? replaceF(this, String(str), replacer)
1972 : replaceS(this, String(str), String(replacer))
1973 }
1974}
1975
1976/** @typedef {import("eslint").Scope.Scope} Scope */
1977/** @typedef {import("eslint").Scope.Variable} Variable */
1978/** @typedef {import("eslint").Rule.Node} RuleNode */
1979/** @typedef {import("estree").Node} Node */
1980/** @typedef {import("estree").Expression} Expression */
1981/** @typedef {import("estree").Pattern} Pattern */
1982/** @typedef {import("estree").Identifier} Identifier */
1983/** @typedef {import("estree").SimpleCallExpression} CallExpression */
1984/** @typedef {import("estree").Program} Program */
1985/** @typedef {import("estree").ImportDeclaration} ImportDeclaration */
1986/** @typedef {import("estree").ExportAllDeclaration} ExportAllDeclaration */
1987/** @typedef {import("estree").ExportDefaultDeclaration} ExportDefaultDeclaration */
1988/** @typedef {import("estree").ExportNamedDeclaration} ExportNamedDeclaration */
1989/** @typedef {import("estree").ImportSpecifier} ImportSpecifier */
1990/** @typedef {import("estree").ImportDefaultSpecifier} ImportDefaultSpecifier */
1991/** @typedef {import("estree").ImportNamespaceSpecifier} ImportNamespaceSpecifier */
1992/** @typedef {import("estree").ExportSpecifier} ExportSpecifier */
1993/** @typedef {import("estree").Property} Property */
1994/** @typedef {import("estree").AssignmentProperty} AssignmentProperty */
1995/** @typedef {import("estree").Literal} Literal */
1996/** @typedef {import("@typescript-eslint/types").TSESTree.Node} TSESTreeNode */
1997/** @typedef {import("./types.mjs").ReferenceTrackerOptions} ReferenceTrackerOptions */
1998/**
1999 * @template T
2000 * @typedef {import("./types.mjs").TraceMap<T>} TraceMap
2001 */
2002/**
2003 * @template T
2004 * @typedef {import("./types.mjs").TraceMapObject<T>} TraceMapObject
2005 */
2006/**
2007 * @template T
2008 * @typedef {import("./types.mjs").TrackedReferences<T>} TrackedReferences
2009 */
2010
2011const IMPORT_TYPE = /^(?:Import|Export(?:All|Default|Named))Declaration$/u;
2012
2013/**
2014 * Check whether a given node is an import node or not.
2015 * @param {Node} node
2016 * @returns {node is ImportDeclaration|ExportAllDeclaration|ExportNamedDeclaration&{source: Literal}} `true` if the node is an import node.
2017 */
2018function isHasSource(node) {
2019 return (
2020 IMPORT_TYPE.test(node.type) &&
2021 /** @type {ImportDeclaration|ExportAllDeclaration|ExportNamedDeclaration} */ (
2022 node
2023 ).source != null
2024 )
2025}
2026const has =
2027 /** @type {<T>(traceMap: TraceMap<unknown>, v: T) => v is (string extends T ? string : T)} */ (
2028 Function.call.bind(Object.hasOwnProperty)
2029 );
2030
2031const READ = Symbol("read");
2032const CALL = Symbol("call");
2033const CONSTRUCT = Symbol("construct");
2034const ESM = Symbol("esm");
2035
2036const requireCall = { require: { [CALL]: true } };
2037
2038/**
2039 * Check whether a given variable is modified or not.
2040 * @param {Variable|undefined} variable The variable to check.
2041 * @returns {boolean} `true` if the variable is modified.
2042 */
2043function isModifiedGlobal(variable) {
2044 return (
2045 variable == null ||
2046 variable.defs.length !== 0 ||
2047 variable.references.some((r) => r.isWrite())
2048 )
2049}
2050
2051/**
2052 * Check if the value of a given node is passed through to the parent syntax as-is.
2053 * For example, `a` and `b` in (`a || b` and `c ? a : b`) are passed through.
2054 * @param {Node} node A node to check.
2055 * @returns {node is RuleNode & {parent: Expression}} `true` if the node is passed through.
2056 */
2057function isPassThrough(node) {
2058 const parent = /** @type {TSESTreeNode} */ (node).parent;
2059
2060 if (parent) {
2061 switch (parent.type) {
2062 case "ConditionalExpression":
2063 return parent.consequent === node || parent.alternate === node
2064 case "LogicalExpression":
2065 return true
2066 case "SequenceExpression":
2067 return (
2068 parent.expressions[parent.expressions.length - 1] === node
2069 )
2070 case "ChainExpression":
2071 return true
2072 case "TSAsExpression":
2073 case "TSSatisfiesExpression":
2074 case "TSTypeAssertion":
2075 case "TSNonNullExpression":
2076 case "TSInstantiationExpression":
2077 return true
2078
2079 default:
2080 return false
2081 }
2082 }
2083 return false
2084}
2085
2086/**
2087 * The reference tracker.
2088 */
2089class ReferenceTracker {
2090 /**
2091 * Initialize this tracker.
2092 * @param {Scope} globalScope The global scope.
2093 * @param {object} [options] The options.
2094 * @param {"legacy"|"strict"} [options.mode="strict"] The mode to determine the ImportDeclaration's behavior for CJS modules.
2095 * @param {string[]} [options.globalObjectNames=["global","globalThis","self","window"]] The variable names for Global Object.
2096 */
2097 constructor(globalScope, options = {}) {
2098 const {
2099 mode = "strict",
2100 globalObjectNames = ["global", "globalThis", "self", "window"],
2101 } = options;
2102 /** @private @type {Variable[]} */
2103 this.variableStack = [];
2104 /** @private */
2105 this.globalScope = globalScope;
2106 /** @private */
2107 this.mode = mode;
2108 /** @private */
2109 this.globalObjectNames = globalObjectNames.slice(0);
2110 }
2111
2112 /**
2113 * Iterate the references of global variables.
2114 * @template T
2115 * @param {TraceMap<T>} traceMap The trace map.
2116 * @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
2117 */
2118 *iterateGlobalReferences(traceMap) {
2119 for (const key of Object.keys(traceMap)) {
2120 const nextTraceMap = traceMap[key];
2121 const path = [key];
2122 const variable = this.globalScope.set.get(key);
2123
2124 if (isModifiedGlobal(variable)) {
2125 continue
2126 }
2127
2128 yield* this._iterateVariableReferences(
2129 /** @type {Variable} */ (variable),
2130 path,
2131 nextTraceMap,
2132 true,
2133 );
2134 }
2135
2136 for (const key of this.globalObjectNames) {
2137 /** @type {string[]} */
2138 const path = [];
2139 const variable = this.globalScope.set.get(key);
2140
2141 if (isModifiedGlobal(variable)) {
2142 continue
2143 }
2144
2145 yield* this._iterateVariableReferences(
2146 /** @type {Variable} */ (variable),
2147 path,
2148 traceMap,
2149 false,
2150 );
2151 }
2152 }
2153
2154 /**
2155 * Iterate the references of CommonJS modules.
2156 * @template T
2157 * @param {TraceMap<T>} traceMap The trace map.
2158 * @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
2159 */
2160 *iterateCjsReferences(traceMap) {
2161 for (const { node } of this.iterateGlobalReferences(requireCall)) {
2162 const key = getStringIfConstant(
2163 /** @type {CallExpression} */ (node).arguments[0],
2164 );
2165 if (key == null || !has(traceMap, key)) {
2166 continue
2167 }
2168
2169 const nextTraceMap = traceMap[key];
2170 const path = [key];
2171
2172 if (nextTraceMap[READ]) {
2173 yield {
2174 node,
2175 path,
2176 type: READ,
2177 info: nextTraceMap[READ],
2178 };
2179 }
2180 yield* this._iteratePropertyReferences(
2181 /** @type {CallExpression} */ (node),
2182 path,
2183 nextTraceMap,
2184 );
2185 }
2186 }
2187
2188 /**
2189 * Iterate the references of ES modules.
2190 * @template T
2191 * @param {TraceMap<T>} traceMap The trace map.
2192 * @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
2193 */
2194 *iterateEsmReferences(traceMap) {
2195 const programNode = /** @type {Program} */ (this.globalScope.block);
2196
2197 for (const node of programNode.body) {
2198 if (!isHasSource(node)) {
2199 continue
2200 }
2201 const moduleId = /** @type {string} */ (node.source.value);
2202
2203 if (!has(traceMap, moduleId)) {
2204 continue
2205 }
2206 const nextTraceMap = traceMap[moduleId];
2207 const path = [moduleId];
2208
2209 if (nextTraceMap[READ]) {
2210 yield {
2211 // eslint-disable-next-line object-shorthand -- apply type
2212 node: /** @type {RuleNode} */ (node),
2213 path,
2214 type: READ,
2215 info: nextTraceMap[READ],
2216 };
2217 }
2218
2219 if (node.type === "ExportAllDeclaration") {
2220 for (const key of Object.keys(nextTraceMap)) {
2221 const exportTraceMap = nextTraceMap[key];
2222 if (exportTraceMap[READ]) {
2223 yield {
2224 // eslint-disable-next-line object-shorthand -- apply type
2225 node: /** @type {RuleNode} */ (node),
2226 path: path.concat(key),
2227 type: READ,
2228 info: exportTraceMap[READ],
2229 };
2230 }
2231 }
2232 } else {
2233 for (const specifier of node.specifiers) {
2234 const esm = has(nextTraceMap, ESM);
2235 const it = this._iterateImportReferences(
2236 specifier,
2237 path,
2238 esm
2239 ? nextTraceMap
2240 : this.mode === "legacy"
2241 ? { default: nextTraceMap, ...nextTraceMap }
2242 : { default: nextTraceMap },
2243 );
2244
2245 if (esm) {
2246 yield* it;
2247 } else {
2248 for (const report of it) {
2249 report.path = report.path.filter(exceptDefault);
2250 if (
2251 report.path.length >= 2 ||
2252 report.type !== READ
2253 ) {
2254 yield report;
2255 }
2256 }
2257 }
2258 }
2259 }
2260 }
2261 }
2262
2263 /**
2264 * Iterate the property references for a given expression AST node.
2265 * @template T
2266 * @param {Expression} node The expression AST node to iterate property references.
2267 * @param {TraceMap<T>} traceMap The trace map.
2268 * @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate property references.
2269 */
2270 *iteratePropertyReferences(node, traceMap) {
2271 yield* this._iteratePropertyReferences(node, [], traceMap);
2272 }
2273
2274 /**
2275 * Iterate the references for a given variable.
2276 * @private
2277 * @template T
2278 * @param {Variable} variable The variable to iterate that references.
2279 * @param {string[]} path The current path.
2280 * @param {TraceMapObject<T>} traceMap The trace map.
2281 * @param {boolean} shouldReport = The flag to report those references.
2282 * @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
2283 */
2284 *_iterateVariableReferences(variable, path, traceMap, shouldReport) {
2285 if (this.variableStack.includes(variable)) {
2286 return
2287 }
2288 this.variableStack.push(variable);
2289 try {
2290 for (const reference of variable.references) {
2291 if (!reference.isRead()) {
2292 continue
2293 }
2294 const node = /** @type {RuleNode & Identifier} */ (
2295 reference.identifier
2296 );
2297
2298 if (shouldReport && traceMap[READ]) {
2299 yield { node, path, type: READ, info: traceMap[READ] };
2300 }
2301 yield* this._iteratePropertyReferences(node, path, traceMap);
2302 }
2303 } finally {
2304 this.variableStack.pop();
2305 }
2306 }
2307
2308 /**
2309 * Iterate the references for a given AST node.
2310 * @private
2311 * @template T
2312 * @param {Expression} rootNode The AST node to iterate references.
2313 * @param {string[]} path The current path.
2314 * @param {TraceMapObject<T>} traceMap The trace map.
2315 * @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
2316 */
2317 //eslint-disable-next-line complexity
2318 *_iteratePropertyReferences(rootNode, path, traceMap) {
2319 let node = rootNode;
2320 while (isPassThrough(node)) {
2321 node = node.parent;
2322 }
2323
2324 const parent = /** @type {RuleNode} */ (node).parent;
2325 if (!parent) {
2326 return
2327 }
2328 if (parent.type === "MemberExpression") {
2329 if (parent.object === node) {
2330 const key = getPropertyName(parent);
2331 if (key == null || !has(traceMap, key)) {
2332 return
2333 }
2334
2335 path = path.concat(key); //eslint-disable-line no-param-reassign
2336 const nextTraceMap = traceMap[key];
2337 if (nextTraceMap[READ]) {
2338 yield {
2339 node: parent,
2340 path,
2341 type: READ,
2342 info: nextTraceMap[READ],
2343 };
2344 }
2345 yield* this._iteratePropertyReferences(
2346 parent,
2347 path,
2348 nextTraceMap,
2349 );
2350 }
2351 return
2352 }
2353 if (parent.type === "CallExpression") {
2354 if (parent.callee === node && traceMap[CALL]) {
2355 yield { node: parent, path, type: CALL, info: traceMap[CALL] };
2356 }
2357 return
2358 }
2359 if (parent.type === "NewExpression") {
2360 if (parent.callee === node && traceMap[CONSTRUCT]) {
2361 yield {
2362 node: parent,
2363 path,
2364 type: CONSTRUCT,
2365 info: traceMap[CONSTRUCT],
2366 };
2367 }
2368 return
2369 }
2370 if (parent.type === "AssignmentExpression") {
2371 if (parent.right === node) {
2372 yield* this._iterateLhsReferences(parent.left, path, traceMap);
2373 yield* this._iteratePropertyReferences(parent, path, traceMap);
2374 }
2375 return
2376 }
2377 if (parent.type === "AssignmentPattern") {
2378 if (parent.right === node) {
2379 yield* this._iterateLhsReferences(parent.left, path, traceMap);
2380 }
2381 return
2382 }
2383 if (parent.type === "VariableDeclarator") {
2384 if (parent.init === node) {
2385 yield* this._iterateLhsReferences(parent.id, path, traceMap);
2386 }
2387 }
2388 }
2389
2390 /**
2391 * Iterate the references for a given Pattern node.
2392 * @private
2393 * @template T
2394 * @param {Pattern} patternNode The Pattern node to iterate references.
2395 * @param {string[]} path The current path.
2396 * @param {TraceMapObject<T>} traceMap The trace map.
2397 * @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
2398 */
2399 *_iterateLhsReferences(patternNode, path, traceMap) {
2400 if (patternNode.type === "Identifier") {
2401 const variable = findVariable(this.globalScope, patternNode);
2402 if (variable != null) {
2403 yield* this._iterateVariableReferences(
2404 variable,
2405 path,
2406 traceMap,
2407 false,
2408 );
2409 }
2410 return
2411 }
2412 if (patternNode.type === "ObjectPattern") {
2413 for (const property of patternNode.properties) {
2414 const key = getPropertyName(
2415 /** @type {AssignmentProperty} */ (property),
2416 );
2417
2418 if (key == null || !has(traceMap, key)) {
2419 continue
2420 }
2421
2422 const nextPath = path.concat(key);
2423 const nextTraceMap = traceMap[key];
2424 if (nextTraceMap[READ]) {
2425 yield {
2426 node: /** @type {RuleNode} */ (property),
2427 path: nextPath,
2428 type: READ,
2429 info: nextTraceMap[READ],
2430 };
2431 }
2432 yield* this._iterateLhsReferences(
2433 /** @type {AssignmentProperty} */ (property).value,
2434 nextPath,
2435 nextTraceMap,
2436 );
2437 }
2438 return
2439 }
2440 if (patternNode.type === "AssignmentPattern") {
2441 yield* this._iterateLhsReferences(patternNode.left, path, traceMap);
2442 }
2443 }
2444
2445 /**
2446 * Iterate the references for a given ModuleSpecifier node.
2447 * @private
2448 * @template T
2449 * @param {ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier | ExportSpecifier} specifierNode The ModuleSpecifier node to iterate references.
2450 * @param {string[]} path The current path.
2451 * @param {TraceMapObject<T>} traceMap The trace map.
2452 * @returns {IterableIterator<TrackedReferences<T>>} The iterator to iterate references.
2453 */
2454 *_iterateImportReferences(specifierNode, path, traceMap) {
2455 const type = specifierNode.type;
2456
2457 if (type === "ImportSpecifier" || type === "ImportDefaultSpecifier") {
2458 const key =
2459 type === "ImportDefaultSpecifier"
2460 ? "default"
2461 : specifierNode.imported.type === "Identifier"
2462 ? specifierNode.imported.name
2463 : specifierNode.imported.value;
2464 if (!has(traceMap, key)) {
2465 return
2466 }
2467
2468 path = path.concat(key); //eslint-disable-line no-param-reassign
2469 const nextTraceMap = traceMap[key];
2470 if (nextTraceMap[READ]) {
2471 yield {
2472 node: /** @type {RuleNode} */ (specifierNode),
2473 path,
2474 type: READ,
2475 info: nextTraceMap[READ],
2476 };
2477 }
2478 yield* this._iterateVariableReferences(
2479 /** @type {Variable} */ (
2480 findVariable(this.globalScope, specifierNode.local)
2481 ),
2482 path,
2483 nextTraceMap,
2484 false,
2485 );
2486
2487 return
2488 }
2489
2490 if (type === "ImportNamespaceSpecifier") {
2491 yield* this._iterateVariableReferences(
2492 /** @type {Variable} */ (
2493 findVariable(this.globalScope, specifierNode.local)
2494 ),
2495 path,
2496 traceMap,
2497 false,
2498 );
2499 return
2500 }
2501
2502 if (type === "ExportSpecifier") {
2503 const key =
2504 specifierNode.local.type === "Identifier"
2505 ? specifierNode.local.name
2506 : specifierNode.local.value;
2507 if (!has(traceMap, key)) {
2508 return
2509 }
2510
2511 path = path.concat(key); //eslint-disable-line no-param-reassign
2512 const nextTraceMap = traceMap[key];
2513 if (nextTraceMap[READ]) {
2514 yield {
2515 node: /** @type {RuleNode} */ (specifierNode),
2516 path,
2517 type: READ,
2518 info: nextTraceMap[READ],
2519 };
2520 }
2521 }
2522 }
2523}
2524
2525ReferenceTracker.READ = READ;
2526ReferenceTracker.CALL = CALL;
2527ReferenceTracker.CONSTRUCT = CONSTRUCT;
2528ReferenceTracker.ESM = ESM;
2529
2530/**
2531 * This is a predicate function for Array#filter.
2532 * @param {string} name A name part.
2533 * @param {number} index The index of the name.
2534 * @returns {boolean} `false` if it's default.
2535 */
2536function exceptDefault(name, index) {
2537 return !(index === 1 && name === "default")
2538}
2539
2540/** @typedef {import("./types.mjs").StaticValue} StaticValue */
2541
2542var index = {
2543 CALL,
2544 CONSTRUCT,
2545 ESM,
2546 findVariable,
2547 getFunctionHeadLocation,
2548 getFunctionNameWithKind,
2549 getInnermostScope,
2550 getPropertyName,
2551 getStaticValue,
2552 getStringIfConstant,
2553 hasSideEffect,
2554 isArrowToken,
2555 isClosingBraceToken,
2556 isClosingBracketToken,
2557 isClosingParenToken,
2558 isColonToken,
2559 isCommaToken,
2560 isCommentToken,
2561 isNotArrowToken,
2562 isNotClosingBraceToken,
2563 isNotClosingBracketToken,
2564 isNotClosingParenToken,
2565 isNotColonToken,
2566 isNotCommaToken,
2567 isNotCommentToken,
2568 isNotOpeningBraceToken,
2569 isNotOpeningBracketToken,
2570 isNotOpeningParenToken,
2571 isNotSemicolonToken,
2572 isOpeningBraceToken,
2573 isOpeningBracketToken,
2574 isOpeningParenToken,
2575 isParenthesized,
2576 isSemicolonToken,
2577 PatternMatcher,
2578 READ,
2579 ReferenceTracker,
2580};
2581
2582exports.CALL = CALL;
2583exports.CONSTRUCT = CONSTRUCT;
2584exports.ESM = ESM;
2585exports.PatternMatcher = PatternMatcher;
2586exports.READ = READ;
2587exports.ReferenceTracker = ReferenceTracker;
2588exports["default"] = index;
2589exports.findVariable = findVariable;
2590exports.getFunctionHeadLocation = getFunctionHeadLocation;
2591exports.getFunctionNameWithKind = getFunctionNameWithKind;
2592exports.getInnermostScope = getInnermostScope;
2593exports.getPropertyName = getPropertyName;
2594exports.getStaticValue = getStaticValue;
2595exports.getStringIfConstant = getStringIfConstant;
2596exports.hasSideEffect = hasSideEffect;
2597exports.isArrowToken = isArrowToken;
2598exports.isClosingBraceToken = isClosingBraceToken;
2599exports.isClosingBracketToken = isClosingBracketToken;
2600exports.isClosingParenToken = isClosingParenToken;
2601exports.isColonToken = isColonToken;
2602exports.isCommaToken = isCommaToken;
2603exports.isCommentToken = isCommentToken;
2604exports.isNotArrowToken = isNotArrowToken;
2605exports.isNotClosingBraceToken = isNotClosingBraceToken;
2606exports.isNotClosingBracketToken = isNotClosingBracketToken;
2607exports.isNotClosingParenToken = isNotClosingParenToken;
2608exports.isNotColonToken = isNotColonToken;
2609exports.isNotCommaToken = isNotCommaToken;
2610exports.isNotCommentToken = isNotCommentToken;
2611exports.isNotOpeningBraceToken = isNotOpeningBraceToken;
2612exports.isNotOpeningBracketToken = isNotOpeningBracketToken;
2613exports.isNotOpeningParenToken = isNotOpeningParenToken;
2614exports.isNotSemicolonToken = isNotSemicolonToken;
2615exports.isOpeningBraceToken = isOpeningBraceToken;
2616exports.isOpeningBracketToken = isOpeningBracketToken;
2617exports.isOpeningParenToken = isOpeningParenToken;
2618exports.isParenthesized = isParenthesized;
2619exports.isSemicolonToken = isSemicolonToken;
2620//# sourceMappingURL=index.js.map
Note: See TracBrowser for help on using the repository browser.