diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts
index c87bcadbe98bc..0cd28702ae8b7 100644
--- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts
+++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts
@@ -199,6 +199,44 @@ const tests: CompilerTestCases = {
},
],
},
+ {
+ name: "'use no forget' does not disable eslint rule",
+ code: normalizeIndent`
+ let count = 0;
+ function Component() {
+ 'use no forget';
+ count = count + 1;
+ return
Hello world {count}
+ }
+ `,
+ errors: [
+ {
+ message:
+ 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)',
+ },
+ ],
+ },
+ {
+ name: "Unused 'use no forget' directive is reported when no errors are present",
+ code: normalizeIndent`
+ function Component() {
+ 'use no forget';
+ return Hello world
+ }
+ `,
+ errors: [
+ {
+ message: "Unused 'use no forget' directive",
+ suggestions: [
+ {
+ output:
+ // yuck
+ '\nfunction Component() {\n \n return Hello world
\n}\n',
+ },
+ ],
+ },
+ ],
+ },
],
};
diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts
index 2fe34a0b7ff2b..27a600fe0cab5 100644
--- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts
+++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts
@@ -15,10 +15,12 @@ import BabelPluginReactCompiler, {
ErrorSeverity,
parsePluginOptions,
validateEnvironmentConfig,
+ OPT_OUT_DIRECTIVES,
type PluginOptions,
} from 'babel-plugin-react-compiler/src';
import {Logger} from 'babel-plugin-react-compiler/src/Entrypoint';
import type {Rule} from 'eslint';
+import {Statement} from 'estree';
import * as HermesParser from 'hermes-parser';
type CompilerErrorDetailWithLoc = Omit & {
@@ -146,6 +148,7 @@ const rule: Rule.RuleModule = {
userOpts['__unstable_donotuse_reportAllBailouts'];
}
+ let shouldReportUnusedOptOutDirective = true;
const options: PluginOptions = {
...parsePluginOptions(userOpts),
...COMPILER_OPTIONS,
@@ -155,6 +158,7 @@ const rule: Rule.RuleModule = {
logEvent: (filename, event): void => {
userLogger?.logEvent(filename, event);
if (event.kind === 'CompileError') {
+ shouldReportUnusedOptOutDirective = false;
const detail = event.detail;
const suggest = makeSuggestions(detail);
if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) {
@@ -269,7 +273,52 @@ const rule: Rule.RuleModule = {
/* errors handled by injected logger */
}
}
- return {};
+
+ function reportUnusedOptOutDirective(stmt: Statement) {
+ if (
+ stmt.type === 'ExpressionStatement' &&
+ stmt.expression.type === 'Literal' &&
+ typeof stmt.expression.value === 'string' &&
+ OPT_OUT_DIRECTIVES.has(stmt.expression.value) &&
+ stmt.loc != null
+ ) {
+ context.report({
+ message: `Unused '${stmt.expression.value}' directive`,
+ loc: stmt.loc,
+ suggest: [
+ {
+ desc: 'Remove the directive',
+ fix(fixer) {
+ return fixer.remove(stmt);
+ },
+ },
+ ],
+ });
+ }
+ }
+ if (shouldReportUnusedOptOutDirective) {
+ return {
+ FunctionDeclaration(fnDecl) {
+ for (const stmt of fnDecl.body.body) {
+ reportUnusedOptOutDirective(stmt);
+ }
+ },
+ ArrowFunctionExpression(fnExpr) {
+ if (fnExpr.body.type === 'BlockStatement') {
+ for (const stmt of fnExpr.body.body) {
+ reportUnusedOptOutDirective(stmt);
+ }
+ }
+ },
+ FunctionExpression(fnExpr) {
+ for (const stmt of fnExpr.body.body) {
+ reportUnusedOptOutDirective(stmt);
+ }
+ },
+ };
+ } else {
+ return {};
+ }
},
};