@@ -29,17 +29,6 @@ var PluginProposalPrivateMethods = require('@babel/plugin-proposal-private-metho
2929var HermesParser = require('hermes-parser');
3030var util = require('util');
3131
32- const SETTINGS_KEY = 'react-hooks';
33- const SETTINGS_ADDITIONAL_EFFECT_HOOKS_KEY = 'additionalEffectHooks';
34- function getAdditionalEffectHooksFromSettings(settings) {
35- var _a;
36- const additionalHooks = (_a = settings[SETTINGS_KEY]) === null || _a === void 0 ? void 0 : _a[SETTINGS_ADDITIONAL_EFFECT_HOOKS_KEY];
37- if (additionalHooks != null && typeof additionalHooks === 'string') {
38- return new RegExp(additionalHooks);
39- }
40- return undefined;
41- }
42-
4332const rule$1 = {
4433 meta: {
4534 type: 'suggestion',
@@ -70,24 +59,23 @@ const rule$1 = {
7059 },
7160 requireExplicitEffectDeps: {
7261 type: 'boolean',
73- },
62+ }
7463 },
7564 },
7665 ],
7766 },
7867 create(context) {
7968 const rawOptions = context.options && context.options[0];
80- const settings = context.settings || {};
8169 const additionalHooks = rawOptions && rawOptions.additionalHooks
8270 ? new RegExp(rawOptions.additionalHooks)
83- : getAdditionalEffectHooksFromSettings(settings) ;
71+ : undefined ;
8472 const enableDangerousAutofixThisMayCauseInfiniteLoops = (rawOptions &&
8573 rawOptions.enableDangerousAutofixThisMayCauseInfiniteLoops) ||
8674 false;
8775 const experimental_autoDependenciesHooks = rawOptions && Array.isArray(rawOptions.experimental_autoDependenciesHooks)
8876 ? rawOptions.experimental_autoDependenciesHooks
8977 : [];
90- const requireExplicitEffectDeps = ( rawOptions && rawOptions.requireExplicitEffectDeps) || false;
78+ const requireExplicitEffectDeps = rawOptions && rawOptions.requireExplicitEffectDeps || false;
9179 const options = {
9280 additionalHooks,
9381 experimental_autoDependenciesHooks,
@@ -956,7 +944,7 @@ const rule$1 = {
956944 reportProblem({
957945 node: reactiveHook,
958946 message: `React Hook ${reactiveHookName} always requires dependencies. ` +
959- `Please add a dependency array or an explicit \`undefined\``,
947+ `Please add a dependency array or an explicit \`undefined\``
960948 });
961949 }
962950 const isAutoDepsHook = options.experimental_autoDependenciesHooks.includes(reactiveHookName);
@@ -1458,7 +1446,9 @@ function isAncestorNodeOf(a, b) {
14581446 a.range[1] >= b.range[1]);
14591447}
14601448function isUseEffectEventIdentifier$1(node) {
1461- return node.type === 'Identifier' && node.name === 'useEffectEvent';
1449+ {
1450+ return node.type === 'Identifier' && node.name === 'useEffectEvent';
1451+ }
14621452}
14631453function getUnknownDependenciesMessage(reactiveHookName) {
14641454 return (`React Hook ${reactiveHookName} received a function whose dependencies ` +
@@ -32123,7 +32113,7 @@ const EnvironmentConfigSchema = zod.z.object({
3212332113 moduleTypeProvider: zod.z.nullable(zod.z.function().args(zod.z.string())).default(null),
3212432114 customMacros: zod.z.nullable(zod.z.array(MacroSchema)).default(null),
3212532115 enableResetCacheOnSourceFileChanges: zod.z.nullable(zod.z.boolean()).default(null),
32126- enablePreserveExistingMemoizationGuarantees: zod.z.boolean().default(true ),
32116+ enablePreserveExistingMemoizationGuarantees: zod.z.boolean().default(false ),
3212732117 validatePreserveExistingMemoizationGuarantees: zod.z.boolean().default(true),
3212832118 enablePreserveExistingManualUseMemo: zod.z.boolean().default(false),
3212932119 enableForest: zod.z.boolean().default(false),
@@ -45363,29 +45353,6 @@ function collectNonNullsInBlocks(fn, context) {
4536345353 }
4536445354 }
4536545355 }
45366- else if (fn.env.config.enablePreserveExistingMemoizationGuarantees &&
45367- instr.value.kind === 'StartMemoize' &&
45368- instr.value.deps != null) {
45369- for (const dep of instr.value.deps) {
45370- if (dep.root.kind === 'NamedLocal') {
45371- if (!isImmutableAtInstr(dep.root.value.identifier, instr.id, context)) {
45372- continue;
45373- }
45374- for (let i = 0; i < dep.path.length; i++) {
45375- const pathEntry = dep.path[i];
45376- if (pathEntry.optional) {
45377- break;
45378- }
45379- const depNode = context.registry.getOrCreateProperty({
45380- identifier: dep.root.value.identifier,
45381- path: dep.path.slice(0, i),
45382- reactive: dep.root.value.reactive,
45383- });
45384- assumedNonNullObjects.add(depNode);
45385- }
45386- }
45387- }
45388- }
4538945356 }
4539045357 nodes.set(block.id, {
4539145358 block,
@@ -54198,6 +54165,20 @@ function getFlowSuppressions(sourceCode) {
5419854165 }
5419954166 return results;
5420054167}
54168+ function filterUnusedOptOutDirectives(directives) {
54169+ const results = [];
54170+ for (const directive of directives) {
54171+ if (OPT_OUT_DIRECTIVES.has(directive.value.value) &&
54172+ directive.loc != null) {
54173+ results.push({
54174+ loc: directive.loc,
54175+ directive: directive.value.value,
54176+ range: [directive.start, directive.end],
54177+ });
54178+ }
54179+ }
54180+ return results;
54181+ }
5420154182function runReactCompilerImpl({ sourceCode, filename, userOpts, }) {
5420254183 var _a, _b;
5420354184 const options = parsePluginOptions(Object.assign(Object.assign(Object.assign({}, COMPILER_OPTIONS), userOpts), { environment: Object.assign(Object.assign({}, COMPILER_OPTIONS.environment), userOpts.environment) }));
@@ -54206,6 +54187,7 @@ function runReactCompilerImpl({ sourceCode, filename, userOpts, }) {
5420654187 filename,
5420754188 userOpts,
5420854189 flowSuppressions: [],
54190+ unusedOptOutDirectives: [],
5420954191 events: [],
5421054192 };
5421154193 const userLogger = options.logger;
@@ -54260,6 +54242,21 @@ function runReactCompilerImpl({ sourceCode, filename, userOpts, }) {
5426054242 configFile: false,
5426154243 babelrc: false,
5426254244 });
54245+ if (results.events.filter(e => e.kind === 'CompileError').length === 0) {
54246+ core$1.traverse(babelAST, {
54247+ FunctionDeclaration(path) {
54248+ results.unusedOptOutDirectives.push(...filterUnusedOptOutDirectives(path.node.body.directives));
54249+ },
54250+ ArrowFunctionExpression(path) {
54251+ if (path.node.body.type === 'BlockStatement') {
54252+ results.unusedOptOutDirectives.push(...filterUnusedOptOutDirectives(path.node.body.directives));
54253+ }
54254+ },
54255+ FunctionExpression(path) {
54256+ results.unusedOptOutDirectives.push(...filterUnusedOptOutDirectives(path.node.body.directives));
54257+ },
54258+ });
54259+ }
5426354260 }
5426454261 catch (err) {
5426554262 }
@@ -54429,14 +54426,53 @@ function makeRule(rule) {
5442954426 create,
5443054427 };
5443154428}
54429+ const NoUnusedDirectivesRule = {
54430+ meta: {
54431+ type: 'suggestion',
54432+ docs: {
54433+ recommended: true,
54434+ },
54435+ fixable: 'code',
54436+ hasSuggestions: true,
54437+ schema: [{ type: 'object', additionalProperties: true }],
54438+ },
54439+ create(context) {
54440+ const results = getReactCompilerResult(context);
54441+ for (const directive of results.unusedOptOutDirectives) {
54442+ context.report({
54443+ message: `Unused '${directive.directive}' directive`,
54444+ loc: directive.loc,
54445+ suggest: [
54446+ {
54447+ desc: 'Remove the directive',
54448+ fix(fixer) {
54449+ return fixer.removeRange(directive.range);
54450+ },
54451+ },
54452+ ],
54453+ });
54454+ }
54455+ return {};
54456+ },
54457+ };
5443254458const allRules = LintRules.reduce((acc, rule) => {
5443354459 acc[rule.name] = { rule: makeRule(rule), severity: rule.severity };
5443454460 return acc;
54435- }, {});
54461+ }, {
54462+ 'no-unused-directives': {
54463+ rule: NoUnusedDirectivesRule,
54464+ severity: ErrorSeverity.Error,
54465+ },
54466+ });
5443654467const recommendedRules = LintRules.filter(rule => rule.recommended).reduce((acc, rule) => {
5443754468 acc[rule.name] = { rule: makeRule(rule), severity: rule.severity };
5443854469 return acc;
54439- }, {});
54470+ }, {
54471+ 'no-unused-directives': {
54472+ rule: NoUnusedDirectivesRule,
54473+ severity: ErrorSeverity.Error,
54474+ },
54475+ });
5444054476function mapErrorSeverityToESlint(severity) {
5444154477 switch (severity) {
5444254478 case ErrorSeverity.Error: {
@@ -57339,26 +57375,13 @@ function getNodeWithoutReactNamespace(node) {
5733957375 }
5734057376 return node;
5734157377}
57342- function isEffectIdentifier(node, additionalHooks) {
57343- const isBuiltInEffect = node.type === 'Identifier' &&
57344- (node.name === 'useEffect' ||
57345- node.name === 'useLayoutEffect' ||
57346- node.name === 'useInsertionEffect');
57347- if (isBuiltInEffect) {
57348- return true;
57349- }
57350- if (additionalHooks && node.type === 'Identifier') {
57351- return additionalHooks.test(node.name);
57352- }
57353- return false;
57378+ function isEffectIdentifier(node) {
57379+ return node.type === 'Identifier' && (node.name === 'useEffect' || node.name === 'useLayoutEffect' || node.name === 'useInsertionEffect');
5735457380}
5735557381function isUseEffectEventIdentifier(node) {
57356- return node.type === 'Identifier' && node.name === 'useEffectEvent';
57357- }
57358- function useEffectEventError(fn, called) {
57359- return (`\`${fn}\` is a function created with React Hook "useEffectEvent", and can only be called from ` +
57360- 'Effects and Effect Events in the same component.' +
57361- (called ? '' : ' It cannot be assigned to a variable or passed down.'));
57382+ {
57383+ return node.type === 'Identifier' && node.name === 'useEffectEvent';
57384+ }
5736257385}
5736357386function isUseIdentifier(node) {
5736457387 return isReactFunction(node, 'use');
@@ -57371,21 +57394,8 @@ const rule = {
5737157394 recommended: true,
5737257395 url: 'https://react.dev/reference/rules/rules-of-hooks',
5737357396 },
57374- schema: [
57375- {
57376- type: 'object',
57377- additionalProperties: false,
57378- properties: {
57379- additionalHooks: {
57380- type: 'string',
57381- },
57382- },
57383- },
57384- ],
5738557397 },
5738657398 create(context) {
57387- const settings = context.settings || {};
57388- const additionalEffectHooks = getAdditionalEffectHooksFromSettings(settings);
5738957399 let lastEffect = null;
5739057400 const codePathReactHooksMapStack = [];
5739157401 const codePathSegmentStack = [];
@@ -57666,15 +57676,19 @@ const rule = {
5766657676 reactHooks.push(node.callee);
5766757677 }
5766857678 const nodeWithoutNamespace = getNodeWithoutReactNamespace(node.callee);
57669- if ((isEffectIdentifier(nodeWithoutNamespace, additionalEffectHooks ) ||
57679+ if ((isEffectIdentifier(nodeWithoutNamespace) ||
5767057680 isUseEffectEventIdentifier(nodeWithoutNamespace)) &&
5767157681 node.arguments.length > 0) {
5767257682 lastEffect = node;
5767357683 }
5767457684 },
5767557685 Identifier(node) {
5767657686 if (lastEffect == null && useEffectEventFunctions.has(node)) {
57677- const message = useEffectEventError(getSourceCode().getText(node), node.parent.type === 'CallExpression');
57687+ const message = `\`${getSourceCode().getText(node)}\` is a function created with React Hook "useEffectEvent", and can only be called from ` +
57688+ 'the same component.' +
57689+ (node.parent.type === 'CallExpression'
57690+ ? ''
57691+ : ' They cannot be assigned to variables or passed down.');
5767857692 context.report({
5767957693 node,
5768057694 message,
@@ -57741,57 +57755,44 @@ function last(array) {
5774157755}
5774257756
5774357757const rules = Object.assign({ 'exhaustive-deps': rule$1, 'rules-of-hooks': rule }, Object.fromEntries(Object.entries(allRules).map(([name, config]) => [name, config.rule])));
57744- const basicRuleConfigs = {
57745- 'react-hooks/rules-of-hooks': 'error',
57746- 'react-hooks/exhaustive-deps': 'warn',
57747- };
57748- const compilerRuleConfigs = Object.fromEntries(Object.entries(recommendedRules).map(([name, ruleConfig]) => {
57758+ const ruleConfigs = Object.assign({ 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn' }, Object.fromEntries(Object.entries(recommendedRules).map(([name, ruleConfig]) => {
5774957759 return [
57750- ` react-hooks/${ name}` ,
57760+ ' react-hooks/' + name,
5775157761 mapErrorSeverityToESlint(ruleConfig.severity),
5775257762 ];
57753- }));
57754- const allRuleConfigs = Object.assign(Object.assign({}, basicRuleConfigs), compilerRuleConfigs);
57763+ })));
5775557764const plugin = {
5775657765 meta: {
5775757766 name: 'eslint-plugin-react-hooks',
5775857767 },
57759- rules,
5776057768 configs: {},
57769+ rules,
5776157770};
5776257771Object.assign(plugin.configs, {
5776357772 'recommended-legacy': {
5776457773 plugins: ['react-hooks'],
57765- rules: basicRuleConfigs,
57766- },
57767- 'recommended-latest-legacy': {
57768- plugins: ['react-hooks'],
57769- rules: allRuleConfigs,
57774+ rules: ruleConfigs,
5777057775 },
5777157776 'flat/recommended': [
5777257777 {
5777357778 plugins: {
5777457779 'react-hooks': plugin,
5777557780 },
57776- rules: basicRuleConfigs ,
57781+ rules: ruleConfigs ,
5777757782 },
5777857783 ],
5777957784 'recommended-latest': [
5778057785 {
5778157786 plugins: {
5778257787 'react-hooks': plugin,
5778357788 },
57784- rules: allRuleConfigs,
57785- },
57786- ],
57787- recommended: [
57788- {
57789- plugins: {
57790- 'react-hooks': plugin,
57791- },
57792- rules: basicRuleConfigs,
57789+ rules: ruleConfigs,
5779357790 },
5779457791 ],
57792+ recommended: {
57793+ plugins: ['react-hooks'],
57794+ rules: ruleConfigs,
57795+ },
5779557796});
5779657797
5779757798module.exports = plugin;
0 commit comments