UNPKG

eslint-plugin-next-route-params

Version:

eslint rule to enforce correct route parameters for Next.js

148 lines (147 loc) 6.59 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateParams = validateParams; exports.getTypeOfMember = getTypeOfMember; const utils_1 = require("@typescript-eslint/utils"); const constants_1 = require("../constants"); const typeGenerator_1 = require("../types/typeGenerator"); const utils_2 = require("../utils"); const unwrapPromise_1 = require("./unwrapPromise"); const findReferencedType_1 = require("./findReferencedType"); function validateParams(paramsType, context) { const paramsMember = paramsType.members.find((member) => member.type === utils_1.AST_NODE_TYPES.TSPropertySignature && member.key.type === utils_1.AST_NODE_TYPES.Identifier && member.key.name === constants_1.ALLOWED_PROPS_FOR_PAGE[0]); if (!paramsMember || !("typeAnnotation" in paramsMember) || !paramsMember.typeAnnotation) { return; } if (context.customContext.asyncRequestAPI) { const unwrappedPromise = (0, unwrapPromise_1.unwrapPromise)(paramsMember, (param) => { switch (param?.type) { case utils_1.AST_NODE_TYPES.TSTypeLiteral: return param; case utils_1.AST_NODE_TYPES.TSTypeReference: const type = (0, findReferencedType_1.findReferencedType)(param, context); return type ?? undefined; default: return undefined; } }); if (!unwrappedPromise.isPromise) { reportAsyncRequestApiWrongParameterIssue(context, paramsMember.typeAnnotation.typeAnnotation); return; } else { validateParamsStructure({ typeToValidate: unwrappedPromise.promiseType, typeToReplace: paramsMember.typeAnnotation.typeAnnotation, }, context); } } else if (paramsMember.typeAnnotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) { validateParamsStructure({ typeToValidate: paramsMember.typeAnnotation.typeAnnotation, typeToReplace: paramsMember.typeAnnotation.typeAnnotation, }, context); } } function reportAsyncRequestApiWrongParameterIssue(context, paramsTypeNode) { context.ruleContext.report({ loc: paramsTypeNode.loc, messageId: "issue:asyncRequestApi-params", fix: (fixer) => fixer.replaceTextRange(paramsTypeNode.range, (0, typeGenerator_1.createParamsTypeNodeString)(context.customContext)), }); } function getTypeOfMember(member) { if ("typeAnnotation" in member && member.typeAnnotation?.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSStringKeyword) return "string"; else if ("typeAnnotation" in member && member.typeAnnotation?.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSArrayType && member.typeAnnotation.typeAnnotation.elementType.type === utils_1.AST_NODE_TYPES.TSStringKeyword) { return "string[]"; } else if ("typeAnnotation" in member && member.typeAnnotation?.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference && member.typeAnnotation.typeAnnotation.typeName.type === utils_1.AST_NODE_TYPES.Identifier && member.typeAnnotation.typeAnnotation.typeName.name === "Array" && member.typeAnnotation.typeAnnotation.typeArguments && member.typeAnnotation.typeAnnotation.typeArguments.params.length === 1 && member.typeAnnotation.typeAnnotation.typeArguments.params[0]?.type === utils_1.AST_NODE_TYPES.TSStringKeyword) { return "string[]"; } else return "other"; } function validateParamsStructure({ typeToValidate, typeToReplace, }, context) { if (!typeToValidate) { return; } const isExplicitlyTypedType = typeToValidate.type === utils_1.AST_NODE_TYPES.TSTypeLiteral; if (!isExplicitlyTypedType) { // report an problem here definitely => must be a TSPropertySignature type context.ruleContext.report({ loc: typeToReplace.loc, messageId: "issue:isNoLiteral", }); return; } const params = typeToValidate.members.map((member) => ({ range: "typeAnnotation" in member ? (member.typeAnnotation?.typeAnnotation.range ?? null) : null, isLiteral: member.type === utils_1.AST_NODE_TYPES.TSPropertySignature && member.key.type === utils_1.AST_NODE_TYPES.Identifier, type: getTypeOfMember(member), name: "key" in member && "name" in member.key ? member.key.name : null, })); const actualParamNames = context.customContext.params.map((param) => param.name); const unAllowedParams = params.filter((param) => param.name == null || !actualParamNames.includes(param.name)); if (unAllowedParams.length > 0) { context.ruleContext.report({ loc: typeToReplace.loc, messageId: "issue:unknown-parameter", data: { name: unAllowedParams[0].name }, fix: (fixer) => fixer.replaceTextRange(typeToReplace.range, (0, typeGenerator_1.createParamsTypeNodeString)({ asyncRequestAPI: context.customContext.asyncRequestAPI, params: context.customContext.params, })), }); } const routeParams = context.customContext.params .filter((param) => !param.catchAll) .map((param) => param.name); const catchAllParams = context.customContext.params .filter((param) => param.catchAll) .map((param) => param.name); const mustBeString = params.find((param) => routeParams.find((p) => param.name === p && param.type !== "string")); if (mustBeString) { (0, utils_2.reportWrongParameterIssue)(context, typeToValidate, mustBeString.range, { name: mustBeString.name, type: "string", }); } const mustBeStringArray = params.find((param) => catchAllParams.find((p) => param.name === p && param.type !== "string[]")); if (mustBeStringArray) { (0, utils_2.reportWrongParameterIssue)(context, typeToValidate, mustBeStringArray.range, { name: mustBeStringArray.name, type: "string[]", }); } const areNotAllLiteral = params.filter((param) => param.isLiteral === false); if (areNotAllLiteral.length > 0) { context.ruleContext.report({ loc: typeToValidate.loc, messageId: "issue:isNoLiteral", }); } return; }