diff --git a/app/client/packages/eslint-plugin/src/index.ts b/app/client/packages/eslint-plugin/src/index.ts index e3e454c20e2..eda6deb6d34 100644 --- a/app/client/packages/eslint-plugin/src/index.ts +++ b/app/client/packages/eslint-plugin/src/index.ts @@ -1,13 +1,16 @@ import { objectKeysRule } from "./object-keys/rule"; +import { namedUseEffectRule } from "./named-use-effect/rule"; const plugin = { rules: { "object-keys": objectKeysRule, + "named-use-effect": namedUseEffectRule, }, configs: { recommended: { rules: { "@appsmith/object-keys": "warn", + "@appsmith/named-use-effect": "warn", }, }, }, diff --git a/app/client/packages/eslint-plugin/src/named-use-effect/rule.test.ts b/app/client/packages/eslint-plugin/src/named-use-effect/rule.test.ts new file mode 100644 index 00000000000..267997f9e4a --- /dev/null +++ b/app/client/packages/eslint-plugin/src/named-use-effect/rule.test.ts @@ -0,0 +1,25 @@ +import { TSESLint } from "@typescript-eslint/utils"; +import { namedUseEffectRule } from "./rule"; + +const ruleTester = new TSESLint.RuleTester(); + +ruleTester.run("named-use-effect", namedUseEffectRule, { + valid: [ + { + code: "useEffect(function add(){ }, [])", + }, + { + code: "React.useEffect(function add(){ }, [])", + }, + ], + invalid: [ + { + code: "useEffect(function (){ }, [])", + errors: [{ messageId: "useNamedUseEffect" }], + }, + { + code: "React.useEffect(function (){ }, [])", + errors: [{ messageId: "useNamedUseEffect" }], + }, + ], +}); diff --git a/app/client/packages/eslint-plugin/src/named-use-effect/rule.ts b/app/client/packages/eslint-plugin/src/named-use-effect/rule.ts new file mode 100644 index 00000000000..5732f04c926 --- /dev/null +++ b/app/client/packages/eslint-plugin/src/named-use-effect/rule.ts @@ -0,0 +1,65 @@ +import type { TSESLint } from "@typescript-eslint/utils"; + +export const namedUseEffectRule: TSESLint.RuleModule<"useNamedUseEffect"> = { + defaultOptions: [], + meta: { + type: "suggestion", + docs: { + description: "Warns when useEffect hook has an anonymous function", + recommended: "warn", + }, + schema: [], // No options + messages: { + useNamedUseEffect: + "The function inside the useEffect should be named for better readability eg: useEffect(function mySideEffect() {...}, [])", + }, + }, + create(context) { + return { + CallExpression(node) { + // useEffect used directly + // eg + // import { useEffect } from "react"; + // ... + // useEffect(() => {}, []) + const isDirectCall = + node.callee.type === "Identifier" && node.callee.name === "useEffect"; + + // useEffect used via React object + // eg + // import React from "react"; + // ... + // React.useEffect(() => {}, []) + const isMemberExpressionCall = + node.callee.type === "MemberExpression" && + node.callee.object.type === "Identifier" && + node.callee.object.name === "React" && + node.callee.property.type === "Identifier" && + node.callee.property.name === "useEffect"; + + if (isDirectCall || isMemberExpressionCall) { + // Get the first argument which should be a function + const callbackArg = node.arguments[0]; + + // Arrow function are never named so it is discouraged + if (callbackArg.type === "ArrowFunctionExpression") { + context.report({ + node: callbackArg, + messageId: "useNamedUseEffect", + }); + } + + // Function Expressions can be unnamed. This is also discouraged + if (callbackArg.type === "FunctionExpression") { + if (!callbackArg.id) { + context.report({ + node: callbackArg, + messageId: "useNamedUseEffect", + }); + } + } + } + }, + }; + }, +};