@adguard/eslint-plugin-logger-context
Version:
ESLint plugin that requires AdGuard logger calls to start with a context tag.
1,585 lines (1,460 loc) • 6.01 MB
JavaScript
'use strict';
var require$$0$4 = require('node:fs');
var require$$0$3 = require('node:path');
var require$$2 = require('node:os');
var require$$1$2 = require('node:tty');
var require$$1$3 = require('node:util');
var require$$1$4 = require('node:module');
var require$$0$5 = require('node:assert');
var require$$13$1 = require('node:url');
var require$$0$6 = require('node:events');
var require$$0$7 = require('node:stream');
var require$$1$5 = require('node:crypto');
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getAugmentedNamespace(n) {
if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n;
var f = n.default;
if (typeof f == "function") {
var a = function a () {
if (this instanceof a) {
return Reflect.construct(f, arguments, this.constructor);
}
return f.apply(this, arguments);
};
a.prototype = f.prototype;
} else a = {};
Object.defineProperty(a, '__esModule', {value: true});
Object.keys(n).forEach(function (k) {
var d = Object.getOwnPropertyDescriptor(n, k);
Object.defineProperty(a, k, d.get ? d : {
enumerable: true,
get: function () {
return n[k];
}
});
});
return a;
}
var dist$3 = {};
var astUtils$2 = {};
var eslintUtils$2 = {};
var astUtilities = {};
var eslintUtils$1 = {};
var eslintVisitorKeys$1 = {};
var hasRequiredEslintVisitorKeys$1;
function requireEslintVisitorKeys$1 () {
if (hasRequiredEslintVisitorKeys$1) return eslintVisitorKeys$1;
hasRequiredEslintVisitorKeys$1 = 1;
Object.defineProperty(eslintVisitorKeys$1, '__esModule', { value: true });
/**
* @typedef {{ readonly [type: string]: ReadonlyArray<string> }} VisitorKeys
*/
/**
* @type {VisitorKeys}
*/
const KEYS = {
ArrayExpression: [
"elements"
],
ArrayPattern: [
"elements"
],
ArrowFunctionExpression: [
"params",
"body"
],
AssignmentExpression: [
"left",
"right"
],
AssignmentPattern: [
"left",
"right"
],
AwaitExpression: [
"argument"
],
BinaryExpression: [
"left",
"right"
],
BlockStatement: [
"body"
],
BreakStatement: [
"label"
],
CallExpression: [
"callee",
"arguments"
],
CatchClause: [
"param",
"body"
],
ChainExpression: [
"expression"
],
ClassBody: [
"body"
],
ClassDeclaration: [
"id",
"superClass",
"body"
],
ClassExpression: [
"id",
"superClass",
"body"
],
ConditionalExpression: [
"test",
"consequent",
"alternate"
],
ContinueStatement: [
"label"
],
DebuggerStatement: [],
DoWhileStatement: [
"body",
"test"
],
EmptyStatement: [],
ExperimentalRestProperty: [
"argument"
],
ExperimentalSpreadProperty: [
"argument"
],
ExportAllDeclaration: [
"exported",
"source"
],
ExportDefaultDeclaration: [
"declaration"
],
ExportNamedDeclaration: [
"declaration",
"specifiers",
"source"
],
ExportSpecifier: [
"exported",
"local"
],
ExpressionStatement: [
"expression"
],
ForInStatement: [
"left",
"right",
"body"
],
ForOfStatement: [
"left",
"right",
"body"
],
ForStatement: [
"init",
"test",
"update",
"body"
],
FunctionDeclaration: [
"id",
"params",
"body"
],
FunctionExpression: [
"id",
"params",
"body"
],
Identifier: [],
IfStatement: [
"test",
"consequent",
"alternate"
],
ImportDeclaration: [
"specifiers",
"source"
],
ImportDefaultSpecifier: [
"local"
],
ImportExpression: [
"source"
],
ImportNamespaceSpecifier: [
"local"
],
ImportSpecifier: [
"imported",
"local"
],
JSXAttribute: [
"name",
"value"
],
JSXClosingElement: [
"name"
],
JSXClosingFragment: [],
JSXElement: [
"openingElement",
"children",
"closingElement"
],
JSXEmptyExpression: [],
JSXExpressionContainer: [
"expression"
],
JSXFragment: [
"openingFragment",
"children",
"closingFragment"
],
JSXIdentifier: [],
JSXMemberExpression: [
"object",
"property"
],
JSXNamespacedName: [
"namespace",
"name"
],
JSXOpeningElement: [
"name",
"attributes"
],
JSXOpeningFragment: [],
JSXSpreadAttribute: [
"argument"
],
JSXSpreadChild: [
"expression"
],
JSXText: [],
LabeledStatement: [
"label",
"body"
],
Literal: [],
LogicalExpression: [
"left",
"right"
],
MemberExpression: [
"object",
"property"
],
MetaProperty: [
"meta",
"property"
],
MethodDefinition: [
"key",
"value"
],
NewExpression: [
"callee",
"arguments"
],
ObjectExpression: [
"properties"
],
ObjectPattern: [
"properties"
],
PrivateIdentifier: [],
Program: [
"body"
],
Property: [
"key",
"value"
],
PropertyDefinition: [
"key",
"value"
],
RestElement: [
"argument"
],
ReturnStatement: [
"argument"
],
SequenceExpression: [
"expressions"
],
SpreadElement: [
"argument"
],
StaticBlock: [
"body"
],
Super: [],
SwitchCase: [
"test",
"consequent"
],
SwitchStatement: [
"discriminant",
"cases"
],
TaggedTemplateExpression: [
"tag",
"quasi"
],
TemplateElement: [],
TemplateLiteral: [
"quasis",
"expressions"
],
ThisExpression: [],
ThrowStatement: [
"argument"
],
TryStatement: [
"block",
"handler",
"finalizer"
],
UnaryExpression: [
"argument"
],
UpdateExpression: [
"argument"
],
VariableDeclaration: [
"declarations"
],
VariableDeclarator: [
"id",
"init"
],
WhileStatement: [
"test",
"body"
],
WithStatement: [
"object",
"body"
],
YieldExpression: [
"argument"
]
};
// Types.
const NODE_TYPES = Object.keys(KEYS);
// Freeze the keys.
for (const type of NODE_TYPES) {
Object.freeze(KEYS[type]);
}
Object.freeze(KEYS);
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
/**
* @typedef {import('./visitor-keys.js').VisitorKeys} VisitorKeys
*/
// List to ignore keys.
const KEY_BLACKLIST = new Set([
"parent",
"leadingComments",
"trailingComments"
]);
/**
* Check whether a given key should be used or not.
* @param {string} key The key to check.
* @returns {boolean} `true` if the key should be used.
*/
function filterKey(key) {
return !KEY_BLACKLIST.has(key) && key[0] !== "_";
}
/**
* Get visitor keys of a given node.
* @param {object} node The AST node to get keys.
* @returns {readonly string[]} Visitor keys of the node.
*/
function getKeys(node) {
return Object.keys(node).filter(filterKey);
}
// Disable valid-jsdoc rule because it reports syntax error on the type of @returns.
// eslint-disable-next-line valid-jsdoc
/**
* Make the union set with `KEYS` and given keys.
* @param {VisitorKeys} additionalKeys The additional keys.
* @returns {VisitorKeys} The union set.
*/
function unionWith(additionalKeys) {
const retv = /** @type {{
[type: string]: ReadonlyArray<string>
}} */ (Object.assign({}, KEYS));
for (const type of Object.keys(additionalKeys)) {
if (Object.prototype.hasOwnProperty.call(retv, type)) {
const keys = new Set(additionalKeys[type]);
for (const key of retv[type]) {
keys.add(key);
}
retv[type] = Object.freeze(Array.from(keys));
} else {
retv[type] = Object.freeze(Array.from(additionalKeys[type]));
}
}
return Object.freeze(retv);
}
eslintVisitorKeys$1.KEYS = KEYS;
eslintVisitorKeys$1.getKeys = getKeys;
eslintVisitorKeys$1.unionWith = unionWith;
return eslintVisitorKeys$1;
}
var hasRequiredEslintUtils$2;
function requireEslintUtils$2 () {
if (hasRequiredEslintUtils$2) return eslintUtils$1;
hasRequiredEslintUtils$2 = 1;
(function (exports) {
Object.defineProperty(exports, '__esModule', { value: true });
var eslintVisitorKeys = requireEslintVisitorKeys$1();
/** @typedef {import("eslint").Scope.Scope} Scope */
/** @typedef {import("estree").Node} Node */
/**
* Get the innermost scope which contains a given location.
* @param {Scope} initialScope The initial scope to search.
* @param {Node} node The location to search.
* @returns {Scope} The innermost scope.
*/
function getInnermostScope(initialScope, node) {
const location = /** @type {[number, number]} */ (node.range)[0];
let scope = initialScope;
let found = false;
do {
found = false;
for (const childScope of scope.childScopes) {
const range = /** @type {[number, number]} */ (
childScope.block.range
);
if (range[0] <= location && location < range[1]) {
scope = childScope;
found = true;
break
}
}
} while (found)
return scope
}
/** @typedef {import("eslint").Scope.Scope} Scope */
/** @typedef {import("eslint").Scope.Variable} Variable */
/** @typedef {import("estree").Identifier} Identifier */
/**
* Find the variable of a given name.
* @param {Scope} initialScope The scope to start finding.
* @param {string|Identifier} nameOrNode The variable name to find. If this is a Node object then it should be an Identifier node.
* @returns {Variable|null} The found variable or null.
*/
function findVariable(initialScope, nameOrNode) {
let name = "";
/** @type {Scope|null} */
let scope = initialScope;
if (typeof nameOrNode === "string") {
name = nameOrNode;
} else {
name = nameOrNode.name;
scope = getInnermostScope(scope, nameOrNode);
}
while (scope != null) {
const variable = scope.set.get(name);
if (variable != null) {
return variable
}
scope = scope.upper;
}
return null
}
/** @typedef {import("eslint").AST.Token} Token */
/** @typedef {import("estree").Comment} Comment */
/** @typedef {import("./types.mjs").ArrowToken} ArrowToken */
/** @typedef {import("./types.mjs").CommaToken} CommaToken */
/** @typedef {import("./types.mjs").SemicolonToken} SemicolonToken */
/** @typedef {import("./types.mjs").ColonToken} ColonToken */
/** @typedef {import("./types.mjs").OpeningParenToken} OpeningParenToken */
/** @typedef {import("./types.mjs").ClosingParenToken} ClosingParenToken */
/** @typedef {import("./types.mjs").OpeningBracketToken} OpeningBracketToken */
/** @typedef {import("./types.mjs").ClosingBracketToken} ClosingBracketToken */
/** @typedef {import("./types.mjs").OpeningBraceToken} OpeningBraceToken */
/** @typedef {import("./types.mjs").ClosingBraceToken} ClosingBraceToken */
/**
* @template {string} Value
* @typedef {import("./types.mjs").PunctuatorToken<Value>} PunctuatorToken
*/
/** @typedef {Comment | Token} CommentOrToken */
/**
* Creates the negate function of the given function.
* @param {function(CommentOrToken):boolean} f - The function to negate.
* @returns {function(CommentOrToken):boolean} Negated function.
*/
function negate(f) {
return (token) => !f(token)
}
/**
* Checks if the given token is a PunctuatorToken with the given value
* @template {string} Value
* @param {CommentOrToken} token - The token to check.
* @param {Value} value - The value to check.
* @returns {token is PunctuatorToken<Value>} `true` if the token is a PunctuatorToken with the given value.
*/
function isPunctuatorTokenWithValue(token, value) {
return token.type === "Punctuator" && token.value === value
}
/**
* Checks if the given token is an arrow token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is ArrowToken} `true` if the token is an arrow token.
*/
function isArrowToken(token) {
return isPunctuatorTokenWithValue(token, "=>")
}
/**
* Checks if the given token is a comma token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is CommaToken} `true` if the token is a comma token.
*/
function isCommaToken(token) {
return isPunctuatorTokenWithValue(token, ",")
}
/**
* Checks if the given token is a semicolon token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is SemicolonToken} `true` if the token is a semicolon token.
*/
function isSemicolonToken(token) {
return isPunctuatorTokenWithValue(token, ";")
}
/**
* Checks if the given token is a colon token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is ColonToken} `true` if the token is a colon token.
*/
function isColonToken(token) {
return isPunctuatorTokenWithValue(token, ":")
}
/**
* Checks if the given token is an opening parenthesis token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is OpeningParenToken} `true` if the token is an opening parenthesis token.
*/
function isOpeningParenToken(token) {
return isPunctuatorTokenWithValue(token, "(")
}
/**
* Checks if the given token is a closing parenthesis token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is ClosingParenToken} `true` if the token is a closing parenthesis token.
*/
function isClosingParenToken(token) {
return isPunctuatorTokenWithValue(token, ")")
}
/**
* Checks if the given token is an opening square bracket token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is OpeningBracketToken} `true` if the token is an opening square bracket token.
*/
function isOpeningBracketToken(token) {
return isPunctuatorTokenWithValue(token, "[")
}
/**
* Checks if the given token is a closing square bracket token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is ClosingBracketToken} `true` if the token is a closing square bracket token.
*/
function isClosingBracketToken(token) {
return isPunctuatorTokenWithValue(token, "]")
}
/**
* Checks if the given token is an opening brace token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is OpeningBraceToken} `true` if the token is an opening brace token.
*/
function isOpeningBraceToken(token) {
return isPunctuatorTokenWithValue(token, "{")
}
/**
* Checks if the given token is a closing brace token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is ClosingBraceToken} `true` if the token is a closing brace token.
*/
function isClosingBraceToken(token) {
return isPunctuatorTokenWithValue(token, "}")
}
/**
* Checks if the given token is a comment token or not.
* @param {CommentOrToken} token - The token to check.
* @returns {token is Comment} `true` if the token is a comment token.
*/
function isCommentToken(token) {
return ["Block", "Line", "Shebang"].includes(token.type)
}
const isNotArrowToken = negate(isArrowToken);
const isNotCommaToken = negate(isCommaToken);
const isNotSemicolonToken = negate(isSemicolonToken);
const isNotColonToken = negate(isColonToken);
const isNotOpeningParenToken = negate(isOpeningParenToken);
const isNotClosingParenToken = negate(isClosingParenToken);
const isNotOpeningBracketToken = negate(isOpeningBracketToken);
const isNotClosingBracketToken = negate(isClosingBracketToken);
const isNotOpeningBraceToken = negate(isOpeningBraceToken);
const isNotClosingBraceToken = negate(isClosingBraceToken);
const isNotCommentToken = negate(isCommentToken);
/** @typedef {import("eslint").Rule.Node} RuleNode */
/** @typedef {import("eslint").SourceCode} SourceCode */
/** @typedef {import("eslint").AST.Token} Token */
/** @typedef {import("estree").Function} FunctionNode */
/** @typedef {import("estree").FunctionDeclaration} FunctionDeclaration */
/** @typedef {import("estree").FunctionExpression} FunctionExpression */
/** @typedef {import("estree").SourceLocation} SourceLocation */
/** @typedef {import("estree").Position} Position */
/**
* Get the `(` token of the given function node.
* @param {FunctionExpression | FunctionDeclaration} node - The function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {Token} `(` token.
*/
function getOpeningParenOfParams(node, sourceCode) {
return node.id
? /** @type {Token} */ (
sourceCode.getTokenAfter(node.id, isOpeningParenToken)
)
: /** @type {Token} */ (
sourceCode.getFirstToken(node, isOpeningParenToken)
)
}
/**
* Get the location of the given function node for reporting.
* @param {FunctionNode} node - The function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {SourceLocation|null} The location of the function node for reporting.
*/
function getFunctionHeadLocation(node, sourceCode) {
const parent = /** @type {RuleNode} */ (node).parent;
/** @type {Position|null} */
let start = null;
/** @type {Position|null} */
let end = null;
if (node.type === "ArrowFunctionExpression") {
const arrowToken = /** @type {Token} */ (
sourceCode.getTokenBefore(node.body, isArrowToken)
);
start = arrowToken.loc.start;
end = arrowToken.loc.end;
} else if (
parent.type === "Property" ||
parent.type === "MethodDefinition" ||
parent.type === "PropertyDefinition"
) {
start = /** @type {SourceLocation} */ (parent.loc).start;
end = getOpeningParenOfParams(node, sourceCode).loc.start;
} else {
start = /** @type {SourceLocation} */ (node.loc).start;
end = getOpeningParenOfParams(node, sourceCode).loc.start;
}
return {
start: { ...start },
end: { ...end },
}
}
/* globals globalThis, global, self, window */
/** @typedef {import("./types.mjs").StaticValue} StaticValue */
/** @typedef {import("eslint").Scope.Scope} Scope */
/** @typedef {import("estree").Node} Node */
/** @typedef {import("@typescript-eslint/types").TSESTree.Node} TSESTreeNode */
/** @typedef {import("@typescript-eslint/types").TSESTree.AST_NODE_TYPES} TSESTreeNodeTypes */
/** @typedef {import("@typescript-eslint/types").TSESTree.MemberExpression} MemberExpression */
/** @typedef {import("@typescript-eslint/types").TSESTree.Property} Property */
/** @typedef {import("@typescript-eslint/types").TSESTree.RegExpLiteral} RegExpLiteral */
/** @typedef {import("@typescript-eslint/types").TSESTree.BigIntLiteral} BigIntLiteral */
/** @typedef {import("@typescript-eslint/types").TSESTree.Literal} Literal */
const globalObject =
typeof globalThis !== "undefined"
? globalThis
: // @ts-ignore
typeof self !== "undefined"
? // @ts-ignore
self
: // @ts-ignore
typeof window !== "undefined"
? // @ts-ignore
window
: typeof commonjsGlobal !== "undefined"
? commonjsGlobal
: {};
const builtinNames = Object.freeze(
new Set([
"Array",
"ArrayBuffer",
"BigInt",
"BigInt64Array",
"BigUint64Array",
"Boolean",
"DataView",
"Date",
"decodeURI",
"decodeURIComponent",
"encodeURI",
"encodeURIComponent",
"escape",
"Float32Array",
"Float64Array",
"Function",
"Infinity",
"Int16Array",
"Int32Array",
"Int8Array",
"isFinite",
"isNaN",
"isPrototypeOf",
"JSON",
"Map",
"Math",
"NaN",
"Number",
"Object",
"parseFloat",
"parseInt",
"Promise",
"Proxy",
"Reflect",
"RegExp",
"Set",
"String",
"Symbol",
"Uint16Array",
"Uint32Array",
"Uint8Array",
"Uint8ClampedArray",
"undefined",
"unescape",
"WeakMap",
"WeakSet",
]),
);
const callAllowed = new Set(
[
Array.isArray,
Array.of,
Array.prototype.at,
Array.prototype.concat,
Array.prototype.entries,
Array.prototype.every,
Array.prototype.filter,
Array.prototype.find,
Array.prototype.findIndex,
Array.prototype.flat,
Array.prototype.includes,
Array.prototype.indexOf,
Array.prototype.join,
Array.prototype.keys,
Array.prototype.lastIndexOf,
Array.prototype.slice,
Array.prototype.some,
Array.prototype.toString,
Array.prototype.values,
typeof BigInt === "function" ? BigInt : undefined,
Boolean,
Date,
Date.parse,
decodeURI,
decodeURIComponent,
encodeURI,
encodeURIComponent,
escape,
isFinite,
isNaN,
// @ts-ignore
isPrototypeOf,
Map,
Map.prototype.entries,
Map.prototype.get,
Map.prototype.has,
Map.prototype.keys,
Map.prototype.values,
.../** @type {(keyof typeof Math)[]} */ (
Object.getOwnPropertyNames(Math)
)
.filter((k) => k !== "random")
.map((k) => Math[k])
.filter((f) => typeof f === "function"),
Number,
Number.isFinite,
Number.isNaN,
Number.parseFloat,
Number.parseInt,
Number.prototype.toExponential,
Number.prototype.toFixed,
Number.prototype.toPrecision,
Number.prototype.toString,
Object,
Object.entries,
Object.is,
Object.isExtensible,
Object.isFrozen,
Object.isSealed,
Object.keys,
Object.values,
parseFloat,
parseInt,
RegExp,
Set,
Set.prototype.entries,
Set.prototype.has,
Set.prototype.keys,
Set.prototype.values,
String,
String.fromCharCode,
String.fromCodePoint,
String.raw,
String.prototype.at,
String.prototype.charAt,
String.prototype.charCodeAt,
String.prototype.codePointAt,
String.prototype.concat,
String.prototype.endsWith,
String.prototype.includes,
String.prototype.indexOf,
String.prototype.lastIndexOf,
String.prototype.normalize,
String.prototype.padEnd,
String.prototype.padStart,
String.prototype.slice,
String.prototype.startsWith,
String.prototype.substr,
String.prototype.substring,
String.prototype.toLowerCase,
String.prototype.toString,
String.prototype.toUpperCase,
String.prototype.trim,
String.prototype.trimEnd,
String.prototype.trimLeft,
String.prototype.trimRight,
String.prototype.trimStart,
Symbol.for,
Symbol.keyFor,
unescape,
].filter((f) => typeof f === "function"),
);
const callPassThrough = new Set([
Object.freeze,
Object.preventExtensions,
Object.seal,
]);
/** @type {ReadonlyArray<readonly [Function, ReadonlySet<string>]>} */
const getterAllowed = [
[Map, new Set(["size"])],
[
RegExp,
new Set([
"dotAll",
"flags",
"global",
"hasIndices",
"ignoreCase",
"multiline",
"source",
"sticky",
"unicode",
]),
],
[Set, new Set(["size"])],
];
/**
* Get the property descriptor.
* @param {object} object The object to get.
* @param {string|number|symbol} name The property name to get.
*/
function getPropertyDescriptor(object, name) {
let x = object;
while ((typeof x === "object" || typeof x === "function") && x !== null) {
const d = Object.getOwnPropertyDescriptor(x, name);
if (d) {
return d
}
x = Object.getPrototypeOf(x);
}
return null
}
/**
* Check if a property is getter or not.
* @param {object} object The object to check.
* @param {string|number|symbol} name The property name to check.
*/
function isGetter(object, name) {
const d = getPropertyDescriptor(object, name);
return d != null && d.get != null
}
/**
* Get the element values of a given node list.
* @param {(Node|TSESTreeNode|null)[]} nodeList The node list to get values.
* @param {Scope|undefined|null} initialScope The initial scope to find variables.
* @returns {any[]|null} The value list if all nodes are constant. Otherwise, null.
*/
function getElementValues(nodeList, initialScope) {
const valueList = [];
for (let i = 0; i < nodeList.length; ++i) {
const elementNode = nodeList[i];
if (elementNode == null) {
valueList.length = i + 1;
} else if (elementNode.type === "SpreadElement") {
const argument = getStaticValueR(elementNode.argument, initialScope);
if (argument == null) {
return null
}
valueList.push(.../** @type {Iterable<any>} */ (argument.value));
} else {
const element = getStaticValueR(elementNode, initialScope);
if (element == null) {
return null
}
valueList.push(element.value);
}
}
return valueList
}
/**
* Returns whether the given variable is never written to after initialization.
* @param {import("eslint").Scope.Variable} variable
* @returns {boolean}
*/
function isEffectivelyConst(variable) {
const refs = variable.references;
const inits = refs.filter((r) => r.init).length;
const reads = refs.filter((r) => r.isReadOnly()).length;
if (inits === 1 && reads + inits === refs.length) {
// there is only one init and all other references only read
return true
}
return false
}
/**
* @template {TSESTreeNodeTypes} T
* @callback VisitorCallback
* @param {TSESTreeNode & { type: T }} node
* @param {Scope|undefined|null} initialScope
* @returns {StaticValue | null}
*/
/**
* @typedef { { [K in TSESTreeNodeTypes]?: VisitorCallback<K> } } Operations
*/
/**
* @type {Operations}
*/
const operations = Object.freeze({
ArrayExpression(node, initialScope) {
const elements = getElementValues(node.elements, initialScope);
return elements != null ? { value: elements } : null
},
AssignmentExpression(node, initialScope) {
if (node.operator === "=") {
return getStaticValueR(node.right, initialScope)
}
return null
},
//eslint-disable-next-line complexity
BinaryExpression(node, initialScope) {
if (node.operator === "in" || node.operator === "instanceof") {
// Not supported.
return null
}
const left = getStaticValueR(node.left, initialScope);
const right = getStaticValueR(node.right, initialScope);
if (left != null && right != null) {
switch (node.operator) {
case "==":
return { value: left.value == right.value } //eslint-disable-line eqeqeq
case "!=":
return { value: left.value != right.value } //eslint-disable-line eqeqeq
case "===":
return { value: left.value === right.value }
case "!==":
return { value: left.value !== right.value }
case "<":
return {
value:
/** @type {any} */ (left.value) <
/** @type {any} */ (right.value),
}
case "<=":
return {
value:
/** @type {any} */ (left.value) <=
/** @type {any} */ (right.value),
}
case ">":
return {
value:
/** @type {any} */ (left.value) >
/** @type {any} */ (right.value),
}
case ">=":
return {
value:
/** @type {any} */ (left.value) >=
/** @type {any} */ (right.value),
}
case "<<":
return {
value:
/** @type {any} */ (left.value) <<
/** @type {any} */ (right.value),
}
case ">>":
return {
value:
/** @type {any} */ (left.value) >>
/** @type {any} */ (right.value),
}
case ">>>":
return {
value:
/** @type {any} */ (left.value) >>>
/** @type {any} */ (right.value),
}
case "+":
return {
value:
/** @type {any} */ (left.value) +
/** @type {any} */ (right.value),
}
case "-":
return {
value:
/** @type {any} */ (left.value) -
/** @type {any} */ (right.value),
}
case "*":
return {
value:
/** @type {any} */ (left.value) *
/** @type {any} */ (right.value),
}
case "/":
return {
value:
/** @type {any} */ (left.value) /
/** @type {any} */ (right.value),
}
case "%":
return {
value:
/** @type {any} */ (left.value) %
/** @type {any} */ (right.value),
}
case "**":
return {
value:
/** @type {any} */ (left.value) **
/** @type {any} */ (right.value),
}
case "|":
return {
value:
/** @type {any} */ (left.value) |
/** @type {any} */ (right.value),
}
case "^":
return {
value:
/** @type {any} */ (left.value) ^
/** @type {any} */ (right.value),
}
case "&":
return {
value:
/** @type {any} */ (left.value) &
/** @type {any} */ (right.value),
}
// no default
}
}
return null
},
CallExpression(node, initialScope) {
const calleeNode = node.callee;
const args = getElementValues(node.arguments, initialScope);
if (args != null) {
if (calleeNode.type === "MemberExpression") {
if (calleeNode.property.type === "PrivateIdentifier") {
return null
}
const object = getStaticValueR(calleeNode.object, initialScope);
if (object != null) {
if (
object.value == null &&
(object.optional || node.optional)
) {
return { value: undefined, optional: true }
}
const property = getStaticPropertyNameValue(
calleeNode,
initialScope,
);
if (property != null) {
const receiver =
/** @type {Record<PropertyKey, (...args: any[]) => any>} */ (
object.value
);
const methodName = /** @type {PropertyKey} */ (
property.value
);
if (callAllowed.has(receiver[methodName])) {
return {
value: receiver[methodName](...args),
}
}
if (callPassThrough.has(receiver[methodName])) {
return { value: args[0] }
}
}
}
} else {
const callee = getStaticValueR(calleeNode, initialScope);
if (callee != null) {
if (callee.value == null && node.optional) {
return { value: undefined, optional: true }
}
const func = /** @type {(...args: any[]) => any} */ (
callee.value
);
if (callAllowed.has(func)) {
return { value: func(...args) }
}
if (callPassThrough.has(func)) {
return { value: args[0] }
}
}
}
}
return null
},
ConditionalExpression(node, initialScope) {
const test = getStaticValueR(node.test, initialScope);
if (test != null) {
return test.value
? getStaticValueR(node.consequent, initialScope)
: getStaticValueR(node.alternate, initialScope)
}
return null
},
ExpressionStatement(node, initialScope) {
return getStaticValueR(node.expression, initialScope)
},
Identifier(node, initialScope) {
if (initialScope != null) {
const variable = findVariable(initialScope, node);
// Built-in globals.
if (
variable != null &&
variable.defs.length === 0 &&
builtinNames.has(variable.name) &&
variable.name in globalObject
) {
return { value: globalObject[variable.name] }
}
// Constants.
if (variable != null && variable.defs.length === 1) {
const def = variable.defs[0];
if (
def.parent &&
def.type === "Variable" &&
(def.parent.kind === "const" ||
isEffectivelyConst(variable)) &&
// TODO(mysticatea): don't support destructuring here.
def.node.id.type === "Identifier"
) {
return getStaticValueR(def.node.init, initialScope)
}
}
}
return null
},
Literal(node) {
const literal =
/** @type {Partial<Literal> & Partial<RegExpLiteral> & Partial<BigIntLiteral>} */ (
node
);
//istanbul ignore if : this is implementation-specific behavior.
if (
(literal.regex != null || literal.bigint != null) &&
literal.value == null
) {
// It was a RegExp/BigInt literal, but Node.js didn't support it.
return null
}
return { value: literal.value }
},
LogicalExpression(node, initialScope) {
const left = getStaticValueR(node.left, initialScope);
if (left != null) {
if (
(node.operator === "||" && Boolean(left.value) === true) ||
(node.operator === "&&" && Boolean(left.value) === false) ||
(node.operator === "??" && left.value != null)
) {
return left
}
const right = getStaticValueR(node.right, initialScope);
if (right != null) {
return right
}
}
return null
},
MemberExpression(node, initialScope) {
if (node.property.type === "PrivateIdentifier") {
return null
}
const object = getStaticValueR(node.object, initialScope);
if (object != null) {
if (object.value == null && (object.optional || node.optional)) {
return { value: undefined, optional: true }
}
const property = getStaticPropertyNameValue(node, initialScope);
if (property != null) {
if (
!isGetter(
/** @type {object} */ (object.value),
/** @type {PropertyKey} */ (property.value),
)
) {
return {
value: /** @type {Record<PropertyKey, unknown>} */ (
object.value
)[/** @type {PropertyKey} */ (property.value)],
}
}
for (const [classFn, allowed] of getterAllowed) {
if (
object.value instanceof classFn &&
allowed.has(/** @type {string} */ (property.value))
) {
return {
value: /** @type {Record<PropertyKey, unknown>} */ (
object.value
)[/** @type {PropertyKey} */ (property.value)],
}
}
}
}
}
return null
},
ChainExpression(node, initialScope) {
const expression = getStaticValueR(node.expression, initialScope);
if (expression != null) {
return { value: expression.value }
}
return null
},
NewExpression(node, initialScope) {
const callee = getStaticValueR(node.callee, initialScope);
const args = getElementValues(node.arguments, initialScope);
if (callee != null && args != null) {
const Func = /** @type {new (...args: any[]) => any} */ (
callee.value
);
if (callAllowed.has(Func)) {
return { value: new Func(...args) }
}
}
return null
},
ObjectExpression(node, initialScope) {
/** @type {Record<PropertyKey, unknown>} */
const object = {};
for (const propertyNode of node.properties) {
if (propertyNode.type === "Property") {
if (propertyNode.kind !== "init") {
return null
}
const key = getStaticPropertyNameValue(
propertyNode,
initialScope,
);
const value = getStaticValueR(propertyNode.value, initialScope);
if (key == null || value == null) {
return null
}
object[/** @type {PropertyKey} */ (key.value)] = value.value;
} else if (
propertyNode.type === "SpreadElement" ||
// @ts-expect-error -- Backward compatibility
propertyNode.type === "ExperimentalSpreadProperty"
) {
const argument = getStaticValueR(
propertyNode.argument,
initialScope,
);
if (argument == null) {
return null
}
Object.assign(object, argument.value);
} else {
return null
}
}
return { value: object }
},
SequenceExpression(node, initialScope) {
const last = node.expressions[node.expressions.length - 1];
return getStaticValueR(last, initialScope)
},
TaggedTemplateExpression(node, initialScope) {
const tag = getStaticValueR(node.tag, initialScope);
const expressions = getElementValues(
node.quasi.expressions,
initialScope,
);
if (tag != null && expressions != null) {
const func = /** @type {(...args: any[]) => any} */ (tag.value);
/** @type {any[] & { raw?: string[] }} */
const strings = node.quasi.quasis.map((q) => q.value.cooked);
strings.raw = node.quasi.quasis.map((q) => q.value.raw);
if (func === String.raw) {
return { value: func(strings, ...expressions) }
}
}
return null
},
TemplateLiteral(node, initialScope) {
const expressions = getElementValues(node.expressions, initialScope);
if (expressions != null) {
let value = node.quasis[0].value.cooked;
for (let i = 0; i < expressions.length; ++i) {
value += expressions[i];
value += /** @type {string} */ (node.quasis[i + 1].value.cooked);
}
return { value }
}
return null
},
UnaryExpression(node, initialScope) {
if (node.operator === "delete") {
// Not supported.
return null
}
if (node.operator === "void") {
return { value: undefined }
}
const arg = getStaticValueR(node.argument, initialScope);
if (arg != null) {
switch (node.operator) {
case "-":
return { value: -(/** @type {any} */ (arg.value)) }
case "+":
return { value: +(/** @type {any} */ (arg.value)) } //eslint-disable-line no-implicit-coercion
case "!":
return { value: !arg.value }
case "~":
return { value: ~(/** @type {any} */ (arg.value)) }
case "typeof":
return { value: typeof arg.value }
// no default
}
}
return null
},
TSAsExpression(node, initialScope) {
return getStaticValueR(node.expression, initialScope)
},
TSSatisfiesExpression(node, initialScope) {
return getStaticValueR(node.expression, initialScope)
},
TSTypeAssertion(node, initialScope) {
return getStaticValueR(node.expression, initialScope)
},
TSNonNullExpression(node, initialScope) {
return getStaticValueR(node.expression, initialScope)
},
TSInstantiationExpression(node, initialScope) {
return getStaticValueR(node.expression, initialScope)
},
});
/**
* Get the value of a given node if it's a static value.
* @param {Node|TSESTreeNode|null|undefined} node The node to get.
* @param {Scope|undefined|null} initialScope The scope to start finding variable.
* @returns {StaticValue|null} The static value of the node, or `null`.
*/
function getStaticValueR(node, initialScope) {
if (node != null && Object.hasOwnProperty.call(operations, node.type)) {
return /** @type {VisitorCallback<any>} */ (operations[node.type])(
/** @type {TSESTreeNode} */ (node),
initialScope,
)
}
return null
}
/**
* Get the static value of property name from a MemberExpression node or a Property node.
* @param {MemberExpression|Property} node The node to get.
* @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.
* @returns {StaticValue|null} The static value of the property name of the node, or `null`.
*/
function getStaticPropertyNameValue(node, initialScope) {
const nameNode = node.type === "Property" ? node.key : node.property;
if (node.computed) {
return getStaticValueR(nameNode, initialScope)
}
if (nameNode.type === "Identifier") {
return { value: nameNode.name }
}
if (nameNode.type === "Literal") {
if (/** @type {Partial<BigIntLiteral>} */ (nameNode).bigint) {
return { value: /** @type {BigIntLiteral} */ (nameNode).bigint }
}
return { value: String(nameNode.value) }
}
return null
}
/**
* Get the value of a given node if it's a static value.
* @param {Node} node The node to get.
* @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.
* @returns {StaticValue | null} The static value of the node, or `null`.
*/
function getStaticValue(node, initialScope = null) {
try {
return getStaticValueR(node, initialScope)
} catch (_error) {
return null
}
}
/** @typedef {import("eslint").Scope.Scope} Scope */
/** @typedef {import("estree").Node} Node */
/** @typedef {import("estree").RegExpLiteral} RegExpLiteral */
/** @typedef {import("estree").BigIntLiteral} BigIntLiteral */
/** @typedef {import("estree").SimpleLiteral} SimpleLiteral */
/**
* Get the value of a given node if it's a literal or a template literal.
* @param {Node} node The node to get.
* @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.
* @returns {string|null} The value of the node, or `null`.
*/
function getStringIfConstant(node, initialScope = null) {
// Handle the literals that the platform doesn't support natively.
if (node && node.type === "Literal" && node.value === null) {
const literal =
/** @type {Partial<SimpleLiteral> & Partial<RegExpLiteral> & Partial<BigIntLiteral>} */ (
node
);
if (literal.regex) {
return `/${literal.regex.pattern}/${literal.regex.flags}`
}
if (literal.bigint) {
return literal.bigint
}
}
const evaluated = getStaticValu