diff --git a/rules/consistent-function-scoping.js b/rules/consistent-function-scoping.js index a8992a40e8..6b9e469518 100644 --- a/rules/consistent-function-scoping.js +++ b/rules/consistent-function-scoping.js @@ -75,6 +75,26 @@ function checkReferences(scope, parent, scopeManager) { ); } +// https://reactjs.org/docs/hooks-reference.html +const reactHooks = new Set([ + 'useState', + 'useEffect', + 'useContext', + 'useReducer', + 'useCallback', + 'useMemo', + 'useRef', + 'useImperativeHandle', + 'useLayoutEffect', + 'useDebugValue' +]); +const isReactHook = scope => + scope.block && + scope.block.parent && + scope.block.parent.callee && + scope.block.parent.callee.type === 'Identifier' && + reactHooks.has(scope.block.parent.callee.name); + function checkNode(node, scopeManager) { const scope = scopeManager.acquire(node); if (!scope) { @@ -102,7 +122,7 @@ function checkNode(node, scopeManager) { } const parentScope = scopeManager.acquire(parentNode); - if (!parentScope || parentScope.type === 'global') { + if (!parentScope || parentScope.type === 'global' || isReactHook(parentScope)) { return true; } diff --git a/test/consistent-function-scoping.js b/test/consistent-function-scoping.js index dbe565d7eb..14e11d366e 100644 --- a/test/consistent-function-scoping.js +++ b/test/consistent-function-scoping.js @@ -194,6 +194,12 @@ ruleTester.run('consistent-function-scoping', rule, { return Bar; }; `, + // React Hooks + outdent` + useEffect(() => { + function foo() {} + }, []) + `, // #391 outdent` const enrichErrors = (packageName, cliArgs, f) => async (...args) => { @@ -377,6 +383,18 @@ ruleTester.run('consistent-function-scoping', rule, { } `, errors: [createError({name: 'doBar'})] + }, + // React Hooks + { + code: outdent` + useEffect(() => { + function foo() { + function bar() { + } + } + }, []) + `, + errors: [createError({name: 'bar'})] } ] });