Repository: facebook/react
Pull request: [eprh] Remove NoUnusedOptOutDirectives


This rule was a leftover from a while ago and doesn't actually lint anything useful. Specifically, you get a lint error if you try to opt out a component that isn't already bailing out. If there's a bailout the compiler already safely skips over it, so adding `'use no memo'` there is unnecessary.

Fixes #31407

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34703).
* __->__ #34703
* #34700

Changed files:
filename: compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts
patch: @@ -1,58 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import {NoUnusedDirectivesRule} from '../src/rules/ReactCompilerRule';
-import {normalizeIndent, testRule} from './shared-utils';
-
-testRule('no unused directives rule', NoUnusedDirectivesRule, {
-  valid: [],
-  invalid: [
-    {
-      name: "Unused 'use no forget' directive is reported when no errors are present on components",
-      code: normalizeIndent`
-        function Component() {
-          'use no forget';
-          return <div>Hello world</div>
-        }
-      `,
-      errors: [
-        {
-          message: "Unused 'use no forget' directive",
-          suggestions: [
-            {
-              output:
-                // yuck
-                '\nfunction Component() {\n  \n  return <div>Hello world</div>\n}\n',
-            },
-          ],
-        },
-      ],
-    },
-
-    {
-      name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks",
-      code: normalizeIndent`
-        function notacomponent() {
-          'use no forget';
-          return 1 + 1;
-        }
-      `,
-      errors: [
-        {
-          message: "Unused 'use no forget' directive",
-          suggestions: [
-            {
-              output:
-                // yuck
-                '\nfunction notacomponent() {\n  \n  return 1 + 1;\n}\n',
-            },
-          ],
-        },
-      ],
-    },
-  ],
-});

filename: compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts
patch: @@ -161,69 +161,21 @@ function makeRule(rule: LintRule): Rule.RuleModule {
   };
 }
 
-export const NoUnusedDirectivesRule: Rule.RuleModule = {
-  meta: {
-    type: 'suggestion',
-    docs: {
-      recommended: true,
-    },
-    fixable: 'code',
-    hasSuggestions: true,
-    // validation is done at runtime with zod
-    schema: [{type: 'object', additionalProperties: true}],
-  },
-  create(context: Rule.RuleContext): Rule.RuleListener {
-    const results = getReactCompilerResult(context);
-
-    for (const directive of results.unusedOptOutDirectives) {
-      context.report({
-        message: `Unused '${directive.directive}' directive`,
-        loc: directive.loc,
-        suggest: [
-          {
-            desc: 'Remove the directive',
-            fix(fixer): Rule.Fix {
-              return fixer.removeRange(directive.range);
-            },
-          },
-        ],
-      });
-    }
-    return {};
-  },
-};
-
 type RulesConfig = {
   [name: string]: {rule: Rule.RuleModule; severity: ErrorSeverity};
 };
 
-export const allRules: RulesConfig = LintRules.reduce(
-  (acc, rule) => {
-    acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
-    return acc;
-  },
-  {
-    'no-unused-directives': {
-      rule: NoUnusedDirectivesRule,
-      severity: ErrorSeverity.Error,
-    },
-  } as RulesConfig,
-);
+export const allRules: RulesConfig = LintRules.reduce((acc, rule) => {
+  acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
+  return acc;
+}, {} as RulesConfig);
 
 export const recommendedRules: RulesConfig = LintRules.filter(
   rule => rule.recommended,
-).reduce(
-  (acc, rule) => {
-    acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
-    return acc;
-  },
-  {
-    'no-unused-directives': {
-      rule: NoUnusedDirectivesRule,
-      severity: ErrorSeverity.Error,
-    },
-  } as RulesConfig,
-);
+).reduce((acc, rule) => {
+  acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
+  return acc;
+}, {} as RulesConfig);
 
 export function mapErrorSeverityToESlint(
   severity: ErrorSeverity,

filename: compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts
patch: @@ -5,20 +5,18 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-import {transformFromAstSync, traverse} from '@babel/core';
+import {transformFromAstSync} from '@babel/core';
 import {parse as babelParse} from '@babel/parser';
-import {Directive, File} from '@babel/types';
+import {File} from '@babel/types';
 // @ts-expect-error: no types available
 import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods';
 import BabelPluginReactCompiler, {
   parsePluginOptions,
   validateEnvironmentConfig,
-  OPT_OUT_DIRECTIVES,
   type PluginOptions,
 } from 'babel-plugin-react-compiler/src';
 import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint';
 import type {SourceCode} from 'eslint';
-import {SourceLocation} from 'estree';
 // @ts-expect-error: no types available
 import * as HermesParser from 'hermes-parser';
 import {isDeepStrictEqual} from 'util';
@@ -45,17 +43,11 @@ const COMPILER_OPTIONS: PluginOptions = {
   }),
 };
 
-export type UnusedOptOutDirective = {
-  loc: SourceLocation;
-  range: [number, number];
-  directive: string;
-};
 export type RunCacheEntry = {
   sourceCode: string;
   filename: string;
   userOpts: PluginOptions;
   flowSuppressions: Array<{line: number; code: string}>;
-  unusedOptOutDirectives: Array<UnusedOptOutDirective>;
   events: Array<LoggerEvent>;
 };
 
@@ -87,25 +79,6 @@ function getFlowSuppressions(
   return results;
 }
 
-function filterUnusedOptOutDirectives(
-  directives: ReadonlyArray<Directive>,
-): Array<UnusedOptOutDirective> {
-  const results: Array<UnusedOptOutDirective> = [];
-  for (const directive of directives) {
-    if (
-      OPT_OUT_DIRECTIVES.has(directive.value.value) &&
-      directive.loc != null
-    ) {
-      results.push({
-        loc: directive.loc,
-        directive: directive.value.value,
-        range: [directive.start!, directive.end!],
-      });
-    }
-  }
-  return results;
-}
-
 function runReactCompilerImpl({
   sourceCode,
   filename,
@@ -125,7 +98,6 @@ function runReactCompilerImpl({
     filename,
     userOpts,
     flowSuppressions: [],
-    unusedOptOutDirectives: [],
     events: [],
   };
   const userLogger: Logger | null = options.logger;
@@ -181,29 +153,6 @@ function runReactCompilerImpl({
         configFile: false,
         babelrc: false,
       });
-
-      if (results.events.filter(e => e.kind === 'CompileError').length === 0) {
-        traverse(babelAST, {
-          FunctionDeclaration(path) {
-            path.node;
-            results.unusedOptOutDirectives.push(
-              ...filterUnusedOptOutDirectives(path.node.body.directives),
-            );
-          },
-          ArrowFunctionExpression(path) {
-            if (path.node.body.type === 'BlockStatement') {
-              results.unusedOptOutDirectives.push(
-                ...filterUnusedOptOutDirectives(path.node.body.directives),
-              );
-            }
-          },
-          FunctionExpression(path) {
-            results.unusedOptOutDirectives.push(
-              ...filterUnusedOptOutDirectives(path.node.body.directives),
-            );
-          },
-        });
-      }
     } catch (err) {
       /* errors handled by injected logger */
     }

filename: packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts
patch: @@ -160,38 +160,6 @@ function makeRule(rule: LintRule): Rule.RuleModule {
   };
 }
 
-export const NoUnusedDirectivesRule: Rule.RuleModule = {
-  meta: {
-    type: 'suggestion',
-    docs: {
-      recommended: true,
-    },
-    fixable: 'code',
-    hasSuggestions: true,
-    // validation is done at runtime with zod
-    schema: [{type: 'object', additionalProperties: true}],
-  },
-  create(context: Rule.RuleContext): Rule.RuleListener {
-    const results = getReactCompilerResult(context);
-
-    for (const directive of results.unusedOptOutDirectives) {
-      context.report({
-        message: `Unused '${directive.directive}' directive`,
-        loc: directive.loc,
-        suggest: [
-          {
-            desc: 'Remove the directive',
-            fix(fixer): Rule.Fix {
-              return fixer.removeRange(directive.range);
-            },
-          },
-        ],
-      });
-    }
-    return {};
-  },
-};
-
 type RulesConfig = {
   [name: string]: {rule: Rule.RuleModule; severity: ErrorSeverity};
 };
@@ -201,12 +169,7 @@ export const allRules: RulesConfig = LintRules.reduce(
     acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
     return acc;
   },
-  {
-    'no-unused-directives': {
-      rule: NoUnusedDirectivesRule,
-      severity: ErrorSeverity.Error,
-    },
-  } as RulesConfig,
+  {} as RulesConfig,
 );
 
 export const recommendedRules: RulesConfig = LintRules.filter(
@@ -216,12 +179,7 @@ export const recommendedRules: RulesConfig = LintRules.filter(
     acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
     return acc;
   },
-  {
-    'no-unused-directives': {
-      rule: NoUnusedDirectivesRule,
-      severity: ErrorSeverity.Error,
-    },
-  } as RulesConfig,
+  {} as RulesConfig,
 );
 
 export function mapErrorSeverityToESlint(

filename: packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts
patch: @@ -6,21 +6,19 @@
  */
 /* eslint-disable no-for-of-loops/no-for-of-loops */
 
-import {transformFromAstSync, traverse} from '@babel/core';
+import {transformFromAstSync} from '@babel/core';
 import {parse as babelParse} from '@babel/parser';
-import {Directive, File} from '@babel/types';
+import {File} from '@babel/types';
 // @ts-expect-error: no types available
 import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods';
 import BabelPluginReactCompiler, {
   parsePluginOptions,
   validateEnvironmentConfig,
-  OPT_OUT_DIRECTIVES,
   type PluginOptions,
   Logger,
   LoggerEvent,
 } from 'babel-plugin-react-compiler';
 import type {SourceCode} from 'eslint';
-import {SourceLocation} from 'estree';
 import * as HermesParser from 'hermes-parser';
 import {isDeepStrictEqual} from 'util';
 import type {ParseResult} from '@babel/parser';
@@ -46,17 +44,11 @@ const COMPILER_OPTIONS: PluginOptions = {
   },
 };
 
-export type UnusedOptOutDirective = {
-  loc: SourceLocation;
-  range: [number, number];
-  directive: string;
-};
 export type RunCacheEntry = {
   sourceCode: string;
   filename: string;
   userOpts: PluginOptions;
   flowSuppressions: Array<{line: number; code: string}>;
-  unusedOptOutDirectives: Array<UnusedOptOutDirective>;
   events: Array<LoggerEvent>;
 };
 
@@ -88,24 +80,6 @@ function getFlowSuppressions(
   return results;
 }
 
-function filterUnusedOptOutDirectives(
-  directives: ReadonlyArray<Directive>,
-): Array<UnusedOptOutDirective> {
-  const results: Array<UnusedOptOutDirective> = [];
-  for (const directive of directives) {
-    if (
-      OPT_OUT_DIRECTIVES.has(directive.value.value) &&
-      directive.loc != null
-    ) {
-      results.push({
-        loc: directive.loc,
-        directive: directive.value.value,
-        range: [directive.start!, directive.end!],
-      });
-    }
-  }
-  return results;
-}
 
 function runReactCompilerImpl({
   sourceCode,
@@ -126,7 +100,6 @@ function runReactCompilerImpl({
     filename,
     userOpts,
     flowSuppressions: [],
-    unusedOptOutDirectives: [],
     events: [],
   };
   const userLogger: Logger | null = options.logger;
@@ -182,28 +155,6 @@ function runReactCompilerImpl({
         configFile: false,
         babelrc: false,
       });
-
-      if (results.events.filter(e => e.kind === 'CompileError').length === 0) {
-        traverse(babelAST, {
-          FunctionDeclaration(path) {
-            results.unusedOptOutDirectives.push(
-              ...filterUnusedOptOutDirectives(path.node.body.directives),
-            );
-          },
-          ArrowFunctionExpression(path) {
-            if (path.node.body.type === 'BlockStatement') {
-              results.unusedOptOutDirectives.push(
-                ...filterUnusedOptOutDirectives(path.node.body.directives),
-              );
-            }
-          },
-          FunctionExpression(path) {
-            results.unusedOptOutDirectives.push(
-              ...filterUnusedOptOutDirectives(path.node.body.directives),
-            );
-          },
-        });
-      }
     } catch (err) {
       /* errors handled by injected logger */
     }