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
478 lines
15 KiB
Plaintext
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)
|
|
}
|
|
}
|