From 1dccd33a02b229006fdae68ad8a2bc71dba1506a Mon Sep 17 00:00:00 2001 From: Horus Lugo Date: Tue, 19 Nov 2024 20:30:14 +0100 Subject: [PATCH 1/2] Add basic support for custom HOCs --- README.md | 13 +++++++++++++ src/only-export-components.test.ts | 14 +++++++++++++- src/only-export-components.ts | 23 +++++++++++++---------- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ea4e0ec..57e0a2b 100644 --- a/README.md +++ b/README.md @@ -140,3 +140,16 @@ If your using JSX inside `.js` files (which I don't recommend because it forces "react-refresh/only-export-components": ["warn", { "checkJS": true }] } ``` + +### customHOCs + +If you're exporting a component wrapped in a custom HOC, you can use this option to avoid false positives. + +```json +{ + "react-refresh/only-export-components": [ + "warn", + { "customHOCs": ["observer", "withAuth"] } + ] +} +``` \ No newline at end of file diff --git a/src/only-export-components.test.ts b/src/only-export-components.test.ts index b5accd9..1bfc26a 100755 --- a/src/only-export-components.test.ts +++ b/src/only-export-components.test.ts @@ -189,6 +189,11 @@ const valid = [ name: "Only React context", code: "export const MyContext = createContext('test');", }, + { + name: "Custom HOCs like mobx's observer", + code: "const MyComponent = () => {}; export default observer(MyComponent);", + options: [{ customHOCs: ["observer"] }], + }, ]; const invalid = [ @@ -295,6 +300,11 @@ const invalid = [ code: "export const MyComponent = () => {}; export const MyContext = React.createContext('test');", errorId: "reactContext", }, + { + name: "should be invalid when custom HOC is used without adding it to the rule configuration", + code: "const MyComponent = () => {}; export default observer(MyComponent);", + errorId: ["localComponents", "anonymousExport"], + }, ]; const it = (name: string, cases: Parameters[2]) => { @@ -322,7 +332,9 @@ for (const { name, code, errorId, filename, options = [] } of invalid) { { filename: filename ?? "Test.jsx", code, - errors: [{ messageId: errorId }], + errors: Array.isArray(errorId) + ? errorId.map((messageId) => ({ messageId })) + : [{ messageId: errorId }], options, }, ], diff --git a/src/only-export-components.ts b/src/only-export-components.ts index b74a285..e85948c 100644 --- a/src/only-export-components.ts +++ b/src/only-export-components.ts @@ -21,6 +21,7 @@ export const onlyExportComponents: TSESLint.RuleModule< allowConstantExport?: boolean; checkJS?: boolean; allowExportNames?: string[]; + customHOCs?: string[]; }, ] > = { @@ -47,6 +48,7 @@ export const onlyExportComponents: TSESLint.RuleModule< allowConstantExport: { type: "boolean" }, checkJS: { type: "boolean" }, allowExportNames: { type: "array", items: { type: "string" } }, + customHOCs: { type: "array", items: { type: "string" } }, }, additionalProperties: false, }, @@ -58,6 +60,7 @@ export const onlyExportComponents: TSESLint.RuleModule< allowConstantExport = false, checkJS = false, allowExportNames, + customHOCs = [], } = context.options[0] ?? {}; const filename = context.filename; // Skip tests & stories files @@ -79,6 +82,16 @@ export const onlyExportComponents: TSESLint.RuleModule< ? new Set(allowExportNames) : undefined; + const reactHOCs = new Set(["memo", "forwardRef", ...customHOCs]); + const canBeReactFunctionComponent = (init: TSESTree.Expression | null) => { + if (!init) return false; + if (init.type === "ArrowFunctionExpression") return true; + if (init.type === "CallExpression" && init.callee.type === "Identifier") { + return reactHOCs.has(init.callee.name); + } + return false; + }; + return { Program(program) { let hasExports = false; @@ -298,16 +311,6 @@ export const onlyExportComponents: TSESLint.RuleModule< }, }; -const reactHOCs = new Set(["memo", "forwardRef"]); -const canBeReactFunctionComponent = (init: TSESTree.Expression | null) => { - if (!init) return false; - if (init.type === "ArrowFunctionExpression") return true; - if (init.type === "CallExpression" && init.callee.type === "Identifier") { - return reactHOCs.has(init.callee.name); - } - return false; -}; - type ToString = T extends `${infer V}` ? V : never; const notReactComponentExpression = new Set< ToString From afa714d836336e05e91f67b603d451d81537d0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Thu, 28 Nov 2024 22:22:41 +0100 Subject: [PATCH 2/2] Fix doc --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 57e0a2b..78e847b 100644 --- a/README.md +++ b/README.md @@ -141,15 +141,15 @@ If your using JSX inside `.js` files (which I don't recommend because it forces } ``` -### customHOCs +### customHOCs (v0.4.15) If you're exporting a component wrapped in a custom HOC, you can use this option to avoid false positives. ```json { "react-refresh/only-export-components": [ - "warn", + "error", { "customHOCs": ["observer", "withAuth"] } ] } -``` \ No newline at end of file +```