mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
This is a great validation, so let's enable by default. Changes: * Move the validation logic into ValidateUseMemo alongside the new check that the useMemo result is used * Update the lint description * Make the void memo errors lint-only, they don't require us to skip compilation (as evidenced by the fact that we've had this validation off) --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34882). * #34855 * __->__ #34882
167 lines
4.3 KiB
TypeScript
167 lines
4.3 KiB
TypeScript
/**
|
|
* 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.
|
|
*/
|
|
|
|
import {
|
|
ErrorCategory,
|
|
getRuleForCategory,
|
|
} from 'babel-plugin-react-compiler/src/CompilerError';
|
|
import {
|
|
normalizeIndent,
|
|
testRule,
|
|
makeTestCaseError,
|
|
TestRecommendedRules,
|
|
} from './shared-utils';
|
|
import {allRules} from '../src/rules/ReactCompilerRule';
|
|
|
|
testRule('plugin-recommended', TestRecommendedRules, {
|
|
valid: [
|
|
{
|
|
name: 'Basic example with component syntax',
|
|
code: normalizeIndent`
|
|
export default component HelloWorld(
|
|
text: string = 'Hello!',
|
|
onClick: () => void,
|
|
) {
|
|
return <div onClick={onClick}>{text}</div>;
|
|
}
|
|
`,
|
|
},
|
|
|
|
{
|
|
// OK because invariants are only meant for the compiler team's consumption
|
|
name: '[Invariant] Defined after use',
|
|
code: normalizeIndent`
|
|
function Component(props) {
|
|
let y = function () {
|
|
m(x);
|
|
};
|
|
|
|
let x = { a };
|
|
m(x);
|
|
return y;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
name: "Classes don't throw",
|
|
code: normalizeIndent`
|
|
class Foo {
|
|
#bar() {}
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
invalid: [
|
|
{
|
|
// TODO: actually return multiple diagnostics in this case
|
|
name: 'Multiple diagnostic kinds from the same function are surfaced',
|
|
code: normalizeIndent`
|
|
import Child from './Child';
|
|
function Component() {
|
|
const result = cond ?? useConditionalHook();
|
|
return <>
|
|
{Child(result)}
|
|
</>;
|
|
}
|
|
`,
|
|
errors: [
|
|
makeTestCaseError('Hooks must always be called in a consistent order'),
|
|
],
|
|
},
|
|
{
|
|
name: 'Multiple diagnostics within the same file are surfaced',
|
|
code: normalizeIndent`
|
|
function useConditional1() {
|
|
'use memo';
|
|
return cond ?? useConditionalHook();
|
|
}
|
|
function useConditional2(props) {
|
|
'use memo';
|
|
return props.cond && useConditionalHook();
|
|
}`,
|
|
errors: [
|
|
makeTestCaseError('Hooks must always be called in a consistent order'),
|
|
makeTestCaseError('Hooks must always be called in a consistent order'),
|
|
],
|
|
},
|
|
{
|
|
name: "'use no forget' does not disable eslint rule",
|
|
code: normalizeIndent`
|
|
let count = 0;
|
|
function Component() {
|
|
'use no forget';
|
|
return cond ?? useConditionalHook();
|
|
|
|
}
|
|
`,
|
|
errors: [
|
|
makeTestCaseError('Hooks must always be called in a consistent order'),
|
|
],
|
|
},
|
|
{
|
|
name: 'Multiple non-fatal useMemo diagnostics are surfaced',
|
|
code: normalizeIndent`
|
|
import {useMemo, useState} from 'react';
|
|
|
|
function Component({item, cond}) {
|
|
const [prevItem, setPrevItem] = useState(item);
|
|
const [state, setState] = useState(0);
|
|
|
|
useMemo(() => {
|
|
if (cond) {
|
|
setPrevItem(item);
|
|
setState(0);
|
|
}
|
|
}, [cond, item, init]);
|
|
|
|
return <Child x={state} />;
|
|
}`,
|
|
errors: [
|
|
makeTestCaseError('useMemo() callbacks must return a value'),
|
|
makeTestCaseError(
|
|
'Calling setState from useMemo may trigger an infinite loop',
|
|
),
|
|
makeTestCaseError(
|
|
'Calling setState from useMemo may trigger an infinite loop',
|
|
),
|
|
],
|
|
},
|
|
{
|
|
name: 'Pipeline errors are reported',
|
|
code: normalizeIndent`
|
|
import useMyEffect from 'useMyEffect';
|
|
import {AUTODEPS} from 'react';
|
|
function Component({a}) {
|
|
'use no memo';
|
|
useMyEffect(() => console.log(a.b), AUTODEPS);
|
|
return <div>Hello world</div>;
|
|
}
|
|
`,
|
|
options: [
|
|
{
|
|
environment: {
|
|
inferEffectDependencies: [
|
|
{
|
|
function: {
|
|
source: 'useMyEffect',
|
|
importSpecifierName: 'default',
|
|
},
|
|
autodepsIndex: 1,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
errors: [
|
|
{
|
|
message: /Cannot infer dependencies of this effect/,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|