Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
packages/react-devtools-shell/dist
packages/react-devtools-timeline/dist
packages/react-devtools-timeline/static

# Imported third-party Flow types
flow-typed/
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ module.exports = {
IteratorResult: 'readonly',
JSONValue: 'readonly',
JSResourceReference: 'readonly',
mixin$Animatable: 'readonly',
MouseEventHandler: 'readonly',
NavigateEvent: 'readonly',
PropagationPhases: 'readonly',
Expand Down Expand Up @@ -619,7 +620,6 @@ module.exports = {
PropertyIndexedKeyframes: 'readonly',
KeyframeAnimationOptions: 'readonly',
GetAnimationsOptions: 'readonly',
Animatable: 'readonly',
ScrollTimeline: 'readonly',
EventListenerOptionsOrUseCapture: 'readonly',
FocusOptions: 'readonly',
Expand Down
3 changes: 2 additions & 1 deletion compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"test": "yarn workspaces run test",
"snap": "yarn workspace babel-plugin-react-compiler run snap",
"snap:build": "yarn workspace snap run build",
"npm:publish": "node scripts/release/publish"
"npm:publish": "node scripts/release/publish",
"eslint-docs": "yarn workspace babel-plugin-react-compiler build && node scripts/build-eslint-docs.js"
},
"dependencies": {
"fs-extra": "^4.0.2",
Expand Down
49 changes: 34 additions & 15 deletions compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

import * as t from '@babel/types';
import {codeFrameColumns} from '@babel/code-frame';
import type {SourceLocation} from './HIR';
import {type SourceLocation} from './HIR';
import {Err, Ok, Result} from './Utils/Result';
import {assertExhaustive} from './Utils/utils';
import invariant from 'invariant';

export enum ErrorSeverity {
/**
Expand Down Expand Up @@ -628,15 +629,26 @@ export type LintRule = {
recommended: boolean;
};

const RULE_NAME_PATTERN = /^[a-z]+(-[a-z]+)*$/;

export function getRuleForCategory(category: ErrorCategory): LintRule {
const rule = getRuleForCategoryImpl(category);
invariant(
RULE_NAME_PATTERN.test(rule.name),
`Invalid rule name, got '${rule.name}' but rules must match ${RULE_NAME_PATTERN.toString()}`,
);
return rule;
}

function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
switch (category) {
case ErrorCategory.AutomaticEffectDependencies: {
return {
category,
name: 'automatic-effect-dependencies',
description:
'Verifies that automatic effect dependencies are compiled if opted-in',
recommended: true,
recommended: false,
};
}
case ErrorCategory.CapitalizedCalls: {
Expand All @@ -652,7 +664,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
return {
category,
name: 'config',
description: 'Validates the configuration',
description: 'Validates the compiler configuration options',
recommended: true,
};
}
Expand All @@ -678,7 +690,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'set-state-in-effect',
description:
'Validates against calling setState synchronously in an effect',
'Validates against calling setState synchronously in an effect, which can lead to re-renders that degrade performance',
recommended: true,
};
}
Expand All @@ -687,7 +699,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'error-boundaries',
description:
'Validates usage of error boundaries instead of try/catch for errors in JSX',
'Validates usage of error boundaries instead of try/catch for errors in child components',
recommended: true,
};
}
Expand All @@ -711,7 +723,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
return {
category,
name: 'gating',
description: 'Validates configuration of gating mode',
description:
'Validates configuration of [gating mode](https://react.dev/reference/react-compiler/gating)',
recommended: true,
};
}
Expand All @@ -720,7 +733,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'globals',
description:
'Validates against assignment/mutation of globals during render',
'Validates against assignment/mutation of globals during render, part of ensuring that ' +
'[side effects must render outside of render](https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)',
recommended: true,
};
}
Expand All @@ -742,7 +756,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'immutability',
description:
'Validates that immutable values (props, state, etc) are not mutated',
'Validates against mutating props, state, and other values that [are immutable](https://react.dev/reference/rules/components-and-hooks-must-be-pure#props-and-state-are-immutable)',
recommended: true,
};
}
Expand All @@ -759,7 +773,9 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'preserve-manual-memoization',
description:
'Validates that existing manual memoized is preserved by the compiler',
'Validates that existing manual memoized is preserved by the compiler. ' +
'React Compiler will only compile components and hooks if its inference ' +
'[matches or exceeds the existing manual memoization](https://react.dev/learn/react-compiler/introduction#what-should-i-do-about-usememo-usecallback-and-reactmemo)',
recommended: true,
};
}
Expand All @@ -768,7 +784,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'purity',
description:
'Validates that the component/hook is pure, and does not call known-impure functions',
'Validates that [components/hooks are pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure) by checking that they do not call known-impure functions',
recommended: true,
};
}
Expand All @@ -777,15 +793,16 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'refs',
description:
'Validates correct usage of refs, not reading/writing during render',
'Validates correct usage of refs, not reading/writing during render. See the "pitfalls" section in [`useRef()` usage](https://react.dev/reference/react/useRef#usage)',
recommended: true,
};
}
case ErrorCategory.RenderSetState: {
return {
category,
name: 'set-state-in-render',
description: 'Validates against setting state during render',
description:
'Validates against setting state during render, which can trigger additional renders and potential infinite render loops',
recommended: true,
};
}
Expand All @@ -794,7 +811,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'static-components',
description:
'Validates that components are static, not recreated every render',
'Validates that components are static, not recreated every render. Components that are recreated dynamically can reset state and trigger excessive re-rendering',
recommended: true,
};
}
Expand Down Expand Up @@ -826,15 +843,17 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
return {
category,
name: 'unsupported-syntax',
description: 'Validates against syntax that we do not plan to support',
description:
'Validates against syntax that we do not plan to support in React Compiler',
recommended: true,
};
}
case ErrorCategory.UseMemo: {
return {
category,
name: 'use-memo',
description: 'Validates usage of the useMemo() hook',
description:
'Validates usage of the useMemo() hook against common mistakes. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
recommended: true,
};
}
Expand Down
32 changes: 32 additions & 0 deletions compiler/scripts/build-eslint-docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const ReactCompiler = require('../packages/babel-plugin-react-compiler/dist');

const combinedRules = [
{
name: 'rules-of-hooks',
recommended: true,
description:
'Validates that components and hooks follow the [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks)',
},
{
name: 'exhaustive-deps',
recommended: true,
description:
'Validates that hooks which accept dependency arrays (`useMemo()`, `useCallback()`, `useEffect()`, etc) ' +
'list all referenced variables in their dependency array. Referencing a value without including it in the ' +
'dependency array can lead to stale UI or callbacks.',
},
...ReactCompiler.LintRules,
];

const printed = combinedRules
.filter(rule => rule.recommended)
.map(rule => {
return `
## \`react-hooks/${rule.name}\`

${rule.description}
`.trim();
})
.join('\n\n');

console.log(printed);
20 changes: 20 additions & 0 deletions flow-typed.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"env": [
"bom",
"cssom",
"dom",
"geometry",
"html",
"node",
"serviceworkers",
"streams",
"web-animations"
],
"ignore": [
"create-react-class",
"jest",
"regenerator-runtime",
"webpack",
"ws"
]
}
Loading
Loading