nixos/home/ags/node_modules/eslint-plugin-es-x/lib/rules/no-error-cause.js
2024-06-13 00:09:21 +02:00

193 lines
5.9 KiB
JavaScript

/**
* @author Sosuke Suzuki <https://github.com/sosukesuzuki>
* See LICENSE file in root directory for full license.
*/
"use strict"
const {
CONSTRUCT,
READ,
ReferenceTracker,
getPropertyName,
} = require("@eslint-community/eslint-utils")
const { getSourceCode } = require("eslint-compat-utils")
/**
* @typedef {import("estree").Node} Node
* @typedef {import("estree").ClassExpression | import("estree").ClassDeclaration} ClassNode
* @typedef {import("estree").CallExpression} CallExpression
*/
const errorConstructorNames = [
"Error",
"EvalError",
"RangeError",
"ReferenceError",
"SyntaxError",
"TypeError",
"URIError",
"AggregateError",
]
const errorsTraceMap = {}
for (const errorConstructorName of errorConstructorNames) {
errorsTraceMap[errorConstructorName] = { [CONSTRUCT]: true, [READ]: true }
}
/**
* @param {Node} node
* @returns {boolean}
*/
function isSuperCall(node) {
return node.type === "CallExpression" && node.callee.type === "Super"
}
/**
* @param {Node|undefined} node
* @returns {boolean}
*/
function isSpreadElement(node) {
return node && node.type === "SpreadElement"
}
/**
* @param {Node} node
* @returns {ClassNode | null}
*/
function findClassFromAncestors(node) {
if (node.type !== "ClassExpression" && node.type !== "ClassDeclaration") {
return findClassFromAncestors(node.parent)
}
if (!node) {
return null
}
return node
}
module.exports = {
meta: {
docs: {
description: "disallow Error Cause.",
category: "ES2022",
recommended: false,
url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-error-cause.html",
},
fixable: null,
messages: {
forbidden: "ES2022 Error Cause is forbidden.",
},
schema: [],
type: "problem",
},
create(context) {
const sourceCode = getSourceCode(context)
/** @type {Array<{ classNode: ClassNode, superCallNode: CallExpression }>} */
const maybeErrorSubclasses = []
/** @type {Array<{ classNode: ClassNode, superCallNode: CallExpression }>} */
const maybeAggregateErrorSubclasses = []
/**
* Checks if the received node is a constructor call with cause option.
* e.g. `new Error("message", { cause: foo })`, `super("message", { cause: foo })`
*
* @param {Node} node
* @param {boolean} isAggregateError
* @returns {boolean}
*/
function isConstructCallWithCauseOption(node, isAggregateError) {
if (node.type !== "NewExpression" && !isSuperCall(node)) {
return false
}
const optionsArgIndex = isAggregateError ? 2 : 1
for (let index = 0; index < optionsArgIndex; index++) {
if (isSpreadElement(node.arguments[index])) {
return false
}
}
const optionsArg = node.arguments[optionsArgIndex]
if (!optionsArg || optionsArg.type !== "ObjectExpression") {
return false
}
return optionsArg.properties.some((property) => {
if (property.type !== "Property") {
return false
}
// new Error("msg", { cause: foo })
return (
getPropertyName(property, sourceCode.getScope(node)) ===
"cause"
)
})
}
/**
* @param {Node} node
* @param {isAggregateError} boolean
* @return {Node | null}
*/
function getReportedNode(node, isAggregateError) {
const errorSubclasses = isAggregateError
? maybeAggregateErrorSubclasses
: maybeErrorSubclasses
if (errorSubclasses.length > 0) {
for (const { classNode, superCallNode } of errorSubclasses) {
if (classNode.superClass === node) {
return superCallNode
}
}
}
if (isConstructCallWithCauseOption(node, isAggregateError)) {
return node
}
return null
}
return {
Super(node) {
const superCallNode = node.parent
function findErrorSubclasses(isAggregateError) {
const errorSubclasses = isAggregateError
? maybeAggregateErrorSubclasses
: maybeErrorSubclasses
if (
isConstructCallWithCauseOption(
superCallNode,
isAggregateError,
)
) {
const classNode = findClassFromAncestors(superCallNode)
if (classNode && classNode.superClass) {
errorSubclasses.push({ classNode, superCallNode })
}
}
}
findErrorSubclasses(/* isAggregateError */ false)
findErrorSubclasses(/* isAggregateError */ true)
},
"Program:exit"(program) {
const tracker = new ReferenceTracker(
sourceCode.getScope(program),
)
for (const { node, path } of tracker.iterateGlobalReferences(
errorsTraceMap,
)) {
const reportedNode = getReportedNode(
node,
path.join(",") === "AggregateError",
)
if (reportedNode) {
context.report({
node: reportedNode,
messageId: "forbidden",
})
}
}
},
}
},
}