Skip to content

Instantly share code, notes, and snippets.

@temoncher
Created November 10, 2022 13:21
Show Gist options
  • Select an option

  • Save temoncher/92a9e93225afb25adcd54e02da689a3e to your computer and use it in GitHub Desktop.

Select an option

Save temoncher/92a9e93225afb25adcd54e02da689a3e to your computer and use it in GitHub Desktop.
ESLint rule to include component name in a prop type definition
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