eslint-plugin-use-encapsulation
Version:
An ESLint rule to encourage using custom hook abstractions
116 lines (110 loc) • 2.62 kB
JavaScript
// 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;
;