diff --git a/compiler/forget/src/Babel/BabelPlugin.ts b/compiler/forget/src/Babel/BabelPlugin.ts index f98d1a9969..180cde0fc3 100644 --- a/compiler/forget/src/Babel/BabelPlugin.ts +++ b/compiler/forget/src/Babel/BabelPlugin.ts @@ -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; } } diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.sketchy-code-use-forget.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/error.sketchy-code-use-forget.expect.md new file mode 100644 index 0000000000..ed9b3e6c7a --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.sketchy-code-use-forget.expect.md @@ -0,0 +1,22 @@ + +## Input + +```javascript +/* eslint-disable react-hooks/rules-of-hooks */ +function lowercasecomponent() { + "use forget"; + const x = []; + return
{x}
; +} +/* 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) +``` + + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.sketchy-code-use-forget.js b/compiler/forget/src/__tests__/fixtures/compiler/error.sketchy-code-use-forget.js new file mode 100644 index 0000000000..08f080632c --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.sketchy-code-use-forget.js @@ -0,0 +1,7 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +function lowercasecomponent() { + "use forget"; + const x = []; + return
{x}
; +} +/* eslint-enable react-hooks/rules-of-hooks */ diff --git a/compiler/forget/src/__tests__/fixtures/compiler/sketchy-code-exhaustive-deps.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/sketchy-code-exhaustive-deps.expect.md new file mode 100644 index 0000000000..bb10d0f524 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/sketchy-code-exhaustive-deps.expect.md @@ -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