Files
NapCatQQ/.pnpm-store/v3/files/2d/8613c0622e58886e5055d9bbf05e20129c1b18e138b349b629dc0121398dda9af58e9f318583080af8726b7a543c4531ec31be715e878676f836ed4e21353d
T
TanTan e311ca3275
Build NapCat Artifacts / Windows Loader Test (push) Canceled after 0s
Build NapCat Artifacts / Type Check & Test (push) Canceled after 0s
Build NapCat Artifacts / Build NapCat.Framework (push) Canceled after 0s
Build NapCat Artifacts / Build NapCat.Shell (push) Canceled after 0s
docs: 仓库链接指向本镜像仓库
2026-11-13 09:25:58 +08:00

478 lines
15 KiB
Plaintext

"use strict"
const { findVariable } = require("@eslint-community/eslint-utils")
const { getSourceCode } = require("eslint-compat-utils")
module.exports = { buildObjectTypeChecker, buildExpressionTypeProvider }
/**
* @typedef {import("eslint").Rule.RuleContext} RuleContext
* @typedef {import("estree").MemberExpression} MemberExpression
* @typedef {import("estree").Expression} Expression
*/
/**
* @typedef {"Array"
* | "Date"
* | "Function"
* | "Intl.Collator"
* | "Intl.DateTimeFormat"
* | "Intl.ListFormat"
* | "Intl.NumberFormat"
* | "Intl.PluralRules"
* | "Intl.RelativeTimeFormat"
* | "Intl.Segmenter"
* | "Promise"
* | "RegExp"
* | "String"
* | "Symbol"
* | "Int8Array"
* | "Uint8Array"
* | "Uint8ClampedArray"
* | "Int16Array"
* | "Uint16Array"
* | "Int32Array"
* | "Uint32Array"
* | "Float32Array"
* | "Float64Array"
* | "BigInt64Array"
* | "BigUint64Array"
* | "Object"
* | "Number"
* | "Boolean"
* | "BigInt"
* | "null"
* | "undefined"
* } TypeName
*/
/**
* @typedef {Record<
* string,
* WellKnownGlobalFunction
* | WellKnownGlobalObject
* | { type: "undefined" }
* | { type: "Number" }
* | undefined>
* } WellKnownGlobals
*/
/**
* @typedef {object} WellKnownGlobalFunction
* @property {"Function"} type
* @property {TypeName} returnType
*/
/**
* @typedef {object} WellKnownGlobalObject
* @property {"Object"} type
* @property {WellKnownGlobals} properties
*/
/** @type {WellKnownGlobals} */
const WELLKNOWN_GLOBALS = {
String: { type: "Function", returnType: "String" },
Number: { type: "Function", returnType: "Number" },
Boolean: { type: "Function", returnType: "Boolean" },
Symbol: { type: "Function", returnType: "Symbol" },
BigInt: { type: "Function", returnType: "BigInt" },
Object: { type: "Function", returnType: "Object" },
Function: { type: "Function", returnType: "Function" },
Array: { type: "Function", returnType: "Array" },
RegExp: { type: "Function", returnType: "RegExp" },
Date: { type: "Function", returnType: "Date" },
Promise: { type: "Function", returnType: "Promise" },
Int8Array: { type: "Function", returnType: "Int8Array" },
Uint8Array: { type: "Function", returnType: "Uint8Array" },
Uint8ClampedArray: { type: "Function", returnType: "Uint8ClampedArray" },
Int16Array: { type: "Function", returnType: "Int16Array" },
Uint16Array: { type: "Function", returnType: "Uint16Array" },
Int32Array: { type: "Function", returnType: "Int32Array" },
Uint32Array: { type: "Function", returnType: "Uint32Array" },
Float32Array: { type: "Function", returnType: "Float32Array" },
Float64Array: { type: "Function", returnType: "Float64Array" },
BigInt64Array: { type: "Function", returnType: "BigInt64Array" },
BigUint64Array: { type: "Function", returnType: "BigUint64Array" },
ArrayBuffer: { type: "Function", returnType: "ArrayBuffer" },
SharedArrayBuffer: { type: "Function", returnType: "SharedArrayBuffer" },
Intl: {
type: "Object",
properties: {
Collator: { type: "Function", returnType: "Intl.Collator" },
DateTimeFormat: {
type: "Function",
returnType: "Intl.DateTimeFormat",
},
ListFormat: {
type: "Function",
returnType: "Intl.ListFormat",
},
NumberFormat: {
type: "Function",
returnType: "Intl.NumberFormat",
},
PluralRules: {
type: "Function",
returnType: "Intl.PluralRules",
},
RelativeTimeFormat: {
type: "Function",
returnType: "Intl.RelativeTimeFormat",
},
Segmenter: { type: "Function", returnType: "Intl.Segmenter" },
},
},
undefined: { type: "undefined" },
NaN: { type: "Number" },
Infinity: { type: "Number" },
}
/**
* Build object type checker.
* @param {RuleContext} context The rule context.
* @param {boolean} aggressiveResult The value to return if the type cannot be determined.
* @returns {((memberAccessNode: MemberExpression, className: string) => boolean)} Returns an object type checker.
*/
function buildObjectTypeChecker(context, aggressiveResult) {
const getType = buildExpressionTypeProvider(context)
return function (memberAccessNode, className) {
const type = getType(memberAccessNode.object)
if (type == null) {
return aggressiveResult
}
return type === className
}
}
const cache = new WeakMap()
/**
* Build expression type provider.
* @param {RuleContext} context The rule context.
* @returns {((node: Expression) => TypeName | null)} Returns an expression type provider.
*/
function buildExpressionTypeProvider(context) {
const key = getSourceCode(context).ast
let result = cache.get(key)
if (!result) {
cache.set(key, (result = buildExpressionTypeProviderImpl(context)))
}
return result
}
/**
* Build expression type provider.
* @param {RuleContext} context The rule context.
* @returns {((node: Expression) => TypeName | null)} Returns an expression type provider.
*/
function buildExpressionTypeProviderImpl(context) {
/** @type {Record<Expression['type'], (node: Expression) => TypeName | null>} */
const GET_TYPES = {
ArrayExpression: () => "Array",
ObjectExpression: () => "Object",
ArrowFunctionExpression: () => "Function",
FunctionExpression: () => "Function",
Literal: getLiteralType,
TemplateLiteral: () => "String",
Identifier: getIdentifierType,
/** @param {import("estree").BinaryExpression} node */
BinaryExpression: (node) =>
getOperatorType(node.operator, node.left, node.right),
/** @param {import("estree").LogicalExpression} node */
LogicalExpression: (node) =>
getOperatorType(node.operator, node.left, node.right),
/** @param {import("estree").AssignmentExpression} node */
AssignmentExpression: (node) =>
getOperatorType(node.operator, node.left, node.right),
UnaryExpression: getUnaryExpressionType,
UpdateExpression: () => "Number",
ClassExpression: () => "Function",
ChainExpression: (node) => getType(node.expression),
/** @param {import("estree").SequenceExpression} node */
SequenceExpression: (node) =>
getType(node.expressions[node.expressions.length - 1]),
CallExpression: getCallExpressionType,
NewExpression: getCallExpressionType,
TaggedTemplateExpression: getCallExpressionType,
/** @param {import("estree").ConditionalExpression} node */
ConditionalExpression(node) {
const consequent = getType(node.consequent)
const alternate = getType(node.alternate)
return consequent === alternate ? consequent : null
},
}
const tracked = new Map()
return getType
/**
* Gets the type name of the given node.
* @param {Expression} node The Expression node
* @returns {TypeName | null} The type name of expression.
*/
function getType(node) {
if (tracked.has(node)) {
return tracked.get(node)
}
tracked.set(node, null)
try {
const result = GET_TYPES[node.type]?.(node) ?? null
tracked.set(node, result)
return result
} catch {
return null
}
}
/**
* @param {import("estree").Identifier} node
* @returns {import("eslint").Scope.Variable | null}
*/
function findVariableFromIdentifier(node) {
return findVariable(
getSourceCode(context).scopeManager.globalScope,
node,
)
}
/**
* @param {import("estree").Literal} node
* @returns {TypeName | null}
*/
function getLiteralType(node) {
if (node.regex) {
return "RegExp"
}
if (node.bigint) {
return "BigInt"
}
if (node.value == null) {
return "null"
}
const valueType = typeof node.value
if (valueType === "string") {
return "String"
}
if (valueType === "number") {
return "Number"
}
if (valueType === "boolean") {
return "Boolean"
}
return valueType[0].toUpperCase() + valueType.slice(1)
}
/**
* @param {import("estree").Identifier} node
* @returns {TypeName | null}
*/
function getIdentifierType(node) {
const variable = findVariableFromIdentifier(node)
if (variable) {
if (variable.defs.length === 0) {
// It is a global variable
return WELLKNOWN_GLOBALS[node.name]?.type ?? null
} else if (variable.defs.length === 1) {
const def = variable.defs[0]
if (def.type === "Variable") {
if (
// It has an initial value.
def.node.init &&
// It does not write new values.
(def.parent.kind === "const" ||
variable.references.every(
(ref) =>
ref.isReadOnly() ||
ref.identifier === def.name,
))
) {
// The type of the initial value is the type of the variable.
return getType(def.node.init)
}
} else if (def.type === "FunctionName") {
return "Function"
}
}
}
return null
}
/**
* @param {import("estree").BinaryOperator
* | import("estree").LogicalOperator
* | import("estree").AssignmentOperator} operator
* @param {import("estree").Expression} leftNode
* @param {import("estree").Expression} rightNode
* @returns {TypeName | null}
*/
// eslint-disable-next-line complexity
function getOperatorType(operator, leftNode, rightNode) {
if (operator === "=") {
return getType(rightNode)
}
if (operator === "+" || operator === "+=") {
return getPlusOperatorType(leftNode, rightNode)
}
if (
operator === "==" ||
operator === "!=" ||
operator === "===" ||
operator === "!==" ||
operator === "<" ||
operator === "<=" ||
operator === ">" ||
operator === ">=" ||
operator === "in" ||
operator === "instanceof"
) {
return "Boolean"
}
if (
operator === "-" ||
operator === "-=" ||
operator === "*" ||
operator === "*=" ||
operator === "/" ||
operator === "/=" ||
operator === "%" ||
operator === "%=" ||
operator === "^" ||
operator === "^=" ||
operator === "**" ||
operator === "**=" ||
operator === "&" ||
operator === "&=" ||
operator === "|" ||
operator === "|="
) {
const left = getType(leftNode)
const right = getType(rightNode)
if (left === "BigInt" || right === "BigInt") {
return "BigInt"
}
return left == null && right == null ? null : "Number"
}
if (
operator === "<<" ||
operator === "<<=" ||
operator === ">>" ||
operator === ">>=" ||
operator === ">>>" ||
operator === ">>>="
) {
return "Number"
}
if (
operator === "&&" ||
operator === "&&=" ||
operator === "||" ||
operator === "||=" ||
operator === "??" ||
operator === "??="
) {
const left = getType(leftNode)
const right = getType(rightNode)
return left === right ? left : null
}
return null
}
/**
* @param {import("estree").Expression} leftNode
* @param {import("estree").Expression} rightNode
* @returns {TypeName | null}
*/
function getPlusOperatorType(leftNode, rightNode) {
const left = getType(leftNode)
const right = getType(rightNode)
if (left === "String" || right === "String") {
return "String"
}
if (left === "BigInt" || right === "BigInt") {
return "BigInt"
}
if (right === "Number") {
return "Number"
}
if (left === "Number") {
if (right === "null" || right === "undefined") {
return "Number"
}
}
if (right == null) {
return null
}
return "String"
}
/**
* @param {import("estree").UnaryExpression} node
* @returns {TypeName | null}
*/
function getUnaryExpressionType(node) {
if (node.operator === "!" || node.operator === "delete") {
return "Boolean"
}
if (node.operator === "+") {
return "Number"
}
if (node.operator === "-" || node.operator === "~") {
const argument = getType(node.argument)
if (argument === "BigInt") {
return argument
}
return argument == null ? null : "Number"
}
if (node.operator === "typeof") {
return "String"
}
if (node.operator === "void") {
return "undefined"
}
return null
}
/**
* @param {import("estree").SimpleCallExpression | import("estree").NewExpression | import("estree").TaggedTemplateExpression} node
* @returns {TypeName | null}
*/
function getCallExpressionType(node) {
const callee =
node.type === "CallExpression" || node.type === "NewExpression"
? node.callee
: node.tag
if (callee.type === "Identifier") {
if (!isGlobalObject(callee)) {
return null
}
const obj = WELLKNOWN_GLOBALS[callee.name]
return obj?.type === "Function" ? obj.returnType : null
} else if (callee.type === "MemberExpression") {
if (
callee.computed ||
callee.property.type !== "Identifier" ||
!isGlobalObject(callee.object)
) {
return null
}
const obj = WELLKNOWN_GLOBALS[callee.object.name]
if (obj?.type === "Object") {
const prop = obj.properties[callee.property.name]
return prop?.type === "Function" ? prop.returnType : null
}
}
return null
}
/**
* @param {import("estree").Node} node
* @returns {node is import("estree").Identifier}
*/
function isGlobalObject(node) {
if (node.type !== "Identifier") {
return false
}
const variable = findVariableFromIdentifier(node)
return Boolean(variable && variable.defs.length === 0)
}
}