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"