[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.
This commit is contained in:
Lauren Tan
2023-04-06 19:14:08 -04:00
parent 2eae7dc6b9
commit fcfb66914a
7 changed files with 173 additions and 4 deletions
+67 -4
View File
@@ -10,7 +10,11 @@
import type * as BabelCore from "@babel/core";
import jsx from "@babel/plugin-syntax-jsx";
import * as t from "@babel/types";
import { CompilerError } from "../CompilerError";
import {
CompilerError,
CompilerErrorDetail,
ErrorSeverity,
} from "../CompilerError";
import { compile } from "../CompilerPipeline";
import { GeneratedSource } from "../HIR";
import {
@@ -23,9 +27,13 @@ type BabelPluginPass = {
opts: PluginOptions;
};
function hasUseForgetDirective(directives: t.Directive[]): boolean {
function hasUseForgetDirective(directive: t.Directive): boolean {
return directive.value.value === "use forget";
}
function hasAnyUseForgetDirectives(directives: t.Directive[]): boolean {
for (const directive of directives) {
if (directive.value.value === "use forget") {
if (hasUseForgetDirective(directive)) {
return true;
}
}
@@ -122,6 +130,61 @@ export default function ReactForgetBabelPlugin(
// want Forget to run true to source as possible.
Program(path, pass): void {
const options = parsePluginOptions(pass.opts);
const violations = [];
const fileComments = pass.file.ast.comments;
let fileHasUseForgetDirective = false;
if (Array.isArray(fileComments)) {
for (const comment of fileComments) {
if (
/eslint-disable(-next-line)? react-hooks\/(exhaustive-deps|rules-of-hooks)/.test(
comment.value
)
) {
violations.push(comment);
}
}
}
if (violations.length > 0) {
path.traverse({
Directive(path) {
if (hasUseForgetDirective(path.node)) {
fileHasUseForgetDirective = true;
path.stop();
}
},
});
const reason = `Skipped compilation as it disables one or more React eslint rules`;
const error = new CompilerError();
for (const violation of violations) {
if (options.logger != null) {
options.logger.logEvent("err", {
reason,
filename: pass.filename,
violation,
});
}
error.pushErrorDetail(
new CompilerErrorDetail({
reason,
description: violation.value.trim(),
severity: ErrorSeverity.InvalidInput,
codeframe: null,
loc: violation.loc ?? null,
})
);
}
if (fileHasUseForgetDirective) {
throw error;
}
return;
}
try {
path.traverse(visitor, {
...pass,
@@ -154,7 +217,7 @@ function shouldCompile(
if (!body.isBlockStatement()) {
return false;
}
if (!hasUseForgetDirective(body.node.directives)) {
if (!hasAnyUseForgetDirectives(body.node.directives)) {
return false;
}
}
@@ -0,0 +1,22 @@
## Input
```javascript
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
"use forget";
const x = [];
return <div>{x}</div>;
}
/* eslint-enable react-hooks/rules-of-hooks */
```
## Error
```
[ReactForget] InvalidInput: Skipped compilation as it disables one or more React eslint rules. eslint-disable react-hooks/rules-of-hooks (1:1)
```
@@ -0,0 +1,7 @@
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
"use forget";
const x = [];
return <div>{x}</div>;
}
/* eslint-enable react-hooks/rules-of-hooks */
@@ -0,0 +1,35 @@
## Input
```javascript
function Component() {
const item = [];
const foo = useCallback(
() => {
item.push(1);
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return <Button foo={foo} />;
}
```
## Code
```javascript
function Component() {
const item = [];
const foo = useCallback(
() => {
item.push(1);
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return <Button foo={foo} />;
}
```
@@ -0,0 +1,11 @@
function Component() {
const item = [];
const foo = useCallback(
() => {
item.push(1);
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return <Button foo={foo} />;
}
@@ -0,0 +1,25 @@
## Input
```javascript
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
const x = [];
return <div>{x}</div>;
}
/* eslint-enable react-hooks/rules-of-hooks */
```
## Code
```javascript
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
const x = [];
return <div>{x}</div>;
}
/* eslint-enable react-hooks/rules-of-hooks */
```
@@ -0,0 +1,6 @@
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
const x = [];
return <div>{x}</div>;
}
/* eslint-enable react-hooks/rules-of-hooks */