[eprh] Remove NoUnusedOptOutDirectives (#34703)

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
This commit is contained in:
lauren 2025-10-02 19:19:01 -04:00 committed by GitHub
parent 26b177bc5e
commit 19f65ff179
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 14 additions and 262 deletions

View File

@ -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',
},
],
},
],
},
],
});

View File

@ -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,

View File

@ -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 */
}

View File

@ -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(

View File

@ -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 */
}