Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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>;
};

Expand Down Expand Up @@ -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,
Expand All @@ -125,7 +98,6 @@ function runReactCompilerImpl({
filename,
userOpts,
flowSuppressions: [],
unusedOptOutDirectives: [],
events: [],
};
const userLogger: Logger | null = options.logger;
Expand Down Expand Up @@ -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 */
}
Expand Down
5 changes: 1 addition & 4 deletions fixtures/eslint-v6/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
{
"root": true,
"extends": ["plugin:react-hooks/recommended-legacy"],
"extends": ["plugin:react-hooks/recommended-latest-legacy"],
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"react-hooks/exhaustive-deps": "error"
}
}
24 changes: 24 additions & 0 deletions fixtures/eslint-v6/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,27 @@ function useHookInLoops() {
useHook4();
}
}

/**
* Compiler Rules
*/
// Invalid: component factory
function InvalidComponentFactory() {
const DynamicComponent = () => <div>Hello</div>;
// eslint-disable-next-line react-hooks/static-components
return <DynamicComponent />;
}

// Invalid: mutating globals
function InvalidGlobals() {
// eslint-disable-next-line react-hooks/immutability
window.myGlobal = 42;
return <div>Done</div>;
}

// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization
function InvalidUseMemo({items}) {
// eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps
const sorted = useMemo(() => [...items].sort(), []);
return <div>{sorted.length}</div>;
}
5 changes: 1 addition & 4 deletions fixtures/eslint-v7/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
{
"root": true,
"extends": ["plugin:react-hooks/recommended-legacy"],
"extends": ["plugin:react-hooks/recommended-latest-legacy"],
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"react-hooks/exhaustive-deps": "error"
}
}
24 changes: 24 additions & 0 deletions fixtures/eslint-v7/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,27 @@ function useHookInLoops() {
useHook4();
}
}

/**
* Compiler Rules
*/
// Invalid: component factory
function InvalidComponentFactory() {
const DynamicComponent = () => <div>Hello</div>;
// eslint-disable-next-line react-hooks/static-components
return <DynamicComponent />;
}

// Invalid: mutating globals
function InvalidGlobals() {
// eslint-disable-next-line react-hooks/immutability
window.myGlobal = 42;
return <div>Done</div>;
}

// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization
function InvalidUseMemo({items}) {
// eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps
const sorted = useMemo(() => [...items].sort(), []);
return <div>{sorted.length}</div>;
}
5 changes: 1 addition & 4 deletions fixtures/eslint-v8/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
{
"root": true,
"extends": ["plugin:react-hooks/recommended-legacy"],
"extends": ["plugin:react-hooks/recommended-latest-legacy"],
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"react-hooks/exhaustive-deps": "error"
}
}
24 changes: 24 additions & 0 deletions fixtures/eslint-v8/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,27 @@ function useHookInLoops() {
useHook4();
}
}

/**
* Compiler Rules
*/
// Invalid: component factory
function InvalidComponentFactory() {
const DynamicComponent = () => <div>Hello</div>;
// eslint-disable-next-line react-hooks/static-components
return <DynamicComponent />;
}

// Invalid: mutating globals
function InvalidGlobals() {
// eslint-disable-next-line react-hooks/immutability
window.myGlobal = 42;
return <div>Done</div>;
}

// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization
function InvalidUseMemo({items}) {
// eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps
const sorted = useMemo(() => [...items].sort(), []);
return <div>{sorted.length}</div>;
}
2 changes: 0 additions & 2 deletions fixtures/eslint-v9/eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import {defineConfig} from 'eslint/config';
import reactHooks from 'eslint-plugin-react-hooks';

console.log(reactHooks.configs['recommended-latest']);

export default defineConfig([
{
languageOptions: {
Expand Down
Loading
Loading