Skip to content

Commit

Permalink
[Tests] Component util isReactHookCall
Browse files Browse the repository at this point in the history
Rename Components test suite filename to match sibling lib/util/Components filename.
Extend Components testComponentsDetect function to accept custom instructions, and to accumulate the results of processing those instructions.
Add utility to check whether a CallExpression is a React hook call.
  • Loading branch information
duncanbeevers committed Dec 13, 2021
1 parent d56fdb8 commit 3ef8aba
Showing 1 changed file with 89 additions and 0 deletions.
89 changes: 89 additions & 0 deletions lib/util/Components.js
Expand Up @@ -46,6 +46,8 @@ function mergeUsedPropTypes(propsList, newPropsList) {
return propsList.concat(propsToAdd);
}

const USE_HOOK_PREFIX_REGEX = /^use/i;

const Lists = new WeakMap();
const ReactImports = new WeakMap();

Expand Down Expand Up @@ -787,6 +789,93 @@ function componentRule(rule, context) {
&& !!(node.params || []).length
);
},

/**
* Identify whether a node (CallExpression) is a call to a React hook
*
* @param {ASTNode} node The AST node being searched. (expects CallExpression)
* @param {('useCallback'|'useContext'|'useDebugValue'|'useEffect'|'useImperativeHandle'|'useLayoutEffect'|'useMemo'|'useReducer'|'useRef'|'useState')[]} [expectedHookNames] React hook names to which search is limited.
* @returns {Boolean} True if the node is a call to a React hook
*/
isReactHookCall(node, expectedHookNames) {
if (node.type !== 'CallExpression') {
return false;
}

const defaultReactImports = components.getDefaultReactImports();
const namedReactImports = components.getNamedReactImports();

const defaultReactImportSpecifier = defaultReactImports
? defaultReactImports[0]
: undefined;

const defaultReactImportName = defaultReactImportSpecifier
? defaultReactImportSpecifier.local.name
: undefined;

const reactHookImportSpecifiers = namedReactImports
? namedReactImports.filter((specifier) => specifier.imported.name.match(USE_HOOK_PREFIX_REGEX))
: undefined;
const reactHookImportNames = reactHookImportSpecifiers
? reactHookImportSpecifiers.reduce(
(acc, specifier) => {
acc[specifier.local.name] = specifier.imported.name;
return acc;
},
{}
)
: undefined;

const isPotentialReactHookCall = !!(
defaultReactImportName
&& node.callee.type === 'MemberExpression'
&& node.callee.object.type === 'Identifier'
&& node.callee.object.name === defaultReactImportName
&& node.callee.property.type === 'Identifier'
&& node.callee.property.name.match(USE_HOOK_PREFIX_REGEX)
);

const isPotentialHookCall = !!(
reactHookImportNames
&& node.callee.type === 'Identifier'
&& node.callee.name.match(USE_HOOK_PREFIX_REGEX)
);

const scope = isPotentialReactHookCall || isPotentialHookCall
? context.getScope()
: undefined;

const reactResolvedDefs = isPotentialReactHookCall
&& scope.references
&& scope.references.find(
(reference) => reference.identifier.name === defaultReactImportName
).resolved.defs;
const potentialHookReference = isPotentialHookCall
&& scope.references
&& scope.references.find(
(reference) => reactHookImportNames[reference.identifier.name]
);
const hookResolvedDefs = potentialHookReference && potentialHookReference.resolved.defs;

const hookName = (isPotentialReactHookCall && node.callee.property.name)
|| (isPotentialHookCall && potentialHookReference && node.callee.name);
const normalizedHookName = (reactHookImportNames && reactHookImportNames[hookName]) || hookName;

const isReactShadowed = isPotentialReactHookCall && reactResolvedDefs
&& reactResolvedDefs.some((reactDef) => reactDef.type !== 'ImportBinding');

const isHookShadowed = isPotentialHookCall
&& hookResolvedDefs
&& hookResolvedDefs.some(
(hookDef) => hookDef.name.name === hookName
&& hookDef.type !== 'ImportBinding'
);

const isHookCall = (isPotentialReactHookCall && !isReactShadowed)
|| (isPotentialHookCall && hookName && !isHookShadowed);
return !!(isHookCall
&& (!expectedHookNames || arrayIncludes(expectedHookNames, normalizedHookName)));
},
};

// Component detection instructions
Expand Down

0 comments on commit 3ef8aba

Please sign in to comment.