main
ts 169 lines 4.47 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import {
9 normalizeIndent,
10 testRule,
11 makeTestCaseError,
12 TestRecommendedRules,
13 } from './shared-utils';
14 import {RuleTester as ESLintTester} from 'eslint';
15 import {allRules, recommendedRules} from '../src/rules/ReactCompilerRule';
16 import {configs} from '../src/index';
17 import type {Rule} from 'eslint';
18
19 /**
20 * Check if the Rust compiler native module is available.
21 * Tests in this file are skipped if the module is not built.
22 */
23 let rustAvailable = false;
24 try {
25 require('babel-plugin-react-compiler-rust');
26 rustAvailable = true;
27 } catch {
28 rustAvailable = false;
29 }
30
31 const describeIfRust = rustAvailable ? describe : describe.skip;
32
33 /**
34 * Aggregates all recommended rules but passes __unstable_useRustCompiler
35 * to each rule via the options.
36 */
37 const TestRecommendedRulesRust: Rule.RuleModule = {
38 meta: {
39 type: 'problem',
40 docs: {
41 description: 'Test recommended rules with Rust backend',
42 category: 'Possible Errors',
43 recommended: true,
44 },
45 schema: [{type: 'object', additionalProperties: true}],
46 },
47 create(context) {
48 for (const ruleConfig of Object.values(
49 configs.recommended.plugins['react-compiler'].rules,
50 )) {
51 const listener = ruleConfig.rule.create(context);
52 if (Object.entries(listener).length !== 0) {
53 throw new Error('TODO: handle rules that return listeners to eslint');
54 }
55 }
56 return {};
57 },
58 };
59
60 function testRuleWithRust(
61 name: string,
62 rule: Rule.RuleModule,
63 tests: {
64 valid: ESLintTester.ValidTestCase[];
65 invalid: ESLintTester.InvalidTestCase[];
66 },
67 ): void {
68 const eslintTester = new ESLintTester({
69 // @ts-ignore[2353] - outdated types
70 parser: require.resolve('hermes-eslint'),
71 parserOptions: {
72 ecmaVersion: 2015,
73 sourceType: 'module',
74 enableExperimentalComponentSyntax: true,
75 },
76 });
77
78 // Inject __unstable_useRustCompiler into all test cases
79 const withRust = (
80 cases: ESLintTester.ValidTestCase[],
81 ): ESLintTester.ValidTestCase[] =>
82 cases.map(tc => ({
83 ...tc,
84 options: [{...(tc.options?.[0] ?? {}), __unstable_useRustCompiler: true}],
85 }));
86
87 const withRustInvalid = (
88 cases: ESLintTester.InvalidTestCase[],
89 ): ESLintTester.InvalidTestCase[] =>
90 cases.map(tc => ({
91 ...tc,
92 options: [{...(tc.options?.[0] ?? {}), __unstable_useRustCompiler: true}],
93 }));
94
95 eslintTester.run(name, rule, {
96 valid: withRust(tests.valid),
97 invalid: withRustInvalid(tests.invalid),
98 });
99 }
100
101 describeIfRust('Rust backend', () => {
102 testRuleWithRust('rust-backend-recommended', TestRecommendedRulesRust, {
103 valid: [
104 {
105 name: 'Basic component compiles without errors',
106 code: normalizeIndent`
107 function Component(props) {
108 return <div>{props.text}</div>;
109 }
110 `,
111 },
112 {
113 name: 'Component with hooks compiles without errors',
114 code: normalizeIndent`
115 import {useState} from 'react';
116 function Component(props) {
117 const [state, setState] = useState(0);
118 return <div onClick={() => setState(state + 1)}>{state}</div>;
119 }
120 `,
121 },
122 {
123 name: "Classes don't throw",
124 code: normalizeIndent`
125 class Foo {
126 #bar() {}
127 }
128 `,
129 },
130 ],
131 invalid: [
132 {
133 name: 'Conditional hook call detected by Rust backend',
134 code: normalizeIndent`
135 function Component() {
136 const result = cond ?? useConditionalHook();
137 return <div>{result}</div>;
138 }
139 `,
140 errors: [
141 makeTestCaseError(
142 'Hooks must always be called in a consistent order',
143 ),
144 ],
145 },
146 {
147 name: 'Multiple diagnostics detected by Rust backend',
148 code: normalizeIndent`
149 function useConditional1() {
150 'use memo';
151 return cond ?? useConditionalHook();
152 }
153 function useConditional2(props) {
154 'use memo';
155 return props.cond && useConditionalHook();
156 }
157 `,
158 errors: [
159 makeTestCaseError(
160 'Hooks must always be called in a consistent order',
161 ),
162 makeTestCaseError(
163 'Hooks must always be called in a consistent order',
164 ),
165 ],
166 },
167 ],
168 });
169 });