@samitouri / QOS-React / commits / 3e1b34dc51

[compiler] Setup RecommendedLatest preset (#34782)

Renames the `recommended` property on LintRule to `preset`, to allow exporting rules for different presets. For now the `Recommended` and `RecommendedLatest` presets are the same, but in the next PR I will enable more rules for the latest preset. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34782). * #34783 * __->__ #34782

lauren committed Oct 8, 2025 at 15:45 UTC 3e1b34dc51dfc3cbada2cdd0ead5acee6998f444
5 files changed +96 -53
compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts
+44 -30
@@ -669,6 +669,21 @@ export enum ErrorCategory {
669 FBT = 'FBT',
670 }
671
672 +export enum LintRulePreset {
673 + /**
674 + * Rules that are stable and included in the `recommended` preset.
675 + */
676 + Recommended = 'recommended',
677 + /**
678 + * Rules that are more experimental and only included in the `recommended-latest` preset.
679 + */
680 + RecommendedLatest = 'recommended-latest',
681 + /**
682 + * Rules that are disabled.
683 + */
684 + Off = 'off',
685 +}
686 +
687 export type LintRule = {
688 // Stores the category the rule corresponds to, used to filter errors when reporting
689 category: ErrorCategory;
@@ -689,15 +704,14 @@ export type LintRule = {
704 description: string;
705
706 /**
692 - * If true, this rule will automatically appear in the default, "recommended" ESLint
693 - * rule set. Otherwise it will be part of an `allRules` export that developers can
694 - * use to opt-in to showing output of all possible rules.
707 + * Configures the preset in which the rule is enabled. If 'off', the rule will not be included in
708 + * any preset.
709 *
710 * NOTE: not all validations are enabled by default! Setting this flag only affects
711 * whether a given rule is part of the recommended set. The corresponding validation
712 * also should be enabled by default if you want the error to actually show up!
713 */
700 - recommended: boolean;
714 + preset: LintRulePreset;
715 };
716
717 const RULE_NAME_PATTERN = /^[a-z]+(-[a-z]+)*$/;
@@ -720,7 +734,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
734 name: 'automatic-effect-dependencies',
735 description:
736 'Verifies that automatic effect dependencies are compiled if opted-in',
723 - recommended: false,
737 + preset: LintRulePreset.Off,
738 };
739 }
740 case ErrorCategory.CapitalizedCalls: {
@@ -730,7 +744,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
744 name: 'capitalized-calls',
745 description:
746 'Validates against calling capitalized functions/methods instead of using JSX',
733 - recommended: false,
747 + preset: LintRulePreset.Off,
748 };
749 }
750 case ErrorCategory.Config: {
@@ -739,7 +753,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
753 severity: ErrorSeverity.Error,
754 name: 'config',
755 description: 'Validates the compiler configuration options',
742 - recommended: true,
756 + preset: LintRulePreset.Recommended,
757 };
758 }
759 case ErrorCategory.EffectDependencies: {
@@ -748,7 +762,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
762 severity: ErrorSeverity.Error,
763 name: 'memoized-effect-dependencies',
764 description: 'Validates that effect dependencies are memoized',
751 - recommended: false,
765 + preset: LintRulePreset.Off,
766 };
767 }
768 case ErrorCategory.EffectDerivationsOfState: {
@@ -758,7 +772,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
772 name: 'no-deriving-state-in-effects',
773 description:
774 'Validates against deriving values from state in an effect',
761 - recommended: false,
775 + preset: LintRulePreset.Off,
776 };
777 }
778 case ErrorCategory.EffectSetState: {
@@ -768,7 +782,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
782 name: 'set-state-in-effect',
783 description:
784 'Validates against calling setState synchronously in an effect, which can lead to re-renders that degrade performance',
771 - recommended: true,
785 + preset: LintRulePreset.Recommended,
786 };
787 }
788 case ErrorCategory.ErrorBoundaries: {
@@ -778,7 +792,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
792 name: 'error-boundaries',
793 description:
794 'Validates usage of error boundaries instead of try/catch for errors in child components',
781 - recommended: true,
795 + preset: LintRulePreset.Recommended,
796 };
797 }
798 case ErrorCategory.Factories: {
@@ -789,7 +803,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
803 description:
804 'Validates against higher order functions defining nested components or hooks. ' +
805 'Components and hooks should be defined at the module level',
792 - recommended: true,
806 + preset: LintRulePreset.Recommended,
807 };
808 }
809 case ErrorCategory.FBT: {
@@ -798,7 +812,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
812 severity: ErrorSeverity.Error,
813 name: 'fbt',
814 description: 'Validates usage of fbt',
801 - recommended: false,
815 + preset: LintRulePreset.Off,
816 };
817 }
818 case ErrorCategory.Fire: {
@@ -807,7 +821,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
821 severity: ErrorSeverity.Error,
822 name: 'fire',
823 description: 'Validates usage of `fire`',
810 - recommended: false,
824 + preset: LintRulePreset.Off,
825 };
826 }
827 case ErrorCategory.Gating: {
@@ -817,7 +831,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
831 name: 'gating',
832 description:
833 'Validates configuration of [gating mode](https://react.dev/reference/react-compiler/gating)',
820 - recommended: true,
834 + preset: LintRulePreset.Recommended,
835 };
836 }
837 case ErrorCategory.Globals: {
@@ -828,7 +842,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
842 description:
843 'Validates against assignment/mutation of globals during render, part of ensuring that ' +
844 '[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)',
831 - recommended: true,
845 + preset: LintRulePreset.Recommended,
846 };
847 }
848 case ErrorCategory.Hooks: {
@@ -842,7 +856,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
856 * We need to dedeupe these (moving the remaining bits into the compiler) and then enable
857 * this rule.
858 */
845 - recommended: false,
859 + preset: LintRulePreset.Off,
860 };
861 }
862 case ErrorCategory.Immutability: {
@@ -852,7 +866,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
866 name: 'immutability',
867 description:
868 '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)',
855 - recommended: true,
869 + preset: LintRulePreset.Recommended,
870 };
871 }
872 case ErrorCategory.Invariant: {
@@ -861,7 +875,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
875 severity: ErrorSeverity.Error,
876 name: 'invariant',
877 description: 'Internal invariants',
864 - recommended: false,
878 + preset: LintRulePreset.Off,
879 };
880 }
881 case ErrorCategory.PreserveManualMemo: {
@@ -873,7 +887,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
887 'Validates that existing manual memoized is preserved by the compiler. ' +
888 'React Compiler will only compile components and hooks if its inference ' +
889 '[matches or exceeds the existing manual memoization](https://react.dev/learn/react-compiler/introduction#what-should-i-do-about-usememo-usecallback-and-reactmemo)',
876 - recommended: true,
890 + preset: LintRulePreset.Recommended,
891 };
892 }
893 case ErrorCategory.Purity: {
@@ -883,7 +897,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
897 name: 'purity',
898 description:
899 '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',
886 - recommended: true,
900 + preset: LintRulePreset.Recommended,
901 };
902 }
903 case ErrorCategory.Refs: {
@@ -893,7 +907,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
907 name: 'refs',
908 description:
909 'Validates correct usage of refs, not reading/writing during render. See the "pitfalls" section in [`useRef()` usage](https://react.dev/reference/react/useRef#usage)',
896 - recommended: true,
910 + preset: LintRulePreset.Recommended,
911 };
912 }
913 case ErrorCategory.RenderSetState: {
@@ -903,7 +917,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
917 name: 'set-state-in-render',
918 description:
919 'Validates against setting state during render, which can trigger additional renders and potential infinite render loops',
906 - recommended: true,
920 + preset: LintRulePreset.Recommended,
921 };
922 }
923 case ErrorCategory.StaticComponents: {
@@ -913,7 +927,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
927 name: 'static-components',
928 description:
929 'Validates that components are static, not recreated every render. Components that are recreated dynamically can reset state and trigger excessive re-rendering',
916 - recommended: true,
930 + preset: LintRulePreset.Recommended,
931 };
932 }
933 case ErrorCategory.Suppression: {
@@ -922,7 +936,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
936 severity: ErrorSeverity.Error,
937 name: 'rule-suppression',
938 description: 'Validates against suppression of other rules',
925 - recommended: false,
939 + preset: LintRulePreset.Off,
940 };
941 }
942 case ErrorCategory.Syntax: {
@@ -931,7 +945,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
945 severity: ErrorSeverity.Error,
946 name: 'syntax',
947 description: 'Validates against invalid syntax',
934 - recommended: false,
948 + preset: LintRulePreset.Off,
949 };
950 }
951 case ErrorCategory.Todo: {
@@ -940,7 +954,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
954 severity: ErrorSeverity.Hint,
955 name: 'todo',
956 description: 'Unimplemented features',
943 - recommended: false,
957 + preset: LintRulePreset.Off,
958 };
959 }
960 case ErrorCategory.UnsupportedSyntax: {
@@ -950,7 +964,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
964 name: 'unsupported-syntax',
965 description:
966 'Validates against syntax that we do not plan to support in React Compiler',
953 - recommended: true,
967 + preset: LintRulePreset.Recommended,
968 };
969 }
970 case ErrorCategory.UseMemo: {
@@ -960,7 +974,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
974 name: 'use-memo',
975 description:
976 'Validates usage of the useMemo() hook against common mistakes. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
963 - recommended: true,
977 + preset: LintRulePreset.Recommended,
978 };
979 }
980 case ErrorCategory.IncompatibleLibrary: {
@@ -970,7 +984,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
984 name: 'incompatible-library',
985 description:
986 'Validates against usage of libraries which are incompatible with memoization (manual or automatic)',
973 - recommended: true,
987 + preset: LintRulePreset.Recommended,
988 };
989 }
990 default: {
compiler/packages/babel-plugin-react-compiler/src/index.ts
+1
@@ -14,6 +14,7 @@ export {
14 ErrorSeverity,
15 ErrorCategory,
16 LintRules,
17 + LintRulePreset,
18 type CompilerErrorDetailOptions,
19 type CompilerDiagnosticOptions,
20 type CompilerDiagnosticDetail,
compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts
+12 -2
@@ -15,6 +15,7 @@ import type {Linter, Rule} from 'eslint';
15 import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler';
16 import {
17 ErrorSeverity,
18 + LintRulePreset,
19 LintRules,
20 type LintRule,
21 } from 'babel-plugin-react-compiler/src/CompilerError';
@@ -150,7 +151,7 @@ function makeRule(rule: LintRule): Rule.RuleModule {
151 type: 'problem',
152 docs: {
153 description: rule.description,
153 - recommended: rule.recommended,
154 + recommended: rule.preset === LintRulePreset.Recommended,
155 },
156 fixable: 'code',
157 hasSuggestions: true,
@@ -171,7 +172,16 @@ export const allRules: RulesConfig = LintRules.reduce((acc, rule) => {
172 }, {} as RulesConfig);
173
174 export const recommendedRules: RulesConfig = LintRules.filter(
174 - rule => rule.recommended,
175 + rule => rule.preset === LintRulePreset.Recommended,
176 +).reduce((acc, rule) => {
177 + acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
178 + return acc;
179 +}, {} as RulesConfig);
180 +
181 +export const recommendedLatestRules: RulesConfig = LintRules.filter(
182 + rule =>
183 + rule.preset === LintRulePreset.Recommended ||
184 + rule.preset === LintRulePreset.RecommendedLatest,
185 ).reduce((acc, rule) => {
186 acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
187 return acc;
packages/eslint-plugin-react-hooks/src/index.ts
+19 -5
@@ -11,6 +11,7 @@ import {
11 allRules,
12 mapErrorSeverityToESlint,
13 recommendedRules,
14 + recommendedLatestRules,
15 } from './shared/ReactCompiler';
16 import RulesOfHooks from './rules/RulesOfHooks';
17
@@ -27,7 +28,7 @@ const basicRuleConfigs = {
28 'react-hooks/exhaustive-deps': 'warn',
29 } as const satisfies Linter.RulesRecord;
30
30 -const compilerRuleConfigs = Object.fromEntries(
31 +const recommendedCompilerRuleConfigs = Object.fromEntries(
32 Object.entries(recommendedRules).map(([name, ruleConfig]) => {
33 return [
34 `react-hooks/${name}` as const,
@@ -36,9 +37,22 @@ const compilerRuleConfigs = Object.fromEntries(
37 }),
38 ) as Record<`react-hooks/${string}`, Linter.RuleEntry>;
39
39 -const allRuleConfigs: Linter.RulesRecord = {
40 +const recommendedLatestCompilerRuleConfigs = Object.fromEntries(
41 + Object.entries(recommendedLatestRules).map(([name, ruleConfig]) => {
42 + return [
43 + `react-hooks/${name}` as const,
44 + mapErrorSeverityToESlint(ruleConfig.severity),
45 + ] as const;
46 + }),
47 +) as Record<`react-hooks/${string}`, Linter.RuleEntry>;
48 +
49 +const recommendedRuleConfigs: Linter.RulesRecord = {
50 + ...basicRuleConfigs,
51 + ...recommendedCompilerRuleConfigs,
52 +};
53 +const recommendedLatestRuleConfigs: Linter.RulesRecord = {
54 ...basicRuleConfigs,
41 - ...compilerRuleConfigs,
55 + ...recommendedLatestCompilerRuleConfigs,
56 };
57
58 const plugins = ['react-hooks'];
@@ -51,11 +65,11 @@ type ReactHooksFlatConfig = {
65 const configs = {
66 recommended: {
67 plugins,
54 - rules: allRuleConfigs,
68 + rules: recommendedRuleConfigs,
69 },
70 'recommended-latest': {
71 plugins,
58 - rules: allRuleConfigs,
72 + rules: recommendedLatestRuleConfigs,
73 },
74 flat: {} as Record<string, ReactHooksFlatConfig>,
75 };
packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts
+20 -16
@@ -14,6 +14,7 @@ import {
14 LintRules,
15 type LintRule,
16 ErrorSeverity,
17 + LintRulePreset,
18 } from 'babel-plugin-react-compiler';
19 import {type Linter, type Rule} from 'eslint';
20 import runReactCompiler, {RunCacheEntry} from './RunReactCompiler';
@@ -149,7 +150,7 @@ function makeRule(rule: LintRule): Rule.RuleModule {
150 type: 'problem',
151 docs: {
152 description: rule.description,
152 - recommended: rule.recommended,
153 + recommended: rule.preset === LintRulePreset.Recommended,
154 },
155 fixable: 'code',
156 hasSuggestions: true,
@@ -164,23 +165,26 @@ type RulesConfig = {
165 [name: string]: {rule: Rule.RuleModule; severity: ErrorSeverity};
166 };
167
167 -export const allRules: RulesConfig = LintRules.reduce(
168 - (acc, rule) => {
169 - acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
170 - return acc;
171 - },
172 - {} as RulesConfig,
173 -);
168 +export const allRules: RulesConfig = LintRules.reduce((acc, rule) => {
169 + acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
170 + return acc;
171 +}, {} as RulesConfig);
172
173 export const recommendedRules: RulesConfig = LintRules.filter(
176 - rule => rule.recommended,
177 -).reduce(
178 - (acc, rule) => {
179 - acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
180 - return acc;
181 - },
182 - {} as RulesConfig,
183 -);
174 + rule => rule.preset === LintRulePreset.Recommended,
175 +).reduce((acc, rule) => {
176 + acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
177 + return acc;
178 +}, {} as RulesConfig);
179 +
180 +export const recommendedLatestRules: RulesConfig = LintRules.filter(
181 + rule =>
182 + rule.preset === LintRulePreset.Recommended ||
183 + rule.preset === LintRulePreset.RecommendedLatest,
184 +).reduce((acc, rule) => {
185 + acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
186 + return acc;
187 +}, {} as RulesConfig);
188
189 export function mapErrorSeverityToESlint(
190 severity: ErrorSeverity,