@samitouri / QOS-React-1 / commits / 76bb13cd26

First attempt at making the linter work with advanced TypeScript syntax

First attempt at making the linter work with advanced TypeScript syntax Falls back to the babel parser for some advanced syntax like string template syntax. This is pretty hacky as it doesn't take in any parsing options that are configured for the outer ESLint parser, not sure how that could be handled.

Jan Kassens committed Apr 4, 2024 at 18:31 UTC 76bb13cd26717312fc5fc5d4a69b4938e7ce27a9
4 files changed +123 -11
compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts new
+66
@@ -0,0 +1,66 @@
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 { RuleTester } from "eslint";
9 +import ReactCompilerRule from "../src/rules/ReactCompilerRule";
10 +
11 +/**
12 + * A string template tag that removes padding from the left side of multi-line strings
13 + * @param {Array} strings array of code strings (only one expected)
14 + */
15 +function normalizeIndent(strings: TemplateStringsArray): string {
16 + const codeLines = strings[0].split("\n");
17 + const leftPadding = codeLines[1].match(/\s+/)[0];
18 + return codeLines.map((line) => line.slice(leftPadding.length)).join("\n");
19 +}
20 +
21 +type CompilerTestCases = {
22 + valid: RuleTester.ValidTestCase[];
23 + invalid: RuleTester.InvalidTestCase[];
24 +};
25 +
26 +const tests: CompilerTestCases = {
27 + valid: [
28 + {
29 + name: "Basic example",
30 + filename: "test.tsx",
31 + code: normalizeIndent`
32 + function Button(props) {
33 + return null;
34 + }
35 + `,
36 + },
37 + ],
38 + invalid: [
39 + {
40 + name: "Mutating useState value",
41 + filename: "test.tsx",
42 + code: `
43 + import { useState } from 'react';
44 + function Component(props) {
45 + // typescript syntax that hermes-parser doesn't understand yet
46 + const x: \`foo\${1}\` = 'foo1';
47 + const [state, setState] = useState({a: 0});
48 + state.a = 1;
49 + return <div>{props.foo}</div>;
50 + }
51 + `,
52 + errors: [
53 + {
54 + message:
55 + "Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead.",
56 + line: 7,
57 + },
58 + ],
59 + },
60 + ],
61 +};
62 +
63 +const eslintTester = new RuleTester({
64 + parser: require.resolve("@typescript-eslint/parser"),
65 +});
66 +eslintTester.run("react-compiler", ReactCompilerRule, tests);
compiler/packages/eslint-plugin-react-compiler/package.json
+2 -1
@@ -7,7 +7,8 @@
7 "test": "tsc && jest"
8 },
9 "dependencies": {
10 - "@babel/core": "^7.19.1",
10 + "@babel/core": "^7.24.4",
11 + "@babel/parser": "^7.24.4",
12 "@babel/plugin-proposal-private-methods": "^7.18.6",
13 "hermes-parser": "^0.20.1",
14 "zod": "^3.22.4",
compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts
+26 -6
@@ -112,12 +112,32 @@ const rule: Rule.RuleModule = {
112 return false;
113 }
114
115 - const babelAST = HermesParser.parse(sourceCode, {
116 - babel: true,
117 - enableExperimentalComponentSyntax: true,
118 - sourceFilename: filename,
119 - sourceType: "module",
120 - });
115 + let babelAST;
116 + try {
117 + // first try parsing with the faster Hermes that also supports JS, Flow
118 + // and most TS syntax
119 + babelAST = HermesParser.parse(sourceCode, {
120 + babel: true,
121 + enableExperimentalComponentSyntax: true,
122 + sourceFilename: filename,
123 + sourceType: "module",
124 + });
125 + } catch {
126 + // If Hermes fails, try Babel for advanced TS syntax.
127 + if (
128 + context.filename.endsWith(".tsx") ||
129 + context.filename.endsWith(".ts")
130 + ) {
131 + try {
132 + const { parse: babelParse } = require("@babel/parser");
133 + babelAST = babelParse(sourceCode, {
134 + sourceType: "unambiguous",
135 + plugins: ["typescript", "jsx"],
136 + });
137 + } catch {}
138 + }
139 + }
140 +
141 if (babelAST != null) {
142 try {
143 transformFromAstSync(babelAST, sourceCode, {
compiler/yarn.lock
+29 -4
@@ -79,7 +79,7 @@
79 semver "^5.4.1"
80 source-map "^0.5.0"
81
82 -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.19.1":
82 +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.19.1", "@babel/core@^7.24.4":
83 version "7.24.4"
84 resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717"
85 integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==
@@ -8887,7 +8887,16 @@ string-length@^4.0.1:
8887 char-regex "^1.0.2"
8888 strip-ansi "^6.0.0"
8889
8890 -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
8890 +"string-width-cjs@npm:string-width@^4.2.0":
8891 + version "4.2.3"
8892 + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
8893 + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
8894 + dependencies:
8895 + emoji-regex "^8.0.0"
8896 + is-fullwidth-code-point "^3.0.0"
8897 + strip-ansi "^6.0.1"
8898 +
8899 +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
8900 version "4.2.3"
8901 resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
8902 integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -8973,7 +8982,14 @@ string.prototype.trimstart@^1.0.7:
8982 define-properties "^1.2.0"
8983 es-abstract "^1.22.1"
8984
8976 -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
8985 +"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
8986 + version "6.0.1"
8987 + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
8988 + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
8989 + dependencies:
8990 + ansi-regex "^5.0.1"
8991 +
8992 +strip-ansi@^6.0.0, strip-ansi@^6.0.1:
8993 version "6.0.1"
8994 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
8995 integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -9616,7 +9632,7 @@ wordwrap@>=0.0.2:
9632 resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
9633 integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
9634
9619 -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
9635 +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
9636 version "7.0.0"
9637 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
9638 integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -9634,6 +9650,15 @@ wrap-ansi@^6.2.0:
9650 string-width "^4.1.0"
9651 strip-ansi "^6.0.0"
9652
9653 +wrap-ansi@^7.0.0:
9654 + version "7.0.0"
9655 + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
9656 + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
9657 + dependencies:
9658 + ansi-styles "^4.0.0"
9659 + string-width "^4.1.0"
9660 + strip-ansi "^6.0.0"
9661 +
9662 wrap-ansi@^8.1.0:
9663 version "8.1.0"
9664 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"