/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ "use strict"; // NOTE: Extracted from https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js /** * A string template tag that removes padding from the left side of multi-line strings */ function normalizeIndent(strings) { const codeLines = "\n" |> strings[0].split(%); const leftPadding = (/\s+/ |> codeLines[1].match(%))[0]; return "\n" |> ((line => leftPadding.length |> line.slice(%)) |> codeLines.map(%)).join(%); } module.exports.tests = { valid: [{ code: normalizeIndent` // Valid because components can use hooks. function ComponentWithHook() { useHook(); } ` }, { code: normalizeIndent` // Valid because components can use hooks. function createComponentWithHook() { return function ComponentWithHook() { useHook(); }; } ` }, { code: normalizeIndent` // Valid because hooks can use hooks. function useHookWithHook() { useHook(); } ` }, { code: normalizeIndent` // Valid because hooks can use hooks. function createHook() { return function useHookWithHook() { useHook(); } } ` }, { code: normalizeIndent` // Valid because components can call functions. function ComponentWithNormalFunction() { doSomething(); } ` }, { code: normalizeIndent` // Valid because functions can call functions. function normalFunctionWithNormalFunction() { doSomething(); } ` }, { code: normalizeIndent` // Valid because functions can call functions. function normalFunctionWithConditionalFunction() { if (cond) { doSomething(); } } ` }, { code: normalizeIndent` // Valid because functions can call functions. function functionThatStartsWithUseButIsntAHook() { if (cond) { userFetch(); } } ` }, { code: normalizeIndent` // Valid although unconditional return doesn't make sense and would fail other rules. // We could make it invalid but it doesn't matter. function useUnreachable() { return; useHook(); } ` }, { code: normalizeIndent` // Valid because hooks can call hooks. function useHook() { useState(); } const whatever = function useHook() { useState(); }; const useHook1 = () => { useState(); }; let useHook2 = () => useState(); useHook2 = () => { useState(); }; ({useHook: () => { useState(); }}); ({useHook() { useState(); }}); const {useHook3 = () => { useState(); }} = {}; ({useHook = () => { useState(); }} = {}); Namespace.useHook = () => { useState(); }; ` }, { code: normalizeIndent` // Valid because hooks can call hooks. function useHook() { useHook1(); useHook2(); } ` }, { code: normalizeIndent` // Valid because hooks can call hooks. function createHook() { return function useHook() { useHook1(); useHook2(); }; } ` }, { code: normalizeIndent` // Valid because hooks can call hooks. function useHook() { useState() && a; } ` }, { code: normalizeIndent` // Valid because hooks can call hooks. function useHook() { return useHook1() + useHook2(); } ` }, { code: normalizeIndent` // Valid because hooks can call hooks. function useHook() { return useHook1(useHook2()); } ` }, { code: normalizeIndent` // Valid because hooks can be used in anonymous arrow-function arguments // to forwardRef. const FancyButton = React.forwardRef((props, ref) => { useHook(); return ; }); `, errors: [] }, { code: normalizeIndent` // Invalid because it's dangerous and might not warn otherwise. // This *must* be invalid. const FancyButton = forwardRef(function(props, ref) { if (props.fancy) { useCustomHook(); } return ; }); `, errors: [] }, { code: normalizeIndent` // Invalid because it's dangerous and might not warn otherwise. // This *must* be invalid. const MemoizedButton = memo(function(props) { if (props.fancy) { useCustomHook(); } return ; }); `, errors: [] }, { code: normalizeIndent` // This is invalid because "use"-prefixed functions used in named // functions are assumed to be hooks. React.unknownFunction(function notAComponent(foo, bar) { useProbablyAHook(bar) }); `, errors: [] }, { code: normalizeIndent` // Invalid because it's dangerous. // Normally, this would crash, but not if you use inline requires. // This *must* be invalid. // It's expected to have some false positives, but arguably // they are confusing anyway due to the use*() convention // already being associated with Hooks. useState(); if (foo) { const foo = React.useCallback(() => {}); } useCustomHook(); `, errors: [] }, { code: normalizeIndent` // Technically this is a false positive. // We *could* make it valid (and it used to be). // // However, top-level Hook-like calls can be very dangerous // in environments with inline requires because they can mask // the runtime error by accident. // So we prefer to disallow it despite the false positive. const {createHistory, useBasename} = require('history-2.1.2'); const browserHistory = useBasename(createHistory)({ basename: '/', }); `, errors: [] }, { code: normalizeIndent` class ClassComponentWithFeatureFlag extends React.Component { render() { if (foo) { useFeatureFlag(); } } } `, errors: [] }, { code: normalizeIndent` class ClassComponentWithHook extends React.Component { render() { React.useState(); } } `, errors: [] }, { code: normalizeIndent` (class {useHook = () => { useState(); }}); `, errors: [] }, { code: normalizeIndent` (class {useHook() { useState(); }}); `, errors: [] }, { code: normalizeIndent` (class {h = () => { useState(); }}); `, errors: [] }, { code: normalizeIndent` (class {i() { useState(); }}); `, errors: [] }] };