[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
lauren committed
Oct 2, 2025 at 19:19 UTC
19f65ff179d377ff0c9284704dff2fce370745be
5 files changed
+14
-262
compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts
deleted
-58
@@ -1,58 +0,0 @@
1
-/**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
-import {NoUnusedDirectivesRule} from '../src/rules/ReactCompilerRule';
9
-import {normalizeIndent, testRule} from './shared-utils';
10
-
11
-testRule('no unused directives rule', NoUnusedDirectivesRule, {
12
- valid: [],
13
- invalid: [
14
- {
15
- name: "Unused 'use no forget' directive is reported when no errors are present on components",
16
- code: normalizeIndent`
17
- function Component() {
18
- 'use no forget';
19
- return <div>Hello world</div>
20
- }
21
- `,
22
- errors: [
23
- {
24
- message: "Unused 'use no forget' directive",
25
- suggestions: [
26
- {
27
- output:
28
- // yuck
29
- '\nfunction Component() {\n \n return <div>Hello world</div>\n}\n',
30
- },
31
- ],
32
- },
33
- ],
34
- },
35
-
36
- {
37
- name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks",
38
- code: normalizeIndent`
39
- function notacomponent() {
40
- 'use no forget';
41
- return 1 + 1;
42
- }
43
- `,
44
- errors: [
45
- {
46
- message: "Unused 'use no forget' directive",
47
- suggestions: [
48
- {
49
- output:
50
- // yuck
51
- '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n',
52
- },
53
- ],
54
- },
55
- ],
56
- },
57
- ],
58
-});
compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts
+8
-56
@@ -161,69 +161,21 @@ function makeRule(rule: LintRule): Rule.RuleModule {
161
};
162
}
163
164
-export const NoUnusedDirectivesRule: Rule.RuleModule = {
165
- meta: {
166
- type: 'suggestion',
167
- docs: {
168
- recommended: true,
169
- },
170
- fixable: 'code',
171
- hasSuggestions: true,
172
- // validation is done at runtime with zod
173
- schema: [{type: 'object', additionalProperties: true}],
174
- },
175
- create(context: Rule.RuleContext): Rule.RuleListener {
176
- const results = getReactCompilerResult(context);
177
-
178
- for (const directive of results.unusedOptOutDirectives) {
179
- context.report({
180
- message: `Unused '${directive.directive}' directive`,
181
- loc: directive.loc,
182
- suggest: [
183
- {
184
- desc: 'Remove the directive',
185
- fix(fixer): Rule.Fix {
186
- return fixer.removeRange(directive.range);
187
- },
188
- },
189
- ],
190
- });
191
- }
192
- return {};
193
- },
194
-};
195
-
164
type RulesConfig = {
165
[name: string]: {rule: Rule.RuleModule; severity: ErrorSeverity};
166
};
167
200
-export const allRules: RulesConfig = LintRules.reduce(
201
- (acc, rule) => {
202
- acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
203
- return acc;
204
- },
205
- {
206
- 'no-unused-directives': {
207
- rule: NoUnusedDirectivesRule,
208
- severity: ErrorSeverity.Error,
209
- },
210
- } as RulesConfig,
211
-);
168
+export const allRules: RulesConfig = LintRules.reduce((acc, rule) => {
169
+ acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
170
+ return acc;
171
+}, {} as RulesConfig);
172
173
export const recommendedRules: RulesConfig = LintRules.filter(
174
rule => rule.recommended,
215
-).reduce(
216
- (acc, rule) => {
217
- acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
218
- return acc;
219
- },
220
- {
221
- 'no-unused-directives': {
222
- rule: NoUnusedDirectivesRule,
223
- severity: ErrorSeverity.Error,
224
- },
225
- } as RulesConfig,
226
-);
175
+).reduce((acc, rule) => {
176
+ acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
177
+ return acc;
178
+}, {} as RulesConfig);
179
180
export function mapErrorSeverityToESlint(
181
severity: ErrorSeverity,
compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts
+2
-53
@@ -5,20 +5,18 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import {transformFromAstSync, traverse} from '@babel/core';
8
+import {transformFromAstSync} from '@babel/core';
9
import {parse as babelParse} from '@babel/parser';
10
-import {Directive, File} from '@babel/types';
10
+import {File} from '@babel/types';
11
// @ts-expect-error: no types available
12
import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods';
13
import BabelPluginReactCompiler, {
14
parsePluginOptions,
15
validateEnvironmentConfig,
16
- OPT_OUT_DIRECTIVES,
16
type PluginOptions,
17
} from 'babel-plugin-react-compiler/src';
18
import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint';
19
import type {SourceCode} from 'eslint';
21
-import {SourceLocation} from 'estree';
20
// @ts-expect-error: no types available
21
import * as HermesParser from 'hermes-parser';
22
import {isDeepStrictEqual} from 'util';
@@ -45,17 +43,11 @@ const COMPILER_OPTIONS: PluginOptions = {
43
}),
44
};
45
48
-export type UnusedOptOutDirective = {
49
- loc: SourceLocation;
50
- range: [number, number];
51
- directive: string;
52
-};
46
export type RunCacheEntry = {
47
sourceCode: string;
48
filename: string;
49
userOpts: PluginOptions;
50
flowSuppressions: Array<{line: number; code: string}>;
58
- unusedOptOutDirectives: Array<UnusedOptOutDirective>;
51
events: Array<LoggerEvent>;
52
};
53
@@ -87,25 +79,6 @@ function getFlowSuppressions(
79
return results;
80
}
81
90
-function filterUnusedOptOutDirectives(
91
- directives: ReadonlyArray<Directive>,
92
-): Array<UnusedOptOutDirective> {
93
- const results: Array<UnusedOptOutDirective> = [];
94
- for (const directive of directives) {
95
- if (
96
- OPT_OUT_DIRECTIVES.has(directive.value.value) &&
97
- directive.loc != null
98
- ) {
99
- results.push({
100
- loc: directive.loc,
101
- directive: directive.value.value,
102
- range: [directive.start!, directive.end!],
103
- });
104
- }
105
- }
106
- return results;
107
-}
108
-
82
function runReactCompilerImpl({
83
sourceCode,
84
filename,
@@ -125,7 +98,6 @@ function runReactCompilerImpl({
98
filename,
99
userOpts,
100
flowSuppressions: [],
128
- unusedOptOutDirectives: [],
101
events: [],
102
};
103
const userLogger: Logger | null = options.logger;
@@ -181,29 +153,6 @@ function runReactCompilerImpl({
153
configFile: false,
154
babelrc: false,
155
});
184
-
185
- if (results.events.filter(e => e.kind === 'CompileError').length === 0) {
186
- traverse(babelAST, {
187
- FunctionDeclaration(path) {
188
- path.node;
189
- results.unusedOptOutDirectives.push(
190
- ...filterUnusedOptOutDirectives(path.node.body.directives),
191
- );
192
- },
193
- ArrowFunctionExpression(path) {
194
- if (path.node.body.type === 'BlockStatement') {
195
- results.unusedOptOutDirectives.push(
196
- ...filterUnusedOptOutDirectives(path.node.body.directives),
197
- );
198
- }
199
- },
200
- FunctionExpression(path) {
201
- results.unusedOptOutDirectives.push(
202
- ...filterUnusedOptOutDirectives(path.node.body.directives),
203
- );
204
- },
205
- });
206
- }
156
} catch (err) {
157
/* errors handled by injected logger */
158
}
packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts
+2
-44
@@ -160,38 +160,6 @@ function makeRule(rule: LintRule): Rule.RuleModule {
160
};
161
}
162
163
-export const NoUnusedDirectivesRule: Rule.RuleModule = {
164
- meta: {
165
- type: 'suggestion',
166
- docs: {
167
- recommended: true,
168
- },
169
- fixable: 'code',
170
- hasSuggestions: true,
171
- // validation is done at runtime with zod
172
- schema: [{type: 'object', additionalProperties: true}],
173
- },
174
- create(context: Rule.RuleContext): Rule.RuleListener {
175
- const results = getReactCompilerResult(context);
176
-
177
- for (const directive of results.unusedOptOutDirectives) {
178
- context.report({
179
- message: `Unused '${directive.directive}' directive`,
180
- loc: directive.loc,
181
- suggest: [
182
- {
183
- desc: 'Remove the directive',
184
- fix(fixer): Rule.Fix {
185
- return fixer.removeRange(directive.range);
186
- },
187
- },
188
- ],
189
- });
190
- }
191
- return {};
192
- },
193
-};
194
-
163
type RulesConfig = {
164
[name: string]: {rule: Rule.RuleModule; severity: ErrorSeverity};
165
};
@@ -201,12 +169,7 @@ export const allRules: RulesConfig = LintRules.reduce(
169
acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
170
return acc;
171
},
204
- {
205
- 'no-unused-directives': {
206
- rule: NoUnusedDirectivesRule,
207
- severity: ErrorSeverity.Error,
208
- },
209
- } as RulesConfig,
172
+ {} as RulesConfig,
173
);
174
175
export const recommendedRules: RulesConfig = LintRules.filter(
@@ -216,12 +179,7 @@ export const recommendedRules: RulesConfig = LintRules.filter(
179
acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
180
return acc;
181
},
219
- {
220
- 'no-unused-directives': {
221
- rule: NoUnusedDirectivesRule,
222
- severity: ErrorSeverity.Error,
223
- },
224
- } as RulesConfig,
182
+ {} as RulesConfig,
183
);
184
185
export function mapErrorSeverityToESlint(
packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts
+2
-51
@@ -6,21 +6,19 @@
6
*/
7
/* eslint-disable no-for-of-loops/no-for-of-loops */
8
9
-import {transformFromAstSync, traverse} from '@babel/core';
9
+import {transformFromAstSync} from '@babel/core';
10
import {parse as babelParse} from '@babel/parser';
11
-import {Directive, File} from '@babel/types';
11
+import {File} from '@babel/types';
12
// @ts-expect-error: no types available
13
import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods';
14
import BabelPluginReactCompiler, {
15
parsePluginOptions,
16
validateEnvironmentConfig,
17
- OPT_OUT_DIRECTIVES,
17
type PluginOptions,
18
Logger,
19
LoggerEvent,
20
} from 'babel-plugin-react-compiler';
21
import type {SourceCode} from 'eslint';
23
-import {SourceLocation} from 'estree';
22
import * as HermesParser from 'hermes-parser';
23
import {isDeepStrictEqual} from 'util';
24
import type {ParseResult} from '@babel/parser';
@@ -46,17 +44,11 @@ const COMPILER_OPTIONS: PluginOptions = {
44
},
45
};
46
49
-export type UnusedOptOutDirective = {
50
- loc: SourceLocation;
51
- range: [number, number];
52
- directive: string;
53
-};
47
export type RunCacheEntry = {
48
sourceCode: string;
49
filename: string;
50
userOpts: PluginOptions;
51
flowSuppressions: Array<{line: number; code: string}>;
59
- unusedOptOutDirectives: Array<UnusedOptOutDirective>;
52
events: Array<LoggerEvent>;
53
};
54
@@ -88,24 +80,6 @@ function getFlowSuppressions(
80
return results;
81
}
82
91
-function filterUnusedOptOutDirectives(
92
- directives: ReadonlyArray<Directive>,
93
-): Array<UnusedOptOutDirective> {
94
- const results: Array<UnusedOptOutDirective> = [];
95
- for (const directive of directives) {
96
- if (
97
- OPT_OUT_DIRECTIVES.has(directive.value.value) &&
98
- directive.loc != null
99
- ) {
100
- results.push({
101
- loc: directive.loc,
102
- directive: directive.value.value,
103
- range: [directive.start!, directive.end!],
104
- });
105
- }
106
- }
107
- return results;
108
-}
83
84
function runReactCompilerImpl({
85
sourceCode,
@@ -126,7 +100,6 @@ function runReactCompilerImpl({
100
filename,
101
userOpts,
102
flowSuppressions: [],
129
- unusedOptOutDirectives: [],
103
events: [],
104
};
105
const userLogger: Logger | null = options.logger;
@@ -182,28 +155,6 @@ function runReactCompilerImpl({
155
configFile: false,
156
babelrc: false,
157
});
185
-
186
- if (results.events.filter(e => e.kind === 'CompileError').length === 0) {
187
- traverse(babelAST, {
188
- FunctionDeclaration(path) {
189
- results.unusedOptOutDirectives.push(
190
- ...filterUnusedOptOutDirectives(path.node.body.directives),
191
- );
192
- },
193
- ArrowFunctionExpression(path) {
194
- if (path.node.body.type === 'BlockStatement') {
195
- results.unusedOptOutDirectives.push(
196
- ...filterUnusedOptOutDirectives(path.node.body.directives),
197
- );
198
- }
199
- },
200
- FunctionExpression(path) {
201
- results.unusedOptOutDirectives.push(
202
- ...filterUnusedOptOutDirectives(path.node.body.directives),
203
- );
204
- },
205
- });
206
- }
158
} catch (err) {
159
/* errors handled by injected logger */
160
}