@samitouri / QOS-React-1 / commits / 19f65ff179

[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 }