@samitouri / QOS-React / commits / cf7d895db6

[compiler:eslint] Fix false positive with TS type param syntax

Previously we would attempt to parse code in the eslint plugin with the HermesParser first as it can handle some TS syntax. However, this was leading to a mis-parse of React hook calls with type params (eg, `useRef<null>()` as a BinaryExpression rather than a CallExpression with a type param. This triggered our validation that Hooks should not be used as normal values. To fix this, we now try to parse with the babel parser (with TS support) for filenames that end with ts/tsx, and fallback to HermesParser for regular JS files. ghstack-source-id: 5b7231031cace749a6e689412b3e8b5c9d03ed4b Pull Request resolved: https://github.com/facebook/react/pull/29081

Lauren Tan committed May 15, 2024 at 15:36 UTC cf7d895db6aa48fb63e6a11d2ef2ae3ea5c48169
2 files changed +16 -26
compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts
+3 -9
@@ -34,10 +34,8 @@ const tests: CompilerTestCases = {
34 }
35 `,
36 },
37 - ],
38 - invalid: [
37 {
40 - name: "[FALSE POSITIVE] Repro for hooks as normal values",
38 + name: "Repro for hooks as normal values",
39 filename: "test.tsx",
40 code: normalizeIndent`
41 function Button(props) {
@@ -45,13 +43,9 @@ const tests: CompilerTestCases = {
43 return <Button thing={scrollview} />;
44 }
45 `,
48 - errors: [
49 - {
50 - message:
51 - "Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values",
52 - },
53 - ],
46 },
47 + ],
48 + invalid: [
49 {
50 name: "Mutating useState value",
51 filename: "test.tsx",
compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts
+13 -17
@@ -114,29 +114,25 @@ const rule: Rule.RuleModule = {
114 }
115
116 let babelAST;
117 - try {
118 - // first try parsing with the faster Hermes that also supports JS, Flow
119 - // and most TS syntax
117 + if (
118 + context.filename.endsWith(".tsx") ||
119 + context.filename.endsWith(".ts")
120 + ) {
121 + try {
122 + const { parse: babelParse } = require("@babel/parser");
123 + babelAST = babelParse(sourceCode, {
124 + filename,
125 + sourceType: "unambiguous",
126 + plugins: ["typescript", "jsx"],
127 + });
128 + } catch {}
129 + } else {
130 babelAST = HermesParser.parse(sourceCode, {
131 babel: true,
132 enableExperimentalComponentSyntax: true,
133 sourceFilename: filename,
134 sourceType: "module",
135 });
126 - } catch {
127 - // If Hermes fails, try Babel for advanced TS syntax.
128 - if (
129 - context.filename.endsWith(".tsx") ||
130 - context.filename.endsWith(".ts")
131 - ) {
132 - try {
133 - const { parse: babelParse } = require("@babel/parser");
134 - babelAST = babelParse(sourceCode, {
135 - sourceType: "unambiguous",
136 - plugins: ["typescript", "jsx"],
137 - });
138 - } catch {}
139 - }
136 }
137
138 if (babelAST != null) {