@samitouri / QOS-React / commits / 425ba0ad6d

[compiler] Script to produce markdown of lint rule docs (#34260)

The docs site is in a separate repo, but this gives us a semi-automated way to update the docs about our lint rules. The script generates markdown files from the rule definitions which we can then manually copy/paste into the docs site somewhere. In the future we can automate this fully.

Joseph Savona committed Aug 22, 2025 at 09:59 UTC 425ba0ad6d3ebd1779f6a658dcbf5c666d054948
3 files changed +68 -16
compiler/package.json
+2 -1
@@ -19,7 +19,8 @@
19 "test": "yarn workspaces run test",
20 "snap": "yarn workspace babel-plugin-react-compiler run snap",
21 "snap:build": "yarn workspace snap run build",
22 - "npm:publish": "node scripts/release/publish"
22 + "npm:publish": "node scripts/release/publish",
23 + "eslint-docs": "yarn workspace babel-plugin-react-compiler build && node scripts/build-eslint-docs.js"
24 },
25 "dependencies": {
26 "fs-extra": "^4.0.2",
compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts
+34 -15
@@ -7,9 +7,10 @@
7
8 import * as t from '@babel/types';
9 import {codeFrameColumns} from '@babel/code-frame';
10 -import type {SourceLocation} from './HIR';
10 +import {type SourceLocation} from './HIR';
11 import {Err, Ok, Result} from './Utils/Result';
12 import {assertExhaustive} from './Utils/utils';
13 +import invariant from 'invariant';
14
15 export enum ErrorSeverity {
16 /**
@@ -628,7 +629,18 @@ export type LintRule = {
629 recommended: boolean;
630 };
631
632 +const RULE_NAME_PATTERN = /^[a-z]+(-[a-z]+)*$/;
633 +
634 export function getRuleForCategory(category: ErrorCategory): LintRule {
635 + const rule = getRuleForCategoryImpl(category);
636 + invariant(
637 + RULE_NAME_PATTERN.test(rule.name),
638 + `Invalid rule name, got '${rule.name}' but rules must match ${RULE_NAME_PATTERN.toString()}`,
639 + );
640 + return rule;
641 +}
642 +
643 +function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
644 switch (category) {
645 case ErrorCategory.AutomaticEffectDependencies: {
646 return {
@@ -636,7 +648,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
648 name: 'automatic-effect-dependencies',
649 description:
650 'Verifies that automatic effect dependencies are compiled if opted-in',
639 - recommended: true,
651 + recommended: false,
652 };
653 }
654 case ErrorCategory.CapitalizedCalls: {
@@ -652,7 +664,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
664 return {
665 category,
666 name: 'config',
655 - description: 'Validates the configuration',
667 + description: 'Validates the compiler configuration options',
668 recommended: true,
669 };
670 }
@@ -678,7 +690,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
690 category,
691 name: 'set-state-in-effect',
692 description:
681 - 'Validates against calling setState synchronously in an effect',
693 + 'Validates against calling setState synchronously in an effect, which can lead to re-renders that degrade performance',
694 recommended: true,
695 };
696 }
@@ -687,7 +699,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
699 category,
700 name: 'error-boundaries',
701 description:
690 - 'Validates usage of error boundaries instead of try/catch for errors in JSX',
702 + 'Validates usage of error boundaries instead of try/catch for errors in child components',
703 recommended: true,
704 };
705 }
@@ -711,7 +723,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
723 return {
724 category,
725 name: 'gating',
714 - description: 'Validates configuration of gating mode',
726 + description:
727 + 'Validates configuration of [gating mode](https://react.dev/reference/react-compiler/gating)',
728 recommended: true,
729 };
730 }
@@ -720,7 +733,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
733 category,
734 name: 'globals',
735 description:
723 - 'Validates against assignment/mutation of globals during render',
736 + 'Validates against assignment/mutation of globals during render, part of ensuring that ' +
737 + '[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)',
738 recommended: true,
739 };
740 }
@@ -742,7 +756,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
756 category,
757 name: 'immutability',
758 description:
745 - 'Validates that immutable values (props, state, etc) are not mutated',
759 + '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)',
760 recommended: true,
761 };
762 }
@@ -759,7 +773,9 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
773 category,
774 name: 'preserve-manual-memoization',
775 description:
762 - 'Validates that existing manual memoized is preserved by the compiler',
776 + 'Validates that existing manual memoized is preserved by the compiler. ' +
777 + 'React Compiler will only compile components and hooks if its inference ' +
778 + '[matches or exceeds the existing manual memoization](https://react.dev/learn/react-compiler/introduction#what-should-i-do-about-usememo-usecallback-and-reactmemo)',
779 recommended: true,
780 };
781 }
@@ -768,7 +784,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
784 category,
785 name: 'purity',
786 description:
771 - 'Validates that the component/hook is pure, and does not call known-impure functions',
787 + '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',
788 recommended: true,
789 };
790 }
@@ -777,7 +793,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
793 category,
794 name: 'refs',
795 description:
780 - 'Validates correct usage of refs, not reading/writing during render',
796 + 'Validates correct usage of refs, not reading/writing during render. See the "pitfalls" section in [`useRef()` usage](https://react.dev/reference/react/useRef#usage)',
797 recommended: true,
798 };
799 }
@@ -785,7 +801,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
801 return {
802 category,
803 name: 'set-state-in-render',
788 - description: 'Validates against setting state during render',
804 + description:
805 + 'Validates against setting state during render, which can trigger additional renders and potential infinite render loops',
806 recommended: true,
807 };
808 }
@@ -794,7 +811,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
811 category,
812 name: 'static-components',
813 description:
797 - 'Validates that components are static, not recreated every render',
814 + 'Validates that components are static, not recreated every render. Components that are recreated dynamically can reset state and trigger excessive re-rendering',
815 recommended: true,
816 };
817 }
@@ -826,7 +843,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
843 return {
844 category,
845 name: 'unsupported-syntax',
829 - description: 'Validates against syntax that we do not plan to support',
846 + description:
847 + 'Validates against syntax that we do not plan to support in React Compiler',
848 recommended: true,
849 };
850 }
@@ -834,7 +852,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
852 return {
853 category,
854 name: 'use-memo',
837 - description: 'Validates usage of the useMemo() hook',
855 + description:
856 + 'Validates usage of the useMemo() hook against common mistakes. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
857 recommended: true,
858 };
859 }
compiler/scripts/build-eslint-docs.js new
+32
@@ -0,0 +1,32 @@
1 +const ReactCompiler = require('../packages/babel-plugin-react-compiler/dist');
2 +
3 +const combinedRules = [
4 + {
5 + name: 'rules-of-hooks',
6 + recommended: true,
7 + description:
8 + 'Validates that components and hooks follow the [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks)',
9 + },
10 + {
11 + name: 'exhaustive-deps',
12 + recommended: true,
13 + description:
14 + 'Validates that hooks which accept dependency arrays (`useMemo()`, `useCallback()`, `useEffect()`, etc) ' +
15 + 'list all referenced variables in their dependency array. Referencing a value without including it in the ' +
16 + 'dependency array can lead to stale UI or callbacks.',
17 + },
18 + ...ReactCompiler.LintRules,
19 +];
20 +
21 +const printed = combinedRules
22 + .filter(rule => rule.recommended)
23 + .map(rule => {
24 + return `
25 +## \`react-hooks/${rule.name}\`
26 +
27 +${rule.description}
28 + `.trim();
29 + })
30 + .join('\n\n');
31 +
32 +console.log(printed);