@samitouri / QOS-React-2 / commits / fcfb66914a

[Babel] Skip files that contain one or more disables of React eslint rules

To unblock internal experimentation, for now let's just skip over compiling any file that contains one or more disables of React's eslint rules, and log that. This is a little coarse in the sense that we could skip over just functions that contain the comments, but Babel doesn't provide an easy way to traverse comments afaict so this is the simplest solution. I did check our internal repo and noted that there was only one disable of exhaustive-hooks in that entire directory in one file, so this should be fine. Notably we are not throwing any errors if we detect these violations as we don't want to fail the build, we just want to skip them for now.

Lauren Tan committed Apr 6, 2023 at 19:14 UTC fcfb66914ad98c6b1d09cfc232bb42729e85848f
7 files changed +173 -4
compiler/forget/src/Babel/BabelPlugin.ts
+67 -4
@@ -10,7 +10,11 @@
10 import type * as BabelCore from "@babel/core";
11 import jsx from "@babel/plugin-syntax-jsx";
12 import * as t from "@babel/types";
13 -import { CompilerError } from "../CompilerError";
13 +import {
14 + CompilerError,
15 + CompilerErrorDetail,
16 + ErrorSeverity,
17 +} from "../CompilerError";
18 import { compile } from "../CompilerPipeline";
19 import { GeneratedSource } from "../HIR";
20 import {
@@ -23,9 +27,13 @@ type BabelPluginPass = {
27 opts: PluginOptions;
28 };
29
26 -function hasUseForgetDirective(directives: t.Directive[]): boolean {
30 +function hasUseForgetDirective(directive: t.Directive): boolean {
31 + return directive.value.value === "use forget";
32 +}
33 +
34 +function hasAnyUseForgetDirectives(directives: t.Directive[]): boolean {
35 for (const directive of directives) {
28 - if (directive.value.value === "use forget") {
36 + if (hasUseForgetDirective(directive)) {
37 return true;
38 }
39 }
@@ -122,6 +130,61 @@ export default function ReactForgetBabelPlugin(
130 // want Forget to run true to source as possible.
131 Program(path, pass): void {
132 const options = parsePluginOptions(pass.opts);
133 +
134 + const violations = [];
135 + const fileComments = pass.file.ast.comments;
136 + let fileHasUseForgetDirective = false;
137 + if (Array.isArray(fileComments)) {
138 + for (const comment of fileComments) {
139 + if (
140 + /eslint-disable(-next-line)? react-hooks\/(exhaustive-deps|rules-of-hooks)/.test(
141 + comment.value
142 + )
143 + ) {
144 + violations.push(comment);
145 + }
146 + }
147 + }
148 +
149 + if (violations.length > 0) {
150 + path.traverse({
151 + Directive(path) {
152 + if (hasUseForgetDirective(path.node)) {
153 + fileHasUseForgetDirective = true;
154 + path.stop();
155 + }
156 + },
157 + });
158 +
159 + const reason = `Skipped compilation as it disables one or more React eslint rules`;
160 + const error = new CompilerError();
161 + for (const violation of violations) {
162 + if (options.logger != null) {
163 + options.logger.logEvent("err", {
164 + reason,
165 + filename: pass.filename,
166 + violation,
167 + });
168 + }
169 +
170 + error.pushErrorDetail(
171 + new CompilerErrorDetail({
172 + reason,
173 + description: violation.value.trim(),
174 + severity: ErrorSeverity.InvalidInput,
175 + codeframe: null,
176 + loc: violation.loc ?? null,
177 + })
178 + );
179 + }
180 +
181 + if (fileHasUseForgetDirective) {
182 + throw error;
183 + }
184 +
185 + return;
186 + }
187 +
188 try {
189 path.traverse(visitor, {
190 ...pass,
@@ -154,7 +217,7 @@ function shouldCompile(
217 if (!body.isBlockStatement()) {
218 return false;
219 }
157 - if (!hasUseForgetDirective(body.node.directives)) {
220 + if (!hasAnyUseForgetDirectives(body.node.directives)) {
221 return false;
222 }
223 }
compiler/forget/src/__tests__/fixtures/compiler/error.sketchy-code-use-forget.expect.md new
+22
@@ -0,0 +1,22 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +/* eslint-disable react-hooks/rules-of-hooks */
6 +function lowercasecomponent() {
7 + "use forget";
8 + const x = [];
9 + return <div>{x}</div>;
10 +}
11 +/* eslint-enable react-hooks/rules-of-hooks */
12 +
13 +```
14 +
15 +
16 +## Error
17 +
18 +```
19 +[ReactForget] InvalidInput: Skipped compilation as it disables one or more React eslint rules. eslint-disable react-hooks/rules-of-hooks (1:1)
20 +```
21 +
22 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/error.sketchy-code-use-forget.js new
+7
@@ -0,0 +1,7 @@
1 +/* eslint-disable react-hooks/rules-of-hooks */
2 +function lowercasecomponent() {
3 + "use forget";
4 + const x = [];
5 + return <div>{x}</div>;
6 +}
7 +/* eslint-enable react-hooks/rules-of-hooks */
compiler/forget/src/__tests__/fixtures/compiler/sketchy-code-exhaustive-deps.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component() {
6 + const item = [];
7 + const foo = useCallback(
8 + () => {
9 + item.push(1);
10 + }, // eslint-disable-next-line react-hooks/exhaustive-deps
11 + []
12 + );
13 +
14 + return <Button foo={foo} />;
15 +}
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function Component() {
23 + const item = [];
24 + const foo = useCallback(
25 + () => {
26 + item.push(1);
27 + }, // eslint-disable-next-line react-hooks/exhaustive-deps
28 + []
29 + );
30 +
31 + return <Button foo={foo} />;
32 +}
33 +
34 +```
35 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/sketchy-code-exhaustive-deps.js new
+11
@@ -0,0 +1,11 @@
1 +function Component() {
2 + const item = [];
3 + const foo = useCallback(
4 + () => {
5 + item.push(1);
6 + }, // eslint-disable-next-line react-hooks/exhaustive-deps
7 + []
8 + );
9 +
10 + return <Button foo={foo} />;
11 +}
compiler/forget/src/__tests__/fixtures/compiler/sketchy-code-rules-of-hooks.expect.md new
+25
@@ -0,0 +1,25 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +/* eslint-disable react-hooks/rules-of-hooks */
6 +function lowercasecomponent() {
7 + const x = [];
8 + return <div>{x}</div>;
9 +}
10 +/* eslint-enable react-hooks/rules-of-hooks */
11 +
12 +```
13 +
14 +## Code
15 +
16 +```javascript
17 +/* eslint-disable react-hooks/rules-of-hooks */
18 +function lowercasecomponent() {
19 + const x = [];
20 + return <div>{x}</div>;
21 +}
22 +/* eslint-enable react-hooks/rules-of-hooks */
23 +
24 +```
25 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/sketchy-code-rules-of-hooks.js new
+6
@@ -0,0 +1,6 @@
1 +/* eslint-disable react-hooks/rules-of-hooks */
2 +function lowercasecomponent() {
3 + const x = [];
4 + return <div>{x}</div>;
5 +}
6 +/* eslint-enable react-hooks/rules-of-hooks */