eslint-plugin-next-route-params
Version:
eslint rule to enforce correct route parameters for Next.js
148 lines (147 loc) • 6.59 kB
JavaScript
;
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;
}