UNPKG

eslint-plugin-next-route-params

Version:

eslint rule to enforce correct route parameters for Next.js

137 lines (136 loc) 5.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateSearchParams = validateSearchParams; const utils_1 = require("@typescript-eslint/utils"); const constants_1 = require("../constants"); const unwrapPromise_1 = require("./unwrapPromise"); const typeGenerator_1 = require("../types/typeGenerator"); function validateSearchParams(propsType, { ruleContext, customContext }) { const searchParamsMember = propsType.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.SEARCHPARAMS_PROP_NAME); if (!searchParamsMember || !("typeAnnotation" in searchParamsMember) || !searchParamsMember.typeAnnotation) { return; } function reportWrongSearchParamsTypeIssue(node, asyncRequestApi) { ruleContext.report({ loc: searchParamsMember.loc, messageId: "issue:wrong-searchParams-type", data: { promiseOrEmpty: asyncRequestApi ? "a Promise" : "" }, fix: (fixer) => fixer.replaceTextRange(node.range, (0, typeGenerator_1.SEARCHPARAMS_TYPE_NODE_STRING)(asyncRequestApi)), }); } let typeToValidate; if (customContext.asyncRequestAPI) { const isPromise = (0, unwrapPromise_1.unwrapPromise)(searchParamsMember); if (!isPromise.isPromise) { reportWrongSearchParamsTypeIssue(searchParamsMember.typeAnnotation.typeAnnotation, true); return; } else { typeToValidate = isPromise.promiseType; } } else { typeToValidate = searchParamsMember.typeAnnotation.typeAnnotation; } //first check if the searchParams property is allowed const isSearchParamsAllowedByFilenameType = customContext.allowedPropsForFileNameType?.includes(constants_1.SEARCHPARAMS_PROP_NAME); if (!isSearchParamsAllowedByFilenameType) { return; // no need to report the error because it is already done in validateFirstParameter } const isValid = validateSearchParamsStructure(typeToValidate); if (!isValid) { reportWrongSearchParamsTypeIssue(searchParamsMember.typeAnnotation.typeAnnotation, !!customContext.asyncRequestAPI); } } function validateSearchParamsStructure(member) { return (validateRecordTypeStructure(member) || validateIndexSignatureStructure(member)); } function validateIndexSignatureStructure(member) { if (!member || member.type !== utils_1.AST_NODE_TYPES.TSTypeLiteral) { return false; } const members = member.members; if (members.length !== 1) { return false; } const firstMember = members[0]; if (firstMember?.type !== utils_1.AST_NODE_TYPES.TSIndexSignature || firstMember.typeAnnotation?.type !== utils_1.AST_NODE_TYPES.TSTypeAnnotation || firstMember.typeAnnotation.typeAnnotation.type !== utils_1.AST_NODE_TYPES.TSUnionType) { return false; } const unionType = firstMember.typeAnnotation?.typeAnnotation; const areAllowedSearchParamsTypes = extractTypes(unionType.types); if (areAllowedSearchParamsTypes == null || Object.values(areAllowedSearchParamsTypes).includes(false)) { return false; } return true; } /** * required that input type is ```Record<string, string | string[] | undefined>``` */ function validateRecordTypeStructure(typeNode) { if (!typeNode || typeNode.type !== utils_1.AST_NODE_TYPES.TSTypeReference) { return false; } // Ensure the base type is Record if (typeNode.typeName.type !== utils_1.AST_NODE_TYPES.Identifier || typeNode.typeName.name !== "Record") { return false; } const typeArguments = typeNode.typeArguments?.params; if (!typeArguments || typeArguments.length !== 2) { return false; } const [keyType, valueType] = typeArguments; // Check if key type is string if (keyType?.type !== utils_1.AST_NODE_TYPES.TSStringKeyword) { return false; } // Check if value type is a union of string, string[], and undefined if (valueType?.type !== utils_1.AST_NODE_TYPES.TSUnionType) { return false; } const allowedValueTypes = extractTypes(valueType.types); if (allowedValueTypes == null || Object.values(allowedValueTypes).includes(false)) { return false; } return true; } function extractTypes(types) { return types.reduce((acc, element) => { if (acc == null) { return acc; } if (element.type === utils_1.AST_NODE_TYPES.TSStringKeyword) { acc.str = true; return acc; } else if ((element.type === utils_1.AST_NODE_TYPES.TSArrayType && element.elementType.type === utils_1.AST_NODE_TYPES.TSStringKeyword) || (element.type === utils_1.AST_NODE_TYPES.TSTypeReference && element.typeName.type === utils_1.AST_NODE_TYPES.Identifier && element.typeName.name === "Array" && element.typeArguments?.params.length === 1 && element.typeArguments.params[0]?.type === utils_1.AST_NODE_TYPES.TSStringKeyword)) { acc.strArr = true; return acc; } else if (element.type === utils_1.AST_NODE_TYPES.TSUndefinedKeyword) { acc.undef = true; return acc; } else { return undefined; } }, { str: false, strArr: false, undef: false }); }