UNPKG

eslint-plugin-use-encapsulation

Version:

An ESLint rule to encourage using custom hook abstractions

116 lines (110 loc) 2.62 kB
'use strict'; // src/constants.js var HOOK_PATTERN = /^use/; var REACT_HOOKS = /* @__PURE__ */ new Set([ "useCallback", "useContext", "useDebugValue", "useDeferredValue", "useEffect", "useId", "useImperativeHandle", "useInsertionEffect", "useLayoutEffect", "useMemo", "useReducer", "useRef", "useState", "useSyncExternalStore", "useTransition" ]); // src/utils.js function difference(a, b) { const result = new Set(a); for (const item of b) { if (a.has(item)) { result.delete(item); } } return result; } function union(a, b) { const result = new Set(a); for (const item of b) { result.add(item); } return result; } // rules/prefer-custom-hooks.js function getHookParent(node) { if (node.type === "Program") return; if (node.type === "FunctionDeclaration") { return node; } if (node.type === "VariableDeclarator" && node.init.type === "ArrowFunctionExpression") { return node; } return getHookParent(node.parent); } var DEFAULT_OPTIONS = { block: [], allow: [] }; var MESSAGE = "Do not use React Hooks directly in a component. Abstract the functionality into a custom hook and use that instead."; var prefer_custom_hooks_default = { meta: { type: "suggestion", docs: { description: "Enforce using React hooks only inside custom hooks", recommended: "error" }, schema: [ { type: "object", properties: { allow: { type: "array", items: { type: "string" }, uniqueItems: true }, block: { type: "array", items: { type: "string" }, uniqueItems: true } }, additionalProperties: false } ], messages: { noDirectHooks: MESSAGE } }, create(context) { const userOptions = context.options[0] || {}; const options = { ...DEFAULT_OPTIONS, ...userOptions }; const allowedHooks = new Set(options.allow); const blockedHooks = union(REACT_HOOKS, new Set(options.block)); const hooksToCheck = difference(blockedHooks, allowedHooks); return { Identifier(node) { if (hooksToCheck.has(node.name)) { const hookParent = getHookParent(node); if (hookParent && !HOOK_PATTERN.test(hookParent.id.name)) { context.report({ node, messageId: "noDirectHooks" }); } } } }; } }; // index.js var index_default = { rules: { "prefer-custom-hooks": prefer_custom_hooks_default } }; module.exports = index_default;