eslint-plugin-complete
Version:
An ESLint plugin that contains useful rules.
82 lines (81 loc) • 3.33 kB
JavaScript
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;
}