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

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