Skip to content

Commit

Permalink
Add basic support for custom HOCs (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
HorusGoul authored Nov 28, 2024
1 parent 94c9d7d commit 478e778
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 11 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,16 @@ If your using JSX inside `.js` files (which I don't recommend because it forces
"react-refresh/only-export-components": ["error", { "checkJS": true }]
}
```

### customHOCs <small>(v0.4.15)</small>

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": [
"error",
{ "customHOCs": ["observer", "withAuth"] }
]
}
```
14 changes: 13 additions & 1 deletion src/only-export-components.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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<typeof ruleTester.run>[2]) => {
Expand Down Expand Up @@ -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,
},
],
Expand Down
23 changes: 13 additions & 10 deletions src/only-export-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const onlyExportComponents: TSESLint.RuleModule<
allowConstantExport?: boolean;
checkJS?: boolean;
allowExportNames?: string[];
customHOCs?: string[];
},
]
> = {
Expand All @@ -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,
},
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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> = T extends `${infer V}` ? V : never;
const notReactComponentExpression = new Set<
ToString<TSESTree.Expression["type"]>
Expand Down

0 comments on commit 478e778

Please sign in to comment.