Created
November 10, 2022 13:21
-
-
Save temoncher/92a9e93225afb25adcd54e02da689a3e to your computer and use it in GitHub Desktop.
ESLint rule to include component name in a prop type definition
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const isCapitalized = (str) => str[0] === str[0].toUpperCase(); | |
| const reportPropsError = (componentName, node, context, sourceCode) => { | |
| const newPropsName = `${componentName}Props`; | |
| const allPropsIdentifierTokens = sourceCode.ast.tokens.filter( | |
| (token) => token.type === 'Identifier' && token.value === 'Props' | |
| ); | |
| context.report({ | |
| node, | |
| message: `Please include component name in the props type definition. Should be "${newPropsName}"`, | |
| *fix(fixer) { | |
| for (const token of allPropsIdentifierTokens) { | |
| yield fixer.replaceTextRange(token.range, newPropsName); | |
| } | |
| }, | |
| }); | |
| }; | |
| /** | |
| * @param {import('eslint').Rule.RuleContext} context | |
| * @returns {import('eslint').Rule.RuleListener} | |
| */ | |
| module.exports = (context) => { | |
| const sourceCode = context.getSourceCode(); | |
| return { | |
| ClassDeclaration(node) { | |
| if ( | |
| (node.superClass?.name === 'Component' || | |
| (node.superClass?.object.name === 'React' && | |
| node.superClass?.property.name === 'Component')) && | |
| node.superTypeParameters?.params.length > 0 | |
| ) { | |
| const currentComponentPropName = node.superTypeParameters?.params[0].typeName?.name; | |
| if (currentComponentPropName === 'Props') { | |
| reportPropsError(node.id.name, node, context, sourceCode); | |
| } | |
| } | |
| }, | |
| FunctionDeclaration(node) { | |
| if ( | |
| isCapitalized(node.id.name) && | |
| node.params[0].typeAnnotation?.typeAnnotation?.typeName.name === 'Props' | |
| ) { | |
| reportPropsError(node.id.name, node, context, sourceCode); | |
| } | |
| }, | |
| ArrowFunctionExpression(node) { | |
| const componentName = node.parent?.id?.name; | |
| if ( | |
| componentName && | |
| isCapitalized(componentName) && | |
| node.params[0].typeAnnotation?.typeAnnotation?.typeName.name === 'Props' | |
| ) { | |
| reportPropsError(componentName, node, context, sourceCode); | |
| } | |
| }, | |
| /** @param {import('eslint').Rule.Node} node */ | |
| TSTypeAnnotation(node) { | |
| const componentName = node.parent?.name; | |
| if (componentName && isCapitalized(componentName)) { | |
| const isFc = node.typeAnnotation?.typeName.name === 'FC'; | |
| const isReactFc = | |
| node.typeAnnotation?.typeName.left?.name === 'React' && | |
| node.typeAnnotation?.typeName.right?.name === 'FC'; | |
| if ( | |
| (isFc || isReactFc) && | |
| node.typeAnnotation?.typeParameters?.params[0]?.typeName?.name === 'Props' | |
| ) { | |
| reportPropsError(componentName, node, context, sourceCode); | |
| } | |
| } | |
| }, | |
| }; | |
| }; | |
| // tests | |
| // import React, { Component, FC } from 'react'; | |
| // type Props = { | |
| // }; | |
| // class LoanReplenishment1 extends React.Component<Props> {} | |
| // export class LoanReplenishment2 extends React.Component<Props> {} | |
| // export default class LoanReplenishment3 extends React.Component<Props> {} | |
| // class LoanReplenishment4 extends Component<Props> {} | |
| // export class LoanReplenishment5 extends Component<Props> {} | |
| // export default class LoanReplenishment6 extends Component<Props> {} | |
| // function LoanReplenishment7(props: Props) {} | |
| // export function LoanReplenishment8(props: Props) {} | |
| // export default function LoanReplenishment9(props: Props) {} | |
| // const LoanReplenishment10 = (props: Props) => {}; | |
| // export const LoanReplenishment11 = (props: Props) => {}; | |
| // const LoanReplenishment12 = ({ some }: Props) => {}; | |
| // export const LoanReplenishment13 = ({ some }: Props) => {}; | |
| // const LoanReplenishment14: React.FC<Props> = (props) => <></>; | |
| // export const LoanReplenishment15: React.FC<Props> = (props) => <></>; | |
| // const LoanReplenishment16: FC<Props> = (props) => <></>; | |
| // export const LoanReplenishment17: FC<Props> = (props) => <></>; | |
| // export default (some: Props) => {}; // ? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment