Skip to content

Commit 3a502d7

Browse files
committed
[compiler] Setup RecommendedLatest preset
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.
1 parent 8f8aaab commit 3a502d7

File tree

5 files changed

+96
-53
lines changed

5 files changed

+96
-53
lines changed

compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,21 @@ export enum ErrorCategory {
669669
FBT = 'FBT',
670670
}
671671

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+
672687
export type LintRule = {
673688
// Stores the category the rule corresponds to, used to filter errors when reporting
674689
category: ErrorCategory;
@@ -689,15 +704,14 @@ export type LintRule = {
689704
description: string;
690705

691706
/**
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.
695709
*
696710
* NOTE: not all validations are enabled by default! Setting this flag only affects
697711
* whether a given rule is part of the recommended set. The corresponding validation
698712
* also should be enabled by default if you want the error to actually show up!
699713
*/
700-
recommended: boolean;
714+
preset: LintRulePreset;
701715
};
702716

703717
const RULE_NAME_PATTERN = /^[a-z]+(-[a-z]+)*$/;
@@ -720,7 +734,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
720734
name: 'automatic-effect-dependencies',
721735
description:
722736
'Verifies that automatic effect dependencies are compiled if opted-in',
723-
recommended: false,
737+
preset: LintRulePreset.Off,
724738
};
725739
}
726740
case ErrorCategory.CapitalizedCalls: {
@@ -730,7 +744,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
730744
name: 'capitalized-calls',
731745
description:
732746
'Validates against calling capitalized functions/methods instead of using JSX',
733-
recommended: false,
747+
preset: LintRulePreset.Off,
734748
};
735749
}
736750
case ErrorCategory.Config: {
@@ -739,7 +753,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
739753
severity: ErrorSeverity.Error,
740754
name: 'config',
741755
description: 'Validates the compiler configuration options',
742-
recommended: true,
756+
preset: LintRulePreset.Recommended,
743757
};
744758
}
745759
case ErrorCategory.EffectDependencies: {
@@ -748,7 +762,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
748762
severity: ErrorSeverity.Error,
749763
name: 'memoized-effect-dependencies',
750764
description: 'Validates that effect dependencies are memoized',
751-
recommended: false,
765+
preset: LintRulePreset.Off,
752766
};
753767
}
754768
case ErrorCategory.EffectDerivationsOfState: {
@@ -758,7 +772,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
758772
name: 'no-deriving-state-in-effects',
759773
description:
760774
'Validates against deriving values from state in an effect',
761-
recommended: false,
775+
preset: LintRulePreset.Off,
762776
};
763777
}
764778
case ErrorCategory.EffectSetState: {
@@ -768,7 +782,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
768782
name: 'set-state-in-effect',
769783
description:
770784
'Validates against calling setState synchronously in an effect, which can lead to re-renders that degrade performance',
771-
recommended: true,
785+
preset: LintRulePreset.Recommended,
772786
};
773787
}
774788
case ErrorCategory.ErrorBoundaries: {
@@ -778,7 +792,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
778792
name: 'error-boundaries',
779793
description:
780794
'Validates usage of error boundaries instead of try/catch for errors in child components',
781-
recommended: true,
795+
preset: LintRulePreset.Recommended,
782796
};
783797
}
784798
case ErrorCategory.Factories: {
@@ -789,7 +803,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
789803
description:
790804
'Validates against higher order functions defining nested components or hooks. ' +
791805
'Components and hooks should be defined at the module level',
792-
recommended: true,
806+
preset: LintRulePreset.Recommended,
793807
};
794808
}
795809
case ErrorCategory.FBT: {
@@ -798,7 +812,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
798812
severity: ErrorSeverity.Error,
799813
name: 'fbt',
800814
description: 'Validates usage of fbt',
801-
recommended: false,
815+
preset: LintRulePreset.Off,
802816
};
803817
}
804818
case ErrorCategory.Fire: {
@@ -807,7 +821,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
807821
severity: ErrorSeverity.Error,
808822
name: 'fire',
809823
description: 'Validates usage of `fire`',
810-
recommended: false,
824+
preset: LintRulePreset.Off,
811825
};
812826
}
813827
case ErrorCategory.Gating: {
@@ -817,7 +831,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
817831
name: 'gating',
818832
description:
819833
'Validates configuration of [gating mode](https://react.dev/reference/react-compiler/gating)',
820-
recommended: true,
834+
preset: LintRulePreset.Recommended,
821835
};
822836
}
823837
case ErrorCategory.Globals: {
@@ -828,7 +842,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
828842
description:
829843
'Validates against assignment/mutation of globals during render, part of ensuring that ' +
830844
'[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,
832846
};
833847
}
834848
case ErrorCategory.Hooks: {
@@ -842,7 +856,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
842856
* We need to dedeupe these (moving the remaining bits into the compiler) and then enable
843857
* this rule.
844858
*/
845-
recommended: false,
859+
preset: LintRulePreset.Off,
846860
};
847861
}
848862
case ErrorCategory.Immutability: {
@@ -852,7 +866,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
852866
name: 'immutability',
853867
description:
854868
'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,
856870
};
857871
}
858872
case ErrorCategory.Invariant: {
@@ -861,7 +875,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
861875
severity: ErrorSeverity.Error,
862876
name: 'invariant',
863877
description: 'Internal invariants',
864-
recommended: false,
878+
preset: LintRulePreset.Off,
865879
};
866880
}
867881
case ErrorCategory.PreserveManualMemo: {
@@ -873,7 +887,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
873887
'Validates that existing manual memoized is preserved by the compiler. ' +
874888
'React Compiler will only compile components and hooks if its inference ' +
875889
'[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,
877891
};
878892
}
879893
case ErrorCategory.Purity: {
@@ -883,7 +897,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
883897
name: 'purity',
884898
description:
885899
'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,
887901
};
888902
}
889903
case ErrorCategory.Refs: {
@@ -893,7 +907,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
893907
name: 'refs',
894908
description:
895909
'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,
897911
};
898912
}
899913
case ErrorCategory.RenderSetState: {
@@ -903,7 +917,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
903917
name: 'set-state-in-render',
904918
description:
905919
'Validates against setting state during render, which can trigger additional renders and potential infinite render loops',
906-
recommended: true,
920+
preset: LintRulePreset.Recommended,
907921
};
908922
}
909923
case ErrorCategory.StaticComponents: {
@@ -913,7 +927,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
913927
name: 'static-components',
914928
description:
915929
'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,
917931
};
918932
}
919933
case ErrorCategory.Suppression: {
@@ -922,7 +936,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
922936
severity: ErrorSeverity.Error,
923937
name: 'rule-suppression',
924938
description: 'Validates against suppression of other rules',
925-
recommended: false,
939+
preset: LintRulePreset.Off,
926940
};
927941
}
928942
case ErrorCategory.Syntax: {
@@ -931,7 +945,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
931945
severity: ErrorSeverity.Error,
932946
name: 'syntax',
933947
description: 'Validates against invalid syntax',
934-
recommended: false,
948+
preset: LintRulePreset.Off,
935949
};
936950
}
937951
case ErrorCategory.Todo: {
@@ -940,7 +954,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
940954
severity: ErrorSeverity.Hint,
941955
name: 'todo',
942956
description: 'Unimplemented features',
943-
recommended: false,
957+
preset: LintRulePreset.Off,
944958
};
945959
}
946960
case ErrorCategory.UnsupportedSyntax: {
@@ -950,7 +964,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
950964
name: 'unsupported-syntax',
951965
description:
952966
'Validates against syntax that we do not plan to support in React Compiler',
953-
recommended: true,
967+
preset: LintRulePreset.Recommended,
954968
};
955969
}
956970
case ErrorCategory.UseMemo: {
@@ -960,7 +974,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
960974
name: 'use-memo',
961975
description:
962976
'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,
964978
};
965979
}
966980
case ErrorCategory.IncompatibleLibrary: {
@@ -970,7 +984,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
970984
name: 'incompatible-library',
971985
description:
972986
'Validates against usage of libraries which are incompatible with memoization (manual or automatic)',
973-
recommended: true,
987+
preset: LintRulePreset.Recommended,
974988
};
975989
}
976990
default: {

compiler/packages/babel-plugin-react-compiler/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export {
1414
ErrorSeverity,
1515
ErrorCategory,
1616
LintRules,
17+
LintRulePreset,
1718
type CompilerErrorDetailOptions,
1819
type CompilerDiagnosticOptions,
1920
type CompilerDiagnosticDetail,

compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {Linter, Rule} from 'eslint';
1515
import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler';
1616
import {
1717
ErrorSeverity,
18+
LintRulePreset,
1819
LintRules,
1920
type LintRule,
2021
} from 'babel-plugin-react-compiler/src/CompilerError';
@@ -150,7 +151,7 @@ function makeRule(rule: LintRule): Rule.RuleModule {
150151
type: 'problem',
151152
docs: {
152153
description: rule.description,
153-
recommended: rule.recommended,
154+
recommended: rule.preset === LintRulePreset.Recommended,
154155
},
155156
fixable: 'code',
156157
hasSuggestions: true,
@@ -171,7 +172,16 @@ export const allRules: RulesConfig = LintRules.reduce((acc, rule) => {
171172
}, {} as RulesConfig);
172173

173174
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,
175185
).reduce((acc, rule) => {
176186
acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
177187
return acc;

packages/eslint-plugin-react-hooks/src/index.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
allRules,
1212
mapErrorSeverityToESlint,
1313
recommendedRules,
14+
recommendedLatestRules,
1415
} from './shared/ReactCompiler';
1516
import RulesOfHooks from './rules/RulesOfHooks';
1617

@@ -27,7 +28,7 @@ const basicRuleConfigs = {
2728
'react-hooks/exhaustive-deps': 'warn',
2829
} as const satisfies Linter.RulesRecord;
2930

30-
const compilerRuleConfigs = Object.fromEntries(
31+
const recommendedCompilerRuleConfigs = Object.fromEntries(
3132
Object.entries(recommendedRules).map(([name, ruleConfig]) => {
3233
return [
3334
`react-hooks/${name}` as const,
@@ -36,9 +37,22 @@ const compilerRuleConfigs = Object.fromEntries(
3637
}),
3738
) as Record<`react-hooks/${string}`, Linter.RuleEntry>;
3839

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 = {
4054
...basicRuleConfigs,
41-
...compilerRuleConfigs,
55+
...recommendedLatestCompilerRuleConfigs,
4256
};
4357

4458
const plugins = ['react-hooks'];
@@ -51,11 +65,11 @@ type ReactHooksFlatConfig = {
5165
const configs = {
5266
recommended: {
5367
plugins,
54-
rules: allRuleConfigs,
68+
rules: recommendedRuleConfigs,
5569
},
5670
'recommended-latest': {
5771
plugins,
58-
rules: allRuleConfigs,
72+
rules: recommendedLatestRuleConfigs,
5973
},
6074
flat: {} as Record<string, ReactHooksFlatConfig>,
6175
};

0 commit comments

Comments
 (0)