UNPKG

eslint-plugin-complete

Version:

An ESLint plugin that contains useful rules.

82 lines (81 loc) 3.33 kB
import { isPromiseLike, isTypeReferenceType, } from "@typescript-eslint/type-utils"; import { ESLintUtils } from "@typescript-eslint/utils"; import { getTypeName, unionTypeParts } from "../typeUtils.js"; import { createRule } from "../utils.js"; export const noMutableReturn = createRule({ name: "no-mutable-return", meta: { type: "problem", docs: { description: "Disallows returning mutable arrays, maps, and sets from functions", recommended: true, requiresTypeChecking: true, }, schema: [], messages: { mutableArray: "Arrays that are returned from functions must be read-only. (Use the `readonly` keyword prefix or the `Readonly` utility type.)", mutableMap: "Maps that are returned from functions must be read-only. (Annotate the function using the `ReadonlyMap` type.)", mutableSet: "Sets that are returned from functions must be read-only. (Annotate the function using the `ReadonlySet` type.)", }, }, defaultOptions: [], create(context) { const parserServices = ESLintUtils.getParserServices(context); const checker = parserServices.program.getTypeChecker(); function checkReturnType(node) { const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); const type = checker.getTypeAtLocation(tsNode); const signatures = type.getCallSignatures(); for (const signature of signatures) { const returnType = signature.getReturnType(); const realReturnType = getRealType(parserServices.program, returnType); for (const t of unionTypeParts(realReturnType)) { const messageId = getErrorMessageId(t); if (messageId !== undefined) { context.report({ loc: node.loc, messageId, }); } } } } // We explicitly do not include `TSESTree.ArrowFunctionExpression` because arrow functions are // commonly used inside of other functions, which compartmentalize the mutations. return { FunctionDeclaration: checkReturnType, FunctionExpression: checkReturnType, }; }, }); /** If the type is a `Promise`, this will unwrap it. */ function getRealType(program, type) { if (isPromiseLike(program, type) && isTypeReferenceType(type) && type.typeArguments !== undefined) { const typeArgument = type.typeArguments[0]; if (typeArgument !== undefined) { return typeArgument; } } return type; } function getErrorMessageId(type) { const typeName = getTypeName(type); if (typeName === undefined) { return undefined; } // This would be "ReadonlyMap" if it was the read-only version. if (typeName === "Map") { return "mutableMap"; } // This would be "ReadonlySet" if it was the read-only version. if (typeName === "Set") { return "mutableSet"; } // This would be "ReadonlyArray" if it was the read-only version. if (typeName === "Array") { return "mutableArray"; } return undefined; }