UNPKG

eslint-plugin-next-route-params

Version:

eslint rule to enforce correct route parameters for Next.js

178 lines (177 loc) 8.24 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../utils/utils"); const utils_2 = require("@typescript-eslint/utils"); const fs_1 = require("../utils/fs"); const validateFunction_1 = require("../utils/validation/validateGenerateStaticParams/validateFunction"); const constants_1 = require("../utils/constants"); const createRule = utils_2.ESLintUtils.RuleCreator(() => `https://www.paulhe.de/blog/next-route-params-eslint-rule`); const GENERATE_STATIC_PARAMS_FUNCTION_NAME = "generateStaticParams"; const enforceRouteParamsRule = createRule({ name: "enforce-route-params", meta: { docs: { description: "enforce correct route parameters built by Next.js' file based routes", }, type: "problem", messages: { "issue:isWrongParameterType": "{{ name }} must be of type {{ type }}", "issue:isNoLiteral": "Consider using an explicit type annotation", "issue:unknown-parameter": "The param {{ name }} does not exist in the corresponding route path of this file", "issue:forbiddenPropertyKey": "The property {{ key }} is forbidden", "issue:wrong-searchParams-type": "searchParams must be {{ promiseOrEmpty }} of type { [key: string]: string | string[] | undefined }", "issue:no-returntype": "The function must specify a returntype", "issue:wrong-returntype": "The function must specify a correct returntype", "issue:isNoOptionalParam": "The param {{ name }} must not be optional", "issue:asyncRequestApi-params": "params must be a Promise", }, schema: [ { type: "object", properties: { searchParams: { type: "boolean", enum: [true, false], default: true, description: "If true, also strictly validates searchParams and enforces that the searchParams parameter is of type { [key: string]: string | string[] | undefined }", }, }, additionalProperties: false, }, ], fixable: "code", hasSuggestions: false, }, defaultOptions: [ { searchParams: true, }, ], create: (ruleContext, options) => { const fileInfo = (0, fs_1.getFileInfo)(ruleContext.filename); if (!fileInfo.inInAppRouterFolder || !fileInfo.isAppRouterFile || fileInfo.asyncRequestAPI == null) { return {}; } let nameOfDefaultExport = null; const context = { ruleContext, customContext: { ...fileInfo, }, }; const registeredRouteHandlerFunctions = []; return { FunctionDeclaration(node) { if (node.id?.name === GENERATE_STATIC_PARAMS_FUNCTION_NAME) { (0, validateFunction_1.validateGenerateStaticParamsFunction)(node, context); } else if (node.id?.name && constants_1.METADATA_FUNCTION_NAMES.includes(node.id?.name) && node.params[0] != null) { (0, utils_1.handleFunctionParameters)({ context, options, props: node.params[0], }); } else if (node.id?.name && registeredRouteHandlerFunctions.includes(node.id?.name) && node.params[1] != null) { (0, utils_1.handleFunctionParameters)({ context, options, props: node.params[1], }); } }, // Handle variable declarations with arrow functions VariableDeclarator(node) { if ((node.init?.type === utils_2.AST_NODE_TYPES.ArrowFunctionExpression || node.init?.type === utils_2.AST_NODE_TYPES.FunctionExpression) && node.id.type === utils_2.AST_NODE_TYPES.Identifier) { if (node.id.name === GENERATE_STATIC_PARAMS_FUNCTION_NAME) { (0, validateFunction_1.validateGenerateStaticParamsFunction)(node.init, context); } else if (constants_1.METADATA_FUNCTION_NAMES.includes(node.id?.name) && node.init.params[0] != null) { (0, utils_1.handleFunctionParameters)({ context, options, props: node.init.params[0], }); } else if (constants_1.ROUTE_HANDLERS_FUNCTION_NAMES.includes(node.id?.name) && node.init.params[1] != null) { (0, utils_1.handleFunctionParameters)({ context, options, props: node.init.params[1], }); } } }, ExportNamedDeclaration(node) { if (node.declaration?.type === utils_2.AST_NODE_TYPES.FunctionDeclaration && node.declaration.id?.name != null && constants_1.ROUTE_HANDLERS_FUNCTION_NAMES.includes(node.declaration.id?.name) && node.declaration.params[1] != null) { (0, utils_1.handleFunctionParameters)({ context, options, props: node.declaration.params[1], }); } }, ExportDefaultDeclaration(node) { if (node.declaration.type === utils_2.AST_NODE_TYPES.Identifier) { nameOfDefaultExport = node.declaration.name; return; } // it is not a default function export if (!(node.declaration.type === utils_2.AST_NODE_TYPES.FunctionDeclaration) || !node.declaration.params[0]) { return; } (0, utils_1.handleFunctionParameters)({ context, options, props: node.declaration.params[0], }); }, "Program:exit"() { if (nameOfDefaultExport) { const variable = ruleContext.sourceCode.scopeManager?.variables?.find((variable) => variable.name === nameOfDefaultExport); if (!variable) { return; } const node = variable.defs[0]?.node; // Perform your custom logic on the declaration node here if (node?.type === utils_2.AST_NODE_TYPES.VariableDeclarator && node.init) { const initNode = node.init; if ((initNode.type === utils_2.AST_NODE_TYPES.ArrowFunctionExpression || initNode.type === utils_2.AST_NODE_TYPES.FunctionExpression) && initNode.params[0]) { // Add your custom logic for the function node here (0, utils_1.handleFunctionParameters)({ context, options, props: initNode.params[0], }); } } else if (node?.type === utils_2.AST_NODE_TYPES.FunctionDeclaration && node.params[0]) { (0, utils_1.handleFunctionParameters)({ context, options, props: node.params[0], }); } } }, }; }, }); exports.default = enforceRouteParamsRule;