JSTQL-JS-Transform/output_testing/404eslint-plugin-react-hooks-test-cases.js

908 lines
26 KiB
JavaScript
Raw Normal View History

/**
* 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 <button {...props} ref={ref} />
});
`
}, {
code: normalizeIndent`
// Valid because hooks can be used in anonymous function arguments to
// forwardRef.
const FancyButton = React.forwardRef(function (props, ref) {
useHook();
return <button {...props} ref={ref} />
});
`
}, {
code: normalizeIndent`
// Valid because hooks can be used in anonymous function arguments to
// forwardRef.
const FancyButton = forwardRef(function (props, ref) {
useHook();
return <button {...props} ref={ref} />
});
`
}, {
code: normalizeIndent`
// Valid because hooks can be used in anonymous function arguments to
// React.memo.
const MemoizedFunction = React.memo(props => {
useHook();
return <button {...props} />
});
`
}, {
code: normalizeIndent`
// Valid because hooks can be used in anonymous function arguments to
// memo.
const MemoizedFunction = memo(function (props) {
useHook();
return <button {...props} />
});
`
}, {
code: normalizeIndent`
// Valid because classes can call functions.
// We don't consider these to be hooks.
class C {
m() {
this.useHook();
super.useHook();
}
}
`
}, {
code: normalizeIndent`
// Valid -- this is a regression test.
jest.useFakeTimers();
beforeEach(() => {
jest.useRealTimers();
})
`
}, {
code: normalizeIndent`
// Valid because they're not matching use[A-Z].
fooState();
_use();
_useState();
use_hook();
// also valid because it's not matching the PascalCase namespace
jest.useFakeTimer()
`
}, {
code: normalizeIndent`
// Regression test for some internal code.
// This shows how the "callback rule" is more relaxed,
// and doesn't kick in unless we're confident we're in
// a component or a hook.
function makeListener(instance) {
each(pixelsWithInferredEvents, pixel => {
if (useExtendedSelector(pixel.id) && extendedButton) {
foo();
}
});
}
`
}, {
code: normalizeIndent`
// This is valid because "use"-prefixed functions called in
// unnamed function arguments are not assumed to be hooks.
React.unknownFunction((foo, bar) => {
if (foo) {
useNotAHook(bar)
}
});
`
}, {
code: normalizeIndent`
// This is valid because "use"-prefixed functions called in
// unnamed function arguments are not assumed to be hooks.
unknownFunction(function(foo, bar) {
if (foo) {
useNotAHook(bar)
}
});
`
}, {
code: normalizeIndent`
// Regression test for incorrectly flagged valid code.
function RegressionTest() {
const foo = cond ? a : b;
useState();
}
`
}, {
code: normalizeIndent`
// Valid because exceptions abort rendering
function RegressionTest() {
if (page == null) {
throw new Error('oh no!');
}
useState();
}
`
}, {
code: normalizeIndent`
// Valid because the loop doesn't change the order of hooks calls.
function RegressionTest() {
const res = [];
const additionalCond = true;
for (let i = 0; i !== 10 && additionalCond; ++i ) {
res.push(i);
}
React.useLayoutEffect(() => {});
}
`
}, {
code: normalizeIndent`
// Is valid but hard to compute by brute-forcing
function MyComponent() {
// 40 conditions
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
if (c) {} else {}
// 10 hooks
useHook();
useHook();
useHook();
useHook();
useHook();
useHook();
useHook();
useHook();
useHook();
useHook();
}
`
}, {
code: normalizeIndent`
// Valid because the neither the conditions before or after the hook affect the hook call
// Failed prior to implementing BigInt because pathsFromStartToEnd and allPathsFromStartToEnd were too big and had rounding errors
const useSomeHook = () => {};
const SomeName = () => {
const filler = FILLER ?? FILLER ?? FILLER;
const filler2 = FILLER ?? FILLER ?? FILLER;
const filler3 = FILLER ?? FILLER ?? FILLER;
const filler4 = FILLER ?? FILLER ?? FILLER;
const filler5 = FILLER ?? FILLER ?? FILLER;
const filler6 = FILLER ?? FILLER ?? FILLER;
const filler7 = FILLER ?? FILLER ?? FILLER;
const filler8 = FILLER ?? FILLER ?? FILLER;
useSomeHook();
if (anyConditionCanEvenBeFalse) {
return null;
}
return (
<React.Fragment>
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
{FILLER ? FILLER : FILLER}
</React.Fragment>
);
};
`
}, {
code: normalizeIndent`
// Valid because the neither the condition nor the loop affect the hook call.
function App(props) {
const someObject = {propA: true};
for (const propName in someObject) {
if (propName === true) {
} else {
}
}
const [myState, setMyState] = useState(null);
}
`
}],
invalid: [{
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function ComponentWithConditionalHook() {
if (cond) {
useConditionalHook();
}
}
`,
errors: []
}, {
code: normalizeIndent`
Hook.useState();
Hook._useState();
Hook.use42();
Hook.useHook();
Hook.use_hook();
`,
errors: []
}, {
code: normalizeIndent`
class C {
m() {
This.useHook();
Super.useHook();
}
}
`,
errors: []
}, {
code: normalizeIndent`
// This is a false positive (it's valid) that unfortunately
// we cannot avoid. Prefer to rename it to not start with "use"
class Foo extends Component {
render() {
if (cond) {
FooStore.useFeatureFlag();
}
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function ComponentWithConditionalHook() {
if (cond) {
Namespace.useConditionalHook();
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function createComponent() {
return function ComponentWithConditionalHook() {
if (cond) {
useConditionalHook();
}
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHookWithConditionalHook() {
if (cond) {
useConditionalHook();
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function createHook() {
return function useHookWithConditionalHook() {
if (cond) {
useConditionalHook();
}
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function ComponentWithTernaryHook() {
cond ? useTernaryHook() : null;
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
function ComponentWithHookInsideCallback() {
useEffect(() => {
useHookInsideCallback();
});
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
function createComponent() {
return function ComponentWithHookInsideCallback() {
useEffect(() => {
useHookInsideCallback();
});
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
const ComponentWithHookInsideCallback = React.forwardRef((props, ref) => {
useEffect(() => {
useHookInsideCallback();
});
return <button {...props} ref={ref} />
});
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
const ComponentWithHookInsideCallback = React.memo(props => {
useEffect(() => {
useHookInsideCallback();
});
return <button {...props} />
});
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
function ComponentWithHookInsideCallback() {
function handleClick() {
useState();
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
function createComponent() {
return function ComponentWithHookInsideCallback() {
function handleClick() {
useState();
}
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function ComponentWithHookInsideLoop() {
while (cond) {
useHookInsideLoop();
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function renderItem() {
useState();
}
function List(props) {
return props.items.map(renderItem);
}
`,
errors: []
}, {
code: normalizeIndent`
// Currently invalid because it violates the convention and removes the "taint"
// from a hook. We *could* make it valid to avoid some false positives but let's
// ensure that we don't break the "renderItem" and "normalFunctionWithConditionalHook"
// cases which must remain invalid.
function normalFunctionWithHook() {
useHookInsideNormalFunction();
}
`,
errors: []
}, {
code: normalizeIndent`
// These are neither functions nor hooks.
function _normalFunctionWithHook() {
useHookInsideNormalFunction();
}
function _useNotAHook() {
useHookInsideNormalFunction();
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function normalFunctionWithConditionalHook() {
if (cond) {
useHookInsideNormalFunction();
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHookInLoops() {
while (a) {
useHook1();
if (b) return;
useHook2();
}
while (c) {
useHook3();
if (d) return;
useHook4();
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHookInLoops() {
while (a) {
useHook1();
if (b) continue;
useHook2();
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useLabeledBlock() {
label: {
if (a) break label;
useHook();
}
}
`,
errors: []
}, {
code: normalizeIndent`
// Currently invalid.
// These are variations capturing the current heuristic--
// we only allow hooks in PascalCase or useFoo functions.
// We *could* make some of these valid. But before doing it,
// consider specific cases documented above that contain reasoning.
function a() { useState(); }
const whatever = function b() { useState(); };
const c = () => { useState(); };
let d = () => useState();
e = () => { useState(); };
({f: () => { useState(); }});
({g() { useState(); }});
const {j = () => { useState(); }} = {};
({k = () => { useState(); }} = {});
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook() {
if (a) return;
useState();
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook() {
if (a) return;
if (b) {
console.log('true');
} else {
console.log('false');
}
useState();
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook() {
if (b) {
console.log('true');
} else {
console.log('false');
}
if (a) return;
useState();
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook() {
a && useHook1();
b && useHook2();
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook() {
try {
f();
useState();
} catch {}
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook({ bar }) {
let foo1 = bar && useState();
let foo2 = bar || useState();
let foo3 = bar ?? useState();
}
`,
errors: []
}, {
code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
const FancyButton = React.forwardRef((props, ref) => {
if (props.fancy) {
useCustomHook();
}
return <button ref={ref}>{props.children}</button>;
});
`,
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 <button ref={ref}>{props.children}</button>;
});
`,
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 <button>{props.children}</button>;
});
`,
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: []
}]
};