Chore(front): Create a custom eslint rule for Props naming (#1904)

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Matheus <matheus_benini@hotmail.com>
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
This commit is contained in:
gitstart-twenty
2023-10-09 17:31:13 +03:00
committed by GitHub
parent 84ed9edefe
commit 77a1840611
170 changed files with 700 additions and 342 deletions

View File

@ -8,5 +8,6 @@ module.exports = {
"matching-state-variable": require("./src/rules/matching-state-variable"),
"sort-css-properties-alphabetically": require("./src/rules/sort-css-properties-alphabetically"),
"styled-components-prefixed-with-styled": require("./src/rules/styled-components-prefixed-with-styled"),
"component-props-naming": require("./src/rules/component-props-naming"),
},
};

View File

@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@typescript-eslint/utils");
const createRule = utils_1.ESLintUtils.RuleCreator(() => "https://docs.twenty.com/developer/frontend/style-guide#props");
const checkPropsTypeName = (node, context, functionName) => {
const expectedPropTypeName = `${functionName}Props`;
if (/^[A-Z]/.test(functionName)) {
node.params.forEach((param) => {
var _a, _b;
if ((param.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
param.type === utils_1.AST_NODE_TYPES.Identifier) &&
((_b = (_a = param.typeAnnotation) === null || _a === void 0 ? void 0 : _a.typeAnnotation) === null || _b === void 0 ? void 0 : _b.type) ===
utils_1.AST_NODE_TYPES.TSTypeReference &&
param.typeAnnotation.typeAnnotation.typeName.type ===
utils_1.AST_NODE_TYPES.Identifier) {
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),
});
}
}
});
}
};
const componentPropsNamingRule = createRule({
create: (context) => {
return {
ArrowFunctionExpression: (node) => {
var _a, _b;
if (node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
const functionName = (_b = (_a = node.parent) === null || _a === void 0 ? void 0 : _a.id) === null || _b === void 0 ? void 0 : _b.name;
checkPropsTypeName(node, context, functionName);
}
},
FunctionDeclaration: (node) => {
var _a;
if ((_a = node.id) === null || _a === void 0 ? void 0 : _a.name) {
const functionName = node.id.name;
checkPropsTypeName(node, context, functionName);
}
},
};
},
name: "component-props-naming",
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: [],
});
module.exports = componentPropsNamingRule;
exports.default = componentPropsNamingRule;

View File

@ -0,0 +1,54 @@
import { RuleTester } from "@typescript-eslint/rule-tester";
import componentPropsNamingRule from "../rules/component-props.naming";
const ruleTester = new RuleTester({
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: __dirname,
ecmaFeatures: {
jsx: true,
},
},
});
ruleTester.run("component-props-naming", componentPropsNamingRule, {
valid: [
{
code: `
type MyComponentProps = { id: number; };
const MyComponent: React.FC<MyComponentProps> = (props) => <div>{props.id}</div>;
`,
},
{
code: `
type AnotherComponentProps = { message: string; };
export const AnotherComponent: React.FC<AnotherComponentProps> = (props) => <div>{props.message}</div>;
`,
},
],
invalid: [
{
code: `
type UnmatchedComponentProps = { id: number; };
`,
errors: [
{
messageId: "invalidPropsTypeName",
},
],
},
{
code: `
type UnmatchedComponentProps = { message: string; };
const DifferentComponent: React.FC<UnmatchedComponentProps> = (props) => <div>{props.message}</div>;
`,
errors: [
{
messageId: "invalidPropsTypeName",
},
],
},
],
});

View File

@ -6,5 +6,6 @@ module.exports = {
"matching-state-variable": require("./src/rules/matching-state-variable"),
"sort-css-properties-alphabetically": require("./src/rules/sort-css-properties-alphabetically"),
"styled-components-prefixed-with-styled": require("./src/rules/styled-components-prefixed-with-styled"),
"component-props-naming": require("./src/rules/component-props-naming"),
},
};

View File

@ -0,0 +1,84 @@
import {
AST_NODE_TYPES,
ESLintUtils,
TSESTree,
} from "@typescript-eslint/utils";
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
const createRule = ESLintUtils.RuleCreator(
() => "https://docs.twenty.com/developer/frontend/style-guide#props",
);
const checkPropsTypeName = (
node: TSESTree.FunctionDeclaration | TSESTree.ArrowFunctionExpression,
context: Readonly<RuleContext<"invalidPropsTypeName", never[]>>,
functionName: string,
) => {
const expectedPropTypeName = `${functionName}Props`;
if (/^[A-Z]/.test(functionName)) {
node.params.forEach((param) => {
if (
(param.type === AST_NODE_TYPES.ObjectPattern ||
param.type === AST_NODE_TYPES.Identifier) &&
param.typeAnnotation?.typeAnnotation?.type ===
AST_NODE_TYPES.TSTypeReference &&
param.typeAnnotation.typeAnnotation.typeName.type ===
AST_NODE_TYPES.Identifier
) {
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),
});
}
}
});
}
};
const componentPropsNamingRule = createRule({
create: (context) => {
return {
ArrowFunctionExpression: (node) => {
if (
node.parent.type === AST_NODE_TYPES.VariableDeclarator &&
node.parent.id.type === AST_NODE_TYPES.Identifier
) {
const functionName = node.parent?.id?.name;
checkPropsTypeName(node, context, functionName);
}
},
FunctionDeclaration: (node) => {
if (node.id?.name) {
const functionName = node.id.name;
checkPropsTypeName(node, context, functionName);
}
},
};
},
name: "component-props-naming",
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: [],
});
module.exports = componentPropsNamingRule;
export default componentPropsNamingRule;

View File

@ -0,0 +1,47 @@
import { RuleTester } from "@typescript-eslint/rule-tester";
import componentPropsNamingRule from "../rules/component-props-naming";
const ruleTester = new RuleTester({
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: __dirname,
ecmaFeatures: {
jsx: true,
},
},
});
ruleTester.run("component-props-naming", componentPropsNamingRule, {
valid: [
{
code: "export const MyComponent= (props: MyComponentProps) => <div>{props.message}</div>;",
},
{
code: "export const MyComponent = ({ message }: MyComponentProps) => <div>{message}</div>;",
},
],
invalid: [
{
code: "export const MyComponent = (props: OwnProps) => <div>{props.message}</div>;",
errors: [
{
messageId: "invalidPropsTypeName",
},
],
output:
"export const MyComponent = (props: MyComponentProps) => <div>{props.message}</div>;",
},
{
code: "export const MyComponent = ({ message }: OwnProps) => <div>{message}</div>;",
errors: [
{
messageId: "invalidPropsTypeName",
},
],
output:
"export const MyComponent = ({ message }: MyComponentProps) => <div>{message}</div>;",
},
],
});