import { ESLintUtils, TSESTree } from '@typescript-eslint/utils'; import { isIdentifier, isVariableDeclarator, } from '@typescript-eslint/utils/ast-utils'; import { RuleContext } from '@typescript-eslint/utils/ts-eslint'; // NOTE: The rule will be available in ESLint configs as "@nx/workspace-component-props-naming" export const RULE_NAME = 'component-props-naming'; const checkPropsTypeName = ({ node, context, functionName, }: { node: TSESTree.FunctionDeclaration | TSESTree.ArrowFunctionExpression; context: Readonly>; functionName: string; }) => { const expectedPropTypeName = `${functionName}Props`; if (!functionName.match(/^[A-Z]/)) return; node.params.forEach((param) => { if ( (param.type === TSESTree.AST_NODE_TYPES.ObjectPattern || isIdentifier(param)) && param.typeAnnotation?.typeAnnotation.type === TSESTree.AST_NODE_TYPES.TSTypeReference && isIdentifier(param.typeAnnotation?.typeAnnotation.typeName) ) { const { typeName } = param.typeAnnotation.typeAnnotation; const actualPropTypeName = typeName.name; if (actualPropTypeName !== expectedPropTypeName) { context.report({ node: param, messageId: 'invalidPropsTypeName', data: { expectedPropTypeName, actualPropTypeName }, fix: (fixer) => fixer.replaceText(typeName, expectedPropTypeName), }); } } }); }; export const rule = ESLintUtils.RuleCreator(() => __filename)({ name: RULE_NAME, meta: { type: 'problem', docs: { description: 'Ensure component props follow naming convention', recommended: 'recommended', }, fixable: 'code', schema: [], messages: { invalidPropsTypeName: "Expected prop type to be '{{ expectedPropTypeName }}' but found '{{ actualPropTypeName }}'", }, }, defaultOptions: [], create: (context) => { return { ArrowFunctionExpression: (node) => { if (isVariableDeclarator(node.parent) && isIdentifier(node.parent.id)) { checkPropsTypeName({ node, context, functionName: node.parent.id.name, }); } }, FunctionDeclaration: (node) => { checkPropsTypeName({ node, context, functionName: node.id.name }); }, }; }, });