eslint-plugin-react-hooks-extra 1.30.0 → 1.30.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +191 -367
  2. package/dist/index.mjs +192 -368
  3. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ var AST = require('@eslint-react/ast');
4
4
  var core = require('@eslint-react/core');
5
5
  var eff = require('@eslint-react/eff');
6
6
  var shared = require('@eslint-react/shared');
7
- var VAR5 = require('@eslint-react/var');
7
+ var VAR4 = require('@eslint-react/var');
8
8
  var types = require('@typescript-eslint/types');
9
9
  var tsPattern = require('ts-pattern');
10
10
 
@@ -27,7 +27,7 @@ function _interopNamespace(e) {
27
27
  }
28
28
 
29
29
  var AST__namespace = /*#__PURE__*/_interopNamespace(AST);
30
- var VAR5__namespace = /*#__PURE__*/_interopNamespace(VAR5);
30
+ var VAR4__namespace = /*#__PURE__*/_interopNamespace(VAR4);
31
31
 
32
32
  var __defProp = Object.defineProperty;
33
33
  var __export = (target, all) => {
@@ -50,13 +50,13 @@ var rules = {
50
50
 
51
51
  // package.json
52
52
  var name2 = "eslint-plugin-react-hooks-extra";
53
- var version = "1.30.0";
53
+ var version = "1.30.1-beta.0";
54
54
  var createRule = shared.createRuleForPlugin("hooks-extra");
55
55
  function isFromHookCall(context, name3, settings, predicate = eff.constTrue) {
56
56
  const hookAlias = settings.additionalHooks[name3] ?? [];
57
57
  return (topLevelId) => {
58
- const variable = VAR5__namespace.findVariable(topLevelId, context.sourceCode.getScope(topLevelId));
59
- const variableNode = VAR5__namespace.getVariableNode(variable, 0);
58
+ const variable = VAR4__namespace.findVariable(topLevelId, context.sourceCode.getScope(topLevelId));
59
+ const variableNode = VAR4__namespace.getVariableNode(variable, 0);
60
60
  if (variableNode == null) return false;
61
61
  if (variableNode.type !== types.AST_NODE_TYPES.CallExpression) return false;
62
62
  if (!core.isReactHookCallWithNameAlias(context, name3, hookAlias)(variableNode)) return false;
@@ -96,7 +96,7 @@ function isSetFunctionCall(context, settings) {
96
96
  return false;
97
97
  }
98
98
  const indexScope = context.sourceCode.getScope(node);
99
- const indexValue = VAR5__namespace.toStaticValue({
99
+ const indexValue = VAR4__namespace.toStaticValue({
100
100
  kind: "lazy",
101
101
  node: index,
102
102
  initialScope: indexScope
@@ -116,7 +116,7 @@ function isSetFunctionCall(context, settings) {
116
116
  }
117
117
  const property = node.callee.property;
118
118
  const propertyScope = context.sourceCode.getScope(node);
119
- const propertyValue = VAR5__namespace.toStaticValue({
119
+ const propertyValue = VAR4__namespace.toStaticValue({
120
120
  kind: "lazy",
121
121
  node: property,
122
122
  initialScope: propertyScope
@@ -152,6 +152,166 @@ function isVariableDeclaratorFromHookCall(node) {
152
152
  }
153
153
  }
154
154
 
155
+ // src/hooks/use-no-direct-set-state-in-use-effect.ts
156
+ function useNoDirectSetStateInUseEffect(context, options) {
157
+ const { onViolation, useEffectKind } = options;
158
+ const settings = shared.getSettingsFromContext(context);
159
+ const additionalHooks = settings.additionalHooks;
160
+ const isUseEffectLikeCall = core.isReactHookCallWithNameAlias(context, useEffectKind, additionalHooks[useEffectKind]);
161
+ const isUseStateCall2 = core.isReactHookCallWithNameAlias(context, "useState", additionalHooks.useState);
162
+ const isUseMemoCall2 = core.isReactHookCallWithNameAlias(context, "useMemo", additionalHooks.useMemo);
163
+ const isUseCallbackCall2 = core.isReactHookCallWithNameAlias(context, "useCallback", additionalHooks.useCallback);
164
+ const isSetStateCall = isSetFunctionCall(context, settings);
165
+ const isIdFromUseStateCall = isFromUseStateCall(context, settings);
166
+ const functionEntries = [];
167
+ const setupFunctionRef = { current: null };
168
+ const setupFunctionIdentifiers = [];
169
+ const indFunctionCalls = [];
170
+ const indSetStateCalls = /* @__PURE__ */ new Map();
171
+ const indSetStateCallsInUseEffectArg0 = /* @__PURE__ */ new Map();
172
+ const indSetStateCallsInUseEffectSetup = /* @__PURE__ */ new Map();
173
+ const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new Map();
174
+ const onSetupFunctionEnter = (node) => {
175
+ setupFunctionRef.current = node;
176
+ };
177
+ const onSetupFunctionExit = (node) => {
178
+ if (setupFunctionRef.current === node) {
179
+ setupFunctionRef.current = null;
180
+ }
181
+ };
182
+ function isFunctionOfUseEffectSetup(node) {
183
+ return node.parent?.type === types.AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
184
+ }
185
+ function getCallKind(node) {
186
+ return tsPattern.match(node).when(isUseStateCall2, () => "useState").when(isUseEffectLikeCall, () => useEffectKind).when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
187
+ }
188
+ function getFunctionKind(node) {
189
+ return tsPattern.match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
190
+ }
191
+ return {
192
+ ":function"(node) {
193
+ const kind = getFunctionKind(node);
194
+ functionEntries.push({ kind, node });
195
+ if (kind === "setup") {
196
+ onSetupFunctionEnter(node);
197
+ }
198
+ },
199
+ ":function:exit"(node) {
200
+ const { kind } = functionEntries.at(-1) ?? {};
201
+ if (kind === "setup") {
202
+ onSetupFunctionExit(node);
203
+ }
204
+ functionEntries.pop();
205
+ },
206
+ CallExpression(node) {
207
+ const setupFunction = setupFunctionRef.current;
208
+ const pEntry = functionEntries.at(-1);
209
+ if (pEntry == null || pEntry.node.async) {
210
+ return;
211
+ }
212
+ tsPattern.match(getCallKind(node)).with("setState", () => {
213
+ switch (true) {
214
+ case (pEntry.node === setupFunction || pEntry.kind === "immediate"): {
215
+ onViolation(context, node, {
216
+ name: context.sourceCode.getText(node.callee)
217
+ });
218
+ return;
219
+ }
220
+ default: {
221
+ const vd = AST__namespace.findParentNode(node, isVariableDeclaratorFromHookCall);
222
+ if (vd == null) eff.getOrUpdate(indSetStateCalls, pEntry.node, () => []).push(node);
223
+ else eff.getOrUpdate(indSetStateCallsInUseMemoOrCallback, vd.init, () => []).push(node);
224
+ }
225
+ }
226
+ }).with(useEffectKind, () => {
227
+ if (AST__namespace.isFunction(node.arguments.at(0))) return;
228
+ setupFunctionIdentifiers.push(...AST__namespace.getNestedIdentifiers(node));
229
+ }).with("other", () => {
230
+ if (pEntry.node !== setupFunction) return;
231
+ indFunctionCalls.push(node);
232
+ }).otherwise(() => eff._);
233
+ },
234
+ Identifier(node) {
235
+ if (node.parent.type === types.AST_NODE_TYPES.CallExpression && node.parent.callee === node) {
236
+ return;
237
+ }
238
+ if (!isIdFromUseStateCall(node)) {
239
+ return;
240
+ }
241
+ switch (node.parent.type) {
242
+ case types.AST_NODE_TYPES.ArrowFunctionExpression: {
243
+ const parent = node.parent.parent;
244
+ if (parent.type !== types.AST_NODE_TYPES.CallExpression) {
245
+ break;
246
+ }
247
+ if (!isUseMemoCall2(parent)) {
248
+ break;
249
+ }
250
+ const vd = AST__namespace.findParentNode(parent, isVariableDeclaratorFromHookCall);
251
+ if (vd != null) {
252
+ eff.getOrUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
253
+ }
254
+ break;
255
+ }
256
+ case types.AST_NODE_TYPES.CallExpression: {
257
+ if (node !== node.parent.arguments.at(0)) {
258
+ break;
259
+ }
260
+ if (isUseCallbackCall2(node.parent)) {
261
+ const vd = AST__namespace.findParentNode(node.parent, isVariableDeclaratorFromHookCall);
262
+ if (vd != null) {
263
+ eff.getOrUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
264
+ }
265
+ break;
266
+ }
267
+ if (isUseEffectLikeCall(node.parent)) {
268
+ eff.getOrUpdate(indSetStateCallsInUseEffectSetup, node.parent, () => []).push(node);
269
+ }
270
+ }
271
+ }
272
+ },
273
+ "Program:exit"() {
274
+ const getSetStateCalls = (id, initialScope) => {
275
+ const node = VAR4__namespace.getVariableNode(VAR4__namespace.findVariable(id, initialScope), 0);
276
+ switch (node?.type) {
277
+ case types.AST_NODE_TYPES.ArrowFunctionExpression:
278
+ case types.AST_NODE_TYPES.FunctionDeclaration:
279
+ case types.AST_NODE_TYPES.FunctionExpression:
280
+ return indSetStateCalls.get(node) ?? [];
281
+ case types.AST_NODE_TYPES.CallExpression:
282
+ return indSetStateCallsInUseMemoOrCallback.get(node) ?? indSetStateCallsInUseEffectArg0.get(node) ?? [];
283
+ }
284
+ return [];
285
+ };
286
+ for (const [, calls] of indSetStateCallsInUseEffectSetup) {
287
+ for (const call of calls) {
288
+ onViolation(context, call, { name: call.name });
289
+ }
290
+ }
291
+ for (const { callee } of indFunctionCalls) {
292
+ if (!("name" in callee)) {
293
+ continue;
294
+ }
295
+ const { name: name3 } = callee;
296
+ const setStateCalls = getSetStateCalls(name3, context.sourceCode.getScope(callee));
297
+ for (const setStateCall of setStateCalls) {
298
+ onViolation(context, setStateCall, {
299
+ name: AST__namespace.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
300
+ });
301
+ }
302
+ }
303
+ for (const id of setupFunctionIdentifiers) {
304
+ const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
305
+ for (const setStateCall of setStateCalls) {
306
+ onViolation(context, setStateCall, {
307
+ name: AST__namespace.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
308
+ });
309
+ }
310
+ }
311
+ }
312
+ };
313
+ }
314
+
155
315
  // src/rules/no-direct-set-state-in-use-effect.ts
156
316
  var RULE_NAME = "no-direct-set-state-in-use-effect";
157
317
  var RULE_FEATURES = [
@@ -171,183 +331,18 @@ var no_direct_set_state_in_use_effect_default = createRule({
171
331
  },
172
332
  name: RULE_NAME,
173
333
  create(context) {
174
- if (!/use\w*Effect/u.test(context.sourceCode.text)) {
175
- return {};
176
- }
177
- const settings = shared.getSettingsFromContext(context);
178
- const additionalHooks = settings.additionalHooks;
179
- const isUseEffectLikeCall = core.isReactHookCallWithNameAlias(context, "useEffect", additionalHooks.useEffect ?? []);
180
- const isUseStateCall2 = core.isReactHookCallWithNameAlias(context, "useState", additionalHooks.useState ?? []);
181
- const isUseMemoCall2 = core.isReactHookCallWithNameAlias(context, "useMemo", additionalHooks.useMemo ?? []);
182
- const isUseCallbackCall2 = core.isReactHookCallWithNameAlias(context, "useCallback", additionalHooks.useCallback ?? []);
183
- const isSetStateCall = isSetFunctionCall(context, settings);
184
- const isIdFromUseStateCall = isFromUseStateCall(context, settings);
185
- const functionEntries = [];
186
- const setupFunctionRef = { current: null };
187
- const setupFunctionIdentifiers = [];
188
- const indFunctionCalls = [];
189
- const indSetStateCalls = /* @__PURE__ */ new Map();
190
- const indSetStateCallsInUseEffectArg0 = /* @__PURE__ */ new Map();
191
- const indSetStateCallsInUseEffectSetup = /* @__PURE__ */ new Map();
192
- const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new Map();
193
- const onSetupFunctionEnter = (node) => {
194
- setupFunctionRef.current = node;
195
- };
196
- const onSetupFunctionExit = (node) => {
197
- if (setupFunctionRef.current === node) {
198
- setupFunctionRef.current = null;
199
- }
200
- };
201
- function isFunctionOfUseEffectSetup(node) {
202
- return node.parent?.type === types.AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
203
- }
204
- function getCallKind(node) {
205
- return tsPattern.match(node).when(isUseStateCall2, () => "useState").when(isUseEffectLikeCall, () => "useEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
206
- }
207
- function getFunctionKind(node) {
208
- return tsPattern.match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
209
- }
210
- return {
211
- ":function"(node) {
212
- const kind = getFunctionKind(node);
213
- functionEntries.push({ kind, node });
214
- if (kind === "setup") {
215
- onSetupFunctionEnter(node);
216
- }
217
- },
218
- ":function:exit"(node) {
219
- const { kind } = functionEntries.at(-1) ?? {};
220
- if (kind === "setup") {
221
- onSetupFunctionExit(node);
222
- }
223
- functionEntries.pop();
334
+ if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
335
+ return useNoDirectSetStateInUseEffect(context, {
336
+ onViolation(ctx, node, data) {
337
+ ctx.report({ messageId: "noDirectSetStateInUseEffect", node, data });
224
338
  },
225
- CallExpression(node) {
226
- const setupFunction = setupFunctionRef.current;
227
- const pEntry = functionEntries.at(-1);
228
- if (pEntry == null || pEntry.node.async) {
229
- return;
230
- }
231
- tsPattern.match(getCallKind(node)).with("setState", () => {
232
- switch (true) {
233
- case (pEntry.node === setupFunction || pEntry.kind === "immediate"): {
234
- context.report({
235
- messageId: "noDirectSetStateInUseEffect",
236
- node,
237
- data: {
238
- name: context.sourceCode.getText(node.callee)
239
- }
240
- });
241
- return;
242
- }
243
- default: {
244
- const vd = AST__namespace.findParentNode(node, isVariableDeclaratorFromHookCall);
245
- if (vd == null) eff.getOrUpdate(indSetStateCalls, pEntry.node, () => []).push(node);
246
- else eff.getOrUpdate(indSetStateCallsInUseMemoOrCallback, vd.init, () => []).push(node);
247
- }
248
- }
249
- }).with("useEffect", () => {
250
- if (AST__namespace.isFunction(node.arguments.at(0))) return;
251
- setupFunctionIdentifiers.push(...AST__namespace.getNestedIdentifiers(node));
252
- }).with("other", () => {
253
- if (pEntry.node !== setupFunction) return;
254
- indFunctionCalls.push(node);
255
- }).otherwise(() => eff._);
256
- },
257
- Identifier(node) {
258
- if (node.parent.type === types.AST_NODE_TYPES.CallExpression && node.parent.callee === node) {
259
- return;
260
- }
261
- if (!isIdFromUseStateCall(node)) {
262
- return;
263
- }
264
- switch (node.parent.type) {
265
- case types.AST_NODE_TYPES.ArrowFunctionExpression: {
266
- const parent = node.parent.parent;
267
- if (parent.type !== types.AST_NODE_TYPES.CallExpression) {
268
- break;
269
- }
270
- if (!isUseMemoCall2(parent)) {
271
- break;
272
- }
273
- const vd = AST__namespace.findParentNode(parent, isVariableDeclaratorFromHookCall);
274
- if (vd != null) {
275
- eff.getOrUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
276
- }
277
- break;
278
- }
279
- case types.AST_NODE_TYPES.CallExpression: {
280
- if (node !== node.parent.arguments.at(0)) {
281
- break;
282
- }
283
- if (isUseCallbackCall2(node.parent)) {
284
- const vd = AST__namespace.findParentNode(node.parent, isVariableDeclaratorFromHookCall);
285
- if (vd != null) {
286
- eff.getOrUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
287
- }
288
- break;
289
- }
290
- if (isUseEffectLikeCall(node.parent)) {
291
- eff.getOrUpdate(indSetStateCallsInUseEffectSetup, node.parent, () => []).push(node);
292
- }
293
- }
294
- }
295
- },
296
- "Program:exit"() {
297
- const getSetStateCalls = (id, initialScope) => {
298
- const node = VAR5__namespace.getVariableNode(VAR5__namespace.findVariable(id, initialScope), 0);
299
- switch (node?.type) {
300
- case types.AST_NODE_TYPES.ArrowFunctionExpression:
301
- case types.AST_NODE_TYPES.FunctionDeclaration:
302
- case types.AST_NODE_TYPES.FunctionExpression:
303
- return indSetStateCalls.get(node) ?? [];
304
- case types.AST_NODE_TYPES.CallExpression:
305
- return indSetStateCallsInUseMemoOrCallback.get(node) ?? indSetStateCallsInUseEffectArg0.get(node) ?? [];
306
- }
307
- return [];
308
- };
309
- for (const [, calls] of indSetStateCallsInUseEffectSetup) {
310
- for (const call of calls) {
311
- context.report({
312
- messageId: "noDirectSetStateInUseEffect",
313
- node: call,
314
- data: { name: call.name }
315
- });
316
- }
317
- }
318
- for (const { callee } of indFunctionCalls) {
319
- if (!("name" in callee)) {
320
- continue;
321
- }
322
- const { name: name3 } = callee;
323
- const setStateCalls = getSetStateCalls(name3, context.sourceCode.getScope(callee));
324
- for (const setStateCall of setStateCalls) {
325
- context.report({
326
- messageId: "noDirectSetStateInUseEffect",
327
- node: setStateCall,
328
- data: {
329
- name: AST__namespace.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
330
- }
331
- });
332
- }
333
- }
334
- for (const id of setupFunctionIdentifiers) {
335
- const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
336
- for (const setStateCall of setStateCalls) {
337
- context.report({
338
- messageId: "noDirectSetStateInUseEffect",
339
- node: setStateCall,
340
- data: {
341
- name: AST__namespace.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
342
- }
343
- });
344
- }
345
- }
346
- }
347
- };
339
+ useEffectKind: "useEffect"
340
+ });
348
341
  },
349
342
  defaultOptions: []
350
343
  });
344
+
345
+ // src/rules/no-direct-set-state-in-use-layout-effect.ts
351
346
  var RULE_NAME2 = "no-direct-set-state-in-use-layout-effect";
352
347
  var RULE_FEATURES2 = [
353
348
  "CHK"
@@ -366,184 +361,13 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
366
361
  },
367
362
  name: RULE_NAME2,
368
363
  create(context) {
369
- if (!/use\w*Effect/u.test(context.sourceCode.text)) {
370
- return {};
371
- }
372
- const settings = shared.getSettingsFromContext(context);
373
- const additionalHooks = settings.additionalHooks;
374
- const isUseLayoutEffectLikeCall = core.isReactHookCallWithNameAlias(
375
- context,
376
- "useLayoutEffect",
377
- additionalHooks.useLayoutEffect ?? []
378
- );
379
- const isUseStateCall2 = core.isReactHookCallWithNameAlias(context, "useState", additionalHooks.useState ?? []);
380
- const isUseMemoCall2 = core.isReactHookCallWithNameAlias(context, "useMemo", additionalHooks.useMemo ?? []);
381
- const isUseCallbackCall2 = core.isReactHookCallWithNameAlias(context, "useCallback", additionalHooks.useCallback ?? []);
382
- const isSetStateCall = isSetFunctionCall(context, settings);
383
- const isIdFromUseStateCall = isFromUseStateCall(context, settings);
384
- const functionEntries = [];
385
- const setupFunctionRef = { current: null };
386
- const setupFunctionIdentifiers = [];
387
- const indFunctionCalls = [];
388
- const indSetStateCalls = /* @__PURE__ */ new Map();
389
- const indSetStateCallsInUseLayoutEffectArg0 = /* @__PURE__ */ new Map();
390
- const indSetStateCallsInUseLayoutEffectSetup = /* @__PURE__ */ new Map();
391
- const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new Map();
392
- const onSetupFunctionEnter = (node) => {
393
- setupFunctionRef.current = node;
394
- };
395
- const onSetupFunctionExit = (node) => {
396
- if (setupFunctionRef.current === node) {
397
- setupFunctionRef.current = null;
398
- }
399
- };
400
- function isFunctionOfUseEffectSetup(node) {
401
- return node.parent?.type === types.AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseLayoutEffectLikeCall(node.parent);
402
- }
403
- function getCallKind(node) {
404
- return tsPattern.match(node).when(isUseStateCall2, () => "useState").when(isUseLayoutEffectLikeCall, () => "useLayoutEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
405
- }
406
- function getFunctionKind(node) {
407
- return tsPattern.match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
408
- }
409
- return {
410
- ":function"(node) {
411
- const kind = getFunctionKind(node);
412
- functionEntries.push({ kind, node });
413
- if (kind === "setup") {
414
- onSetupFunctionEnter(node);
415
- }
416
- },
417
- ":function:exit"(node) {
418
- const { kind } = functionEntries.at(-1) ?? {};
419
- if (kind === "setup") {
420
- onSetupFunctionExit(node);
421
- }
422
- functionEntries.pop();
423
- },
424
- CallExpression(node) {
425
- const setupFunction = setupFunctionRef.current;
426
- const pEntry = functionEntries.at(-1);
427
- if (pEntry == null || pEntry.node.async) {
428
- return;
429
- }
430
- tsPattern.match(getCallKind(node)).with("setState", () => {
431
- switch (true) {
432
- case (pEntry.node === setupFunction || pEntry.kind === "immediate"): {
433
- context.report({
434
- messageId: "noDirectSetStateInUseLayoutEffect",
435
- node,
436
- data: {
437
- name: context.sourceCode.getText(node.callee)
438
- }
439
- });
440
- return;
441
- }
442
- default: {
443
- const vd = AST__namespace.findParentNode(node, isVariableDeclaratorFromHookCall);
444
- if (vd == null) eff.getOrUpdate(indSetStateCalls, pEntry.node, () => []).push(node);
445
- else eff.getOrUpdate(indSetStateCallsInUseMemoOrCallback, vd.init, () => []).push(node);
446
- }
447
- }
448
- }).with("useLayoutEffect", () => {
449
- if (AST__namespace.isFunction(node.arguments.at(0))) return;
450
- setupFunctionIdentifiers.push(...AST__namespace.getNestedIdentifiers(node));
451
- }).with("other", () => {
452
- if (pEntry.node !== setupFunction) return;
453
- indFunctionCalls.push(node);
454
- }).otherwise(() => eff._);
455
- },
456
- Identifier(node) {
457
- if (node.parent.type === types.AST_NODE_TYPES.CallExpression && node.parent.callee === node) {
458
- return;
459
- }
460
- if (!isIdFromUseStateCall(node)) {
461
- return;
462
- }
463
- switch (node.parent.type) {
464
- case types.AST_NODE_TYPES.ArrowFunctionExpression: {
465
- const parent = node.parent.parent;
466
- if (parent.type !== types.AST_NODE_TYPES.CallExpression) {
467
- break;
468
- }
469
- if (!isUseMemoCall2(parent)) {
470
- break;
471
- }
472
- const vd = AST__namespace.findParentNode(parent, isVariableDeclaratorFromHookCall);
473
- if (vd != null) {
474
- eff.getOrUpdate(indSetStateCallsInUseLayoutEffectArg0, vd.init, () => []).push(node);
475
- }
476
- break;
477
- }
478
- case types.AST_NODE_TYPES.CallExpression: {
479
- if (node !== node.parent.arguments.at(0)) {
480
- break;
481
- }
482
- if (isUseCallbackCall2(node.parent)) {
483
- const vd = AST__namespace.findParentNode(node.parent, isVariableDeclaratorFromHookCall);
484
- if (vd != null) {
485
- eff.getOrUpdate(indSetStateCallsInUseLayoutEffectArg0, vd.init, () => []).push(node);
486
- }
487
- break;
488
- }
489
- if (isUseLayoutEffectLikeCall(node.parent)) {
490
- eff.getOrUpdate(indSetStateCallsInUseLayoutEffectSetup, node.parent, () => []).push(node);
491
- }
492
- }
493
- }
364
+ if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
365
+ return useNoDirectSetStateInUseEffect(context, {
366
+ onViolation(ctx, node, data) {
367
+ ctx.report({ messageId: "noDirectSetStateInUseLayoutEffect", node, data });
494
368
  },
495
- "Program:exit"() {
496
- const getSetStateCalls = (id, initialScope) => {
497
- const node = VAR5__namespace.getVariableNode(VAR5__namespace.findVariable(id, initialScope), 0);
498
- switch (node?.type) {
499
- case types.AST_NODE_TYPES.ArrowFunctionExpression:
500
- case types.AST_NODE_TYPES.FunctionDeclaration:
501
- case types.AST_NODE_TYPES.FunctionExpression:
502
- return indSetStateCalls.get(node) ?? [];
503
- case types.AST_NODE_TYPES.CallExpression:
504
- return indSetStateCallsInUseMemoOrCallback.get(node) ?? indSetStateCallsInUseLayoutEffectArg0.get(node) ?? [];
505
- }
506
- return [];
507
- };
508
- for (const [, calls] of indSetStateCallsInUseLayoutEffectSetup) {
509
- for (const call of calls) {
510
- context.report({
511
- messageId: "noDirectSetStateInUseLayoutEffect",
512
- node: call,
513
- data: { name: call.name }
514
- });
515
- }
516
- }
517
- for (const { callee } of indFunctionCalls) {
518
- if (!("name" in callee)) {
519
- continue;
520
- }
521
- const { name: name3 } = callee;
522
- const setStateCalls = getSetStateCalls(name3, context.sourceCode.getScope(callee));
523
- for (const setStateCall of setStateCalls) {
524
- context.report({
525
- messageId: "noDirectSetStateInUseLayoutEffect",
526
- node: setStateCall,
527
- data: {
528
- name: AST__namespace.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
529
- }
530
- });
531
- }
532
- }
533
- for (const id of setupFunctionIdentifiers) {
534
- const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
535
- for (const setStateCall of setStateCalls) {
536
- context.report({
537
- messageId: "noDirectSetStateInUseLayoutEffect",
538
- node: setStateCall,
539
- data: {
540
- name: AST__namespace.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
541
- }
542
- });
543
- }
544
- }
545
- }
546
- };
369
+ useEffectKind: "useLayoutEffect"
370
+ });
547
371
  },
548
372
  defaultOptions: []
549
373
  });
@@ -588,8 +412,8 @@ var no_unnecessary_use_callback_default = createRule({
588
412
  return;
589
413
  }
590
414
  const hasEmptyDeps = tsPattern.match(arg1).with({ type: types.AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: types.AST_NODE_TYPES.Identifier }, (n) => {
591
- const variable = VAR5__namespace.findVariable(n.name, initialScope);
592
- const variableNode = VAR5__namespace.getVariableNode(variable, 0);
415
+ const variable = VAR4__namespace.findVariable(n.name, initialScope);
416
+ const variableNode = VAR4__namespace.getVariableNode(variable, 0);
593
417
  if (variableNode?.type !== types.AST_NODE_TYPES.ArrayExpression) {
594
418
  return false;
595
419
  }
@@ -604,8 +428,8 @@ var no_unnecessary_use_callback_default = createRule({
604
428
  }
605
429
  return n;
606
430
  }).with({ type: types.AST_NODE_TYPES.FunctionExpression }, eff.identity).with({ type: types.AST_NODE_TYPES.Identifier }, (n) => {
607
- const variable = VAR5__namespace.findVariable(n.name, initialScope);
608
- const variableNode = VAR5__namespace.getVariableNode(variable, 0);
431
+ const variable = VAR4__namespace.findVariable(n.name, initialScope);
432
+ const variableNode = VAR4__namespace.getVariableNode(variable, 0);
609
433
  if (variableNode?.type !== types.AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== types.AST_NODE_TYPES.FunctionExpression) {
610
434
  return eff._;
611
435
  }
@@ -613,7 +437,7 @@ var no_unnecessary_use_callback_default = createRule({
613
437
  }).otherwise(() => eff._);
614
438
  if (arg0Node == null) return;
615
439
  const arg0NodeScope = context.sourceCode.getScope(arg0Node);
616
- const arg0NodeReferences = VAR5__namespace.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
440
+ const arg0NodeReferences = VAR4__namespace.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
617
441
  const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
618
442
  if (!isReferencedToComponentScope) {
619
443
  context.report({
@@ -671,8 +495,8 @@ var no_unnecessary_use_memo_default = createRule({
671
495
  return;
672
496
  }
673
497
  const hasEmptyDeps = tsPattern.match(arg1).with({ type: types.AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: types.AST_NODE_TYPES.Identifier }, (n) => {
674
- const variable = VAR5__namespace.findVariable(n.name, initialScope);
675
- const variableNode = VAR5__namespace.getVariableNode(variable, 0);
498
+ const variable = VAR4__namespace.findVariable(n.name, initialScope);
499
+ const variableNode = VAR4__namespace.getVariableNode(variable, 0);
676
500
  if (variableNode?.type !== types.AST_NODE_TYPES.ArrayExpression) {
677
501
  return false;
678
502
  }
@@ -687,8 +511,8 @@ var no_unnecessary_use_memo_default = createRule({
687
511
  }
688
512
  return n;
689
513
  }).with({ type: types.AST_NODE_TYPES.FunctionExpression }, eff.identity).with({ type: types.AST_NODE_TYPES.Identifier }, (n) => {
690
- const variable = VAR5__namespace.findVariable(n.name, initialScope);
691
- const variableNode = VAR5__namespace.getVariableNode(variable, 0);
514
+ const variable = VAR4__namespace.findVariable(n.name, initialScope);
515
+ const variableNode = VAR4__namespace.getVariableNode(variable, 0);
692
516
  if (variableNode?.type !== types.AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== types.AST_NODE_TYPES.FunctionExpression) {
693
517
  return eff._;
694
518
  }
@@ -696,7 +520,7 @@ var no_unnecessary_use_memo_default = createRule({
696
520
  }).otherwise(() => eff._);
697
521
  if (arg0Node == null) return;
698
522
  const arg0NodeScope = context.sourceCode.getScope(arg0Node);
699
- const arg0NodeReferences = VAR5__namespace.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
523
+ const arg0NodeReferences = VAR4__namespace.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
700
524
  const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
701
525
  if (!isReferencedToComponentScope) {
702
526
  context.report({
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as AST from '@eslint-react/ast';
2
- import { isReactHookCallWithNameAlias, isReactHookCall, isUseCallbackCall, isReactHookCallWithNameLoose, isUseMemoCall, useHookCollector, isUseStateCall, isReactHookName } from '@eslint-react/core';
3
- import { getOrUpdate, _, identity, constTrue } from '@eslint-react/eff';
2
+ import { isReactHookCall, isUseCallbackCall, isReactHookCallWithNameLoose, isUseMemoCall, useHookCollector, isUseStateCall, isReactHookCallWithNameAlias, isReactHookName } from '@eslint-react/core';
3
+ import { identity, _, getOrUpdate, constTrue } from '@eslint-react/eff';
4
4
  import { createRuleForPlugin, getSettingsFromContext } from '@eslint-react/shared';
5
- import * as VAR5 from '@eslint-react/var';
5
+ import * as VAR4 from '@eslint-react/var';
6
6
  import { AST_NODE_TYPES } from '@typescript-eslint/types';
7
7
  import { match } from 'ts-pattern';
8
8
 
@@ -27,13 +27,13 @@ var rules = {
27
27
 
28
28
  // package.json
29
29
  var name2 = "eslint-plugin-react-hooks-extra";
30
- var version = "1.30.0";
30
+ var version = "1.30.1-beta.0";
31
31
  var createRule = createRuleForPlugin("hooks-extra");
32
32
  function isFromHookCall(context, name3, settings, predicate = constTrue) {
33
33
  const hookAlias = settings.additionalHooks[name3] ?? [];
34
34
  return (topLevelId) => {
35
- const variable = VAR5.findVariable(topLevelId, context.sourceCode.getScope(topLevelId));
36
- const variableNode = VAR5.getVariableNode(variable, 0);
35
+ const variable = VAR4.findVariable(topLevelId, context.sourceCode.getScope(topLevelId));
36
+ const variableNode = VAR4.getVariableNode(variable, 0);
37
37
  if (variableNode == null) return false;
38
38
  if (variableNode.type !== AST_NODE_TYPES.CallExpression) return false;
39
39
  if (!isReactHookCallWithNameAlias(context, name3, hookAlias)(variableNode)) return false;
@@ -73,7 +73,7 @@ function isSetFunctionCall(context, settings) {
73
73
  return false;
74
74
  }
75
75
  const indexScope = context.sourceCode.getScope(node);
76
- const indexValue = VAR5.toStaticValue({
76
+ const indexValue = VAR4.toStaticValue({
77
77
  kind: "lazy",
78
78
  node: index,
79
79
  initialScope: indexScope
@@ -93,7 +93,7 @@ function isSetFunctionCall(context, settings) {
93
93
  }
94
94
  const property = node.callee.property;
95
95
  const propertyScope = context.sourceCode.getScope(node);
96
- const propertyValue = VAR5.toStaticValue({
96
+ const propertyValue = VAR4.toStaticValue({
97
97
  kind: "lazy",
98
98
  node: property,
99
99
  initialScope: propertyScope
@@ -129,6 +129,166 @@ function isVariableDeclaratorFromHookCall(node) {
129
129
  }
130
130
  }
131
131
 
132
+ // src/hooks/use-no-direct-set-state-in-use-effect.ts
133
+ function useNoDirectSetStateInUseEffect(context, options) {
134
+ const { onViolation, useEffectKind } = options;
135
+ const settings = getSettingsFromContext(context);
136
+ const additionalHooks = settings.additionalHooks;
137
+ const isUseEffectLikeCall = isReactHookCallWithNameAlias(context, useEffectKind, additionalHooks[useEffectKind]);
138
+ const isUseStateCall2 = isReactHookCallWithNameAlias(context, "useState", additionalHooks.useState);
139
+ const isUseMemoCall2 = isReactHookCallWithNameAlias(context, "useMemo", additionalHooks.useMemo);
140
+ const isUseCallbackCall2 = isReactHookCallWithNameAlias(context, "useCallback", additionalHooks.useCallback);
141
+ const isSetStateCall = isSetFunctionCall(context, settings);
142
+ const isIdFromUseStateCall = isFromUseStateCall(context, settings);
143
+ const functionEntries = [];
144
+ const setupFunctionRef = { current: null };
145
+ const setupFunctionIdentifiers = [];
146
+ const indFunctionCalls = [];
147
+ const indSetStateCalls = /* @__PURE__ */ new Map();
148
+ const indSetStateCallsInUseEffectArg0 = /* @__PURE__ */ new Map();
149
+ const indSetStateCallsInUseEffectSetup = /* @__PURE__ */ new Map();
150
+ const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new Map();
151
+ const onSetupFunctionEnter = (node) => {
152
+ setupFunctionRef.current = node;
153
+ };
154
+ const onSetupFunctionExit = (node) => {
155
+ if (setupFunctionRef.current === node) {
156
+ setupFunctionRef.current = null;
157
+ }
158
+ };
159
+ function isFunctionOfUseEffectSetup(node) {
160
+ return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
161
+ }
162
+ function getCallKind(node) {
163
+ return match(node).when(isUseStateCall2, () => "useState").when(isUseEffectLikeCall, () => useEffectKind).when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
164
+ }
165
+ function getFunctionKind(node) {
166
+ return match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
167
+ }
168
+ return {
169
+ ":function"(node) {
170
+ const kind = getFunctionKind(node);
171
+ functionEntries.push({ kind, node });
172
+ if (kind === "setup") {
173
+ onSetupFunctionEnter(node);
174
+ }
175
+ },
176
+ ":function:exit"(node) {
177
+ const { kind } = functionEntries.at(-1) ?? {};
178
+ if (kind === "setup") {
179
+ onSetupFunctionExit(node);
180
+ }
181
+ functionEntries.pop();
182
+ },
183
+ CallExpression(node) {
184
+ const setupFunction = setupFunctionRef.current;
185
+ const pEntry = functionEntries.at(-1);
186
+ if (pEntry == null || pEntry.node.async) {
187
+ return;
188
+ }
189
+ match(getCallKind(node)).with("setState", () => {
190
+ switch (true) {
191
+ case (pEntry.node === setupFunction || pEntry.kind === "immediate"): {
192
+ onViolation(context, node, {
193
+ name: context.sourceCode.getText(node.callee)
194
+ });
195
+ return;
196
+ }
197
+ default: {
198
+ const vd = AST.findParentNode(node, isVariableDeclaratorFromHookCall);
199
+ if (vd == null) getOrUpdate(indSetStateCalls, pEntry.node, () => []).push(node);
200
+ else getOrUpdate(indSetStateCallsInUseMemoOrCallback, vd.init, () => []).push(node);
201
+ }
202
+ }
203
+ }).with(useEffectKind, () => {
204
+ if (AST.isFunction(node.arguments.at(0))) return;
205
+ setupFunctionIdentifiers.push(...AST.getNestedIdentifiers(node));
206
+ }).with("other", () => {
207
+ if (pEntry.node !== setupFunction) return;
208
+ indFunctionCalls.push(node);
209
+ }).otherwise(() => _);
210
+ },
211
+ Identifier(node) {
212
+ if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) {
213
+ return;
214
+ }
215
+ if (!isIdFromUseStateCall(node)) {
216
+ return;
217
+ }
218
+ switch (node.parent.type) {
219
+ case AST_NODE_TYPES.ArrowFunctionExpression: {
220
+ const parent = node.parent.parent;
221
+ if (parent.type !== AST_NODE_TYPES.CallExpression) {
222
+ break;
223
+ }
224
+ if (!isUseMemoCall2(parent)) {
225
+ break;
226
+ }
227
+ const vd = AST.findParentNode(parent, isVariableDeclaratorFromHookCall);
228
+ if (vd != null) {
229
+ getOrUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
230
+ }
231
+ break;
232
+ }
233
+ case AST_NODE_TYPES.CallExpression: {
234
+ if (node !== node.parent.arguments.at(0)) {
235
+ break;
236
+ }
237
+ if (isUseCallbackCall2(node.parent)) {
238
+ const vd = AST.findParentNode(node.parent, isVariableDeclaratorFromHookCall);
239
+ if (vd != null) {
240
+ getOrUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
241
+ }
242
+ break;
243
+ }
244
+ if (isUseEffectLikeCall(node.parent)) {
245
+ getOrUpdate(indSetStateCallsInUseEffectSetup, node.parent, () => []).push(node);
246
+ }
247
+ }
248
+ }
249
+ },
250
+ "Program:exit"() {
251
+ const getSetStateCalls = (id, initialScope) => {
252
+ const node = VAR4.getVariableNode(VAR4.findVariable(id, initialScope), 0);
253
+ switch (node?.type) {
254
+ case AST_NODE_TYPES.ArrowFunctionExpression:
255
+ case AST_NODE_TYPES.FunctionDeclaration:
256
+ case AST_NODE_TYPES.FunctionExpression:
257
+ return indSetStateCalls.get(node) ?? [];
258
+ case AST_NODE_TYPES.CallExpression:
259
+ return indSetStateCallsInUseMemoOrCallback.get(node) ?? indSetStateCallsInUseEffectArg0.get(node) ?? [];
260
+ }
261
+ return [];
262
+ };
263
+ for (const [, calls] of indSetStateCallsInUseEffectSetup) {
264
+ for (const call of calls) {
265
+ onViolation(context, call, { name: call.name });
266
+ }
267
+ }
268
+ for (const { callee } of indFunctionCalls) {
269
+ if (!("name" in callee)) {
270
+ continue;
271
+ }
272
+ const { name: name3 } = callee;
273
+ const setStateCalls = getSetStateCalls(name3, context.sourceCode.getScope(callee));
274
+ for (const setStateCall of setStateCalls) {
275
+ onViolation(context, setStateCall, {
276
+ name: AST.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
277
+ });
278
+ }
279
+ }
280
+ for (const id of setupFunctionIdentifiers) {
281
+ const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
282
+ for (const setStateCall of setStateCalls) {
283
+ onViolation(context, setStateCall, {
284
+ name: AST.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
285
+ });
286
+ }
287
+ }
288
+ }
289
+ };
290
+ }
291
+
132
292
  // src/rules/no-direct-set-state-in-use-effect.ts
133
293
  var RULE_NAME = "no-direct-set-state-in-use-effect";
134
294
  var RULE_FEATURES = [
@@ -148,183 +308,18 @@ var no_direct_set_state_in_use_effect_default = createRule({
148
308
  },
149
309
  name: RULE_NAME,
150
310
  create(context) {
151
- if (!/use\w*Effect/u.test(context.sourceCode.text)) {
152
- return {};
153
- }
154
- const settings = getSettingsFromContext(context);
155
- const additionalHooks = settings.additionalHooks;
156
- const isUseEffectLikeCall = isReactHookCallWithNameAlias(context, "useEffect", additionalHooks.useEffect ?? []);
157
- const isUseStateCall2 = isReactHookCallWithNameAlias(context, "useState", additionalHooks.useState ?? []);
158
- const isUseMemoCall2 = isReactHookCallWithNameAlias(context, "useMemo", additionalHooks.useMemo ?? []);
159
- const isUseCallbackCall2 = isReactHookCallWithNameAlias(context, "useCallback", additionalHooks.useCallback ?? []);
160
- const isSetStateCall = isSetFunctionCall(context, settings);
161
- const isIdFromUseStateCall = isFromUseStateCall(context, settings);
162
- const functionEntries = [];
163
- const setupFunctionRef = { current: null };
164
- const setupFunctionIdentifiers = [];
165
- const indFunctionCalls = [];
166
- const indSetStateCalls = /* @__PURE__ */ new Map();
167
- const indSetStateCallsInUseEffectArg0 = /* @__PURE__ */ new Map();
168
- const indSetStateCallsInUseEffectSetup = /* @__PURE__ */ new Map();
169
- const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new Map();
170
- const onSetupFunctionEnter = (node) => {
171
- setupFunctionRef.current = node;
172
- };
173
- const onSetupFunctionExit = (node) => {
174
- if (setupFunctionRef.current === node) {
175
- setupFunctionRef.current = null;
176
- }
177
- };
178
- function isFunctionOfUseEffectSetup(node) {
179
- return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
180
- }
181
- function getCallKind(node) {
182
- return match(node).when(isUseStateCall2, () => "useState").when(isUseEffectLikeCall, () => "useEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
183
- }
184
- function getFunctionKind(node) {
185
- return match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
186
- }
187
- return {
188
- ":function"(node) {
189
- const kind = getFunctionKind(node);
190
- functionEntries.push({ kind, node });
191
- if (kind === "setup") {
192
- onSetupFunctionEnter(node);
193
- }
311
+ if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
312
+ return useNoDirectSetStateInUseEffect(context, {
313
+ onViolation(ctx, node, data) {
314
+ ctx.report({ messageId: "noDirectSetStateInUseEffect", node, data });
194
315
  },
195
- ":function:exit"(node) {
196
- const { kind } = functionEntries.at(-1) ?? {};
197
- if (kind === "setup") {
198
- onSetupFunctionExit(node);
199
- }
200
- functionEntries.pop();
201
- },
202
- CallExpression(node) {
203
- const setupFunction = setupFunctionRef.current;
204
- const pEntry = functionEntries.at(-1);
205
- if (pEntry == null || pEntry.node.async) {
206
- return;
207
- }
208
- match(getCallKind(node)).with("setState", () => {
209
- switch (true) {
210
- case (pEntry.node === setupFunction || pEntry.kind === "immediate"): {
211
- context.report({
212
- messageId: "noDirectSetStateInUseEffect",
213
- node,
214
- data: {
215
- name: context.sourceCode.getText(node.callee)
216
- }
217
- });
218
- return;
219
- }
220
- default: {
221
- const vd = AST.findParentNode(node, isVariableDeclaratorFromHookCall);
222
- if (vd == null) getOrUpdate(indSetStateCalls, pEntry.node, () => []).push(node);
223
- else getOrUpdate(indSetStateCallsInUseMemoOrCallback, vd.init, () => []).push(node);
224
- }
225
- }
226
- }).with("useEffect", () => {
227
- if (AST.isFunction(node.arguments.at(0))) return;
228
- setupFunctionIdentifiers.push(...AST.getNestedIdentifiers(node));
229
- }).with("other", () => {
230
- if (pEntry.node !== setupFunction) return;
231
- indFunctionCalls.push(node);
232
- }).otherwise(() => _);
233
- },
234
- Identifier(node) {
235
- if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) {
236
- return;
237
- }
238
- if (!isIdFromUseStateCall(node)) {
239
- return;
240
- }
241
- switch (node.parent.type) {
242
- case AST_NODE_TYPES.ArrowFunctionExpression: {
243
- const parent = node.parent.parent;
244
- if (parent.type !== AST_NODE_TYPES.CallExpression) {
245
- break;
246
- }
247
- if (!isUseMemoCall2(parent)) {
248
- break;
249
- }
250
- const vd = AST.findParentNode(parent, isVariableDeclaratorFromHookCall);
251
- if (vd != null) {
252
- getOrUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
253
- }
254
- break;
255
- }
256
- case AST_NODE_TYPES.CallExpression: {
257
- if (node !== node.parent.arguments.at(0)) {
258
- break;
259
- }
260
- if (isUseCallbackCall2(node.parent)) {
261
- const vd = AST.findParentNode(node.parent, isVariableDeclaratorFromHookCall);
262
- if (vd != null) {
263
- getOrUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
264
- }
265
- break;
266
- }
267
- if (isUseEffectLikeCall(node.parent)) {
268
- getOrUpdate(indSetStateCallsInUseEffectSetup, node.parent, () => []).push(node);
269
- }
270
- }
271
- }
272
- },
273
- "Program:exit"() {
274
- const getSetStateCalls = (id, initialScope) => {
275
- const node = VAR5.getVariableNode(VAR5.findVariable(id, initialScope), 0);
276
- switch (node?.type) {
277
- case AST_NODE_TYPES.ArrowFunctionExpression:
278
- case AST_NODE_TYPES.FunctionDeclaration:
279
- case AST_NODE_TYPES.FunctionExpression:
280
- return indSetStateCalls.get(node) ?? [];
281
- case AST_NODE_TYPES.CallExpression:
282
- return indSetStateCallsInUseMemoOrCallback.get(node) ?? indSetStateCallsInUseEffectArg0.get(node) ?? [];
283
- }
284
- return [];
285
- };
286
- for (const [, calls] of indSetStateCallsInUseEffectSetup) {
287
- for (const call of calls) {
288
- context.report({
289
- messageId: "noDirectSetStateInUseEffect",
290
- node: call,
291
- data: { name: call.name }
292
- });
293
- }
294
- }
295
- for (const { callee } of indFunctionCalls) {
296
- if (!("name" in callee)) {
297
- continue;
298
- }
299
- const { name: name3 } = callee;
300
- const setStateCalls = getSetStateCalls(name3, context.sourceCode.getScope(callee));
301
- for (const setStateCall of setStateCalls) {
302
- context.report({
303
- messageId: "noDirectSetStateInUseEffect",
304
- node: setStateCall,
305
- data: {
306
- name: AST.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
307
- }
308
- });
309
- }
310
- }
311
- for (const id of setupFunctionIdentifiers) {
312
- const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
313
- for (const setStateCall of setStateCalls) {
314
- context.report({
315
- messageId: "noDirectSetStateInUseEffect",
316
- node: setStateCall,
317
- data: {
318
- name: AST.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
319
- }
320
- });
321
- }
322
- }
323
- }
324
- };
316
+ useEffectKind: "useEffect"
317
+ });
325
318
  },
326
319
  defaultOptions: []
327
320
  });
321
+
322
+ // src/rules/no-direct-set-state-in-use-layout-effect.ts
328
323
  var RULE_NAME2 = "no-direct-set-state-in-use-layout-effect";
329
324
  var RULE_FEATURES2 = [
330
325
  "CHK"
@@ -343,184 +338,13 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
343
338
  },
344
339
  name: RULE_NAME2,
345
340
  create(context) {
346
- if (!/use\w*Effect/u.test(context.sourceCode.text)) {
347
- return {};
348
- }
349
- const settings = getSettingsFromContext(context);
350
- const additionalHooks = settings.additionalHooks;
351
- const isUseLayoutEffectLikeCall = isReactHookCallWithNameAlias(
352
- context,
353
- "useLayoutEffect",
354
- additionalHooks.useLayoutEffect ?? []
355
- );
356
- const isUseStateCall2 = isReactHookCallWithNameAlias(context, "useState", additionalHooks.useState ?? []);
357
- const isUseMemoCall2 = isReactHookCallWithNameAlias(context, "useMemo", additionalHooks.useMemo ?? []);
358
- const isUseCallbackCall2 = isReactHookCallWithNameAlias(context, "useCallback", additionalHooks.useCallback ?? []);
359
- const isSetStateCall = isSetFunctionCall(context, settings);
360
- const isIdFromUseStateCall = isFromUseStateCall(context, settings);
361
- const functionEntries = [];
362
- const setupFunctionRef = { current: null };
363
- const setupFunctionIdentifiers = [];
364
- const indFunctionCalls = [];
365
- const indSetStateCalls = /* @__PURE__ */ new Map();
366
- const indSetStateCallsInUseLayoutEffectArg0 = /* @__PURE__ */ new Map();
367
- const indSetStateCallsInUseLayoutEffectSetup = /* @__PURE__ */ new Map();
368
- const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new Map();
369
- const onSetupFunctionEnter = (node) => {
370
- setupFunctionRef.current = node;
371
- };
372
- const onSetupFunctionExit = (node) => {
373
- if (setupFunctionRef.current === node) {
374
- setupFunctionRef.current = null;
375
- }
376
- };
377
- function isFunctionOfUseEffectSetup(node) {
378
- return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseLayoutEffectLikeCall(node.parent);
379
- }
380
- function getCallKind(node) {
381
- return match(node).when(isUseStateCall2, () => "useState").when(isUseLayoutEffectLikeCall, () => "useLayoutEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
382
- }
383
- function getFunctionKind(node) {
384
- return match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
385
- }
386
- return {
387
- ":function"(node) {
388
- const kind = getFunctionKind(node);
389
- functionEntries.push({ kind, node });
390
- if (kind === "setup") {
391
- onSetupFunctionEnter(node);
392
- }
393
- },
394
- ":function:exit"(node) {
395
- const { kind } = functionEntries.at(-1) ?? {};
396
- if (kind === "setup") {
397
- onSetupFunctionExit(node);
398
- }
399
- functionEntries.pop();
341
+ if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
342
+ return useNoDirectSetStateInUseEffect(context, {
343
+ onViolation(ctx, node, data) {
344
+ ctx.report({ messageId: "noDirectSetStateInUseLayoutEffect", node, data });
400
345
  },
401
- CallExpression(node) {
402
- const setupFunction = setupFunctionRef.current;
403
- const pEntry = functionEntries.at(-1);
404
- if (pEntry == null || pEntry.node.async) {
405
- return;
406
- }
407
- match(getCallKind(node)).with("setState", () => {
408
- switch (true) {
409
- case (pEntry.node === setupFunction || pEntry.kind === "immediate"): {
410
- context.report({
411
- messageId: "noDirectSetStateInUseLayoutEffect",
412
- node,
413
- data: {
414
- name: context.sourceCode.getText(node.callee)
415
- }
416
- });
417
- return;
418
- }
419
- default: {
420
- const vd = AST.findParentNode(node, isVariableDeclaratorFromHookCall);
421
- if (vd == null) getOrUpdate(indSetStateCalls, pEntry.node, () => []).push(node);
422
- else getOrUpdate(indSetStateCallsInUseMemoOrCallback, vd.init, () => []).push(node);
423
- }
424
- }
425
- }).with("useLayoutEffect", () => {
426
- if (AST.isFunction(node.arguments.at(0))) return;
427
- setupFunctionIdentifiers.push(...AST.getNestedIdentifiers(node));
428
- }).with("other", () => {
429
- if (pEntry.node !== setupFunction) return;
430
- indFunctionCalls.push(node);
431
- }).otherwise(() => _);
432
- },
433
- Identifier(node) {
434
- if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) {
435
- return;
436
- }
437
- if (!isIdFromUseStateCall(node)) {
438
- return;
439
- }
440
- switch (node.parent.type) {
441
- case AST_NODE_TYPES.ArrowFunctionExpression: {
442
- const parent = node.parent.parent;
443
- if (parent.type !== AST_NODE_TYPES.CallExpression) {
444
- break;
445
- }
446
- if (!isUseMemoCall2(parent)) {
447
- break;
448
- }
449
- const vd = AST.findParentNode(parent, isVariableDeclaratorFromHookCall);
450
- if (vd != null) {
451
- getOrUpdate(indSetStateCallsInUseLayoutEffectArg0, vd.init, () => []).push(node);
452
- }
453
- break;
454
- }
455
- case AST_NODE_TYPES.CallExpression: {
456
- if (node !== node.parent.arguments.at(0)) {
457
- break;
458
- }
459
- if (isUseCallbackCall2(node.parent)) {
460
- const vd = AST.findParentNode(node.parent, isVariableDeclaratorFromHookCall);
461
- if (vd != null) {
462
- getOrUpdate(indSetStateCallsInUseLayoutEffectArg0, vd.init, () => []).push(node);
463
- }
464
- break;
465
- }
466
- if (isUseLayoutEffectLikeCall(node.parent)) {
467
- getOrUpdate(indSetStateCallsInUseLayoutEffectSetup, node.parent, () => []).push(node);
468
- }
469
- }
470
- }
471
- },
472
- "Program:exit"() {
473
- const getSetStateCalls = (id, initialScope) => {
474
- const node = VAR5.getVariableNode(VAR5.findVariable(id, initialScope), 0);
475
- switch (node?.type) {
476
- case AST_NODE_TYPES.ArrowFunctionExpression:
477
- case AST_NODE_TYPES.FunctionDeclaration:
478
- case AST_NODE_TYPES.FunctionExpression:
479
- return indSetStateCalls.get(node) ?? [];
480
- case AST_NODE_TYPES.CallExpression:
481
- return indSetStateCallsInUseMemoOrCallback.get(node) ?? indSetStateCallsInUseLayoutEffectArg0.get(node) ?? [];
482
- }
483
- return [];
484
- };
485
- for (const [, calls] of indSetStateCallsInUseLayoutEffectSetup) {
486
- for (const call of calls) {
487
- context.report({
488
- messageId: "noDirectSetStateInUseLayoutEffect",
489
- node: call,
490
- data: { name: call.name }
491
- });
492
- }
493
- }
494
- for (const { callee } of indFunctionCalls) {
495
- if (!("name" in callee)) {
496
- continue;
497
- }
498
- const { name: name3 } = callee;
499
- const setStateCalls = getSetStateCalls(name3, context.sourceCode.getScope(callee));
500
- for (const setStateCall of setStateCalls) {
501
- context.report({
502
- messageId: "noDirectSetStateInUseLayoutEffect",
503
- node: setStateCall,
504
- data: {
505
- name: AST.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
506
- }
507
- });
508
- }
509
- }
510
- for (const id of setupFunctionIdentifiers) {
511
- const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
512
- for (const setStateCall of setStateCalls) {
513
- context.report({
514
- messageId: "noDirectSetStateInUseLayoutEffect",
515
- node: setStateCall,
516
- data: {
517
- name: AST.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
518
- }
519
- });
520
- }
521
- }
522
- }
523
- };
346
+ useEffectKind: "useLayoutEffect"
347
+ });
524
348
  },
525
349
  defaultOptions: []
526
350
  });
@@ -565,8 +389,8 @@ var no_unnecessary_use_callback_default = createRule({
565
389
  return;
566
390
  }
567
391
  const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
568
- const variable = VAR5.findVariable(n.name, initialScope);
569
- const variableNode = VAR5.getVariableNode(variable, 0);
392
+ const variable = VAR4.findVariable(n.name, initialScope);
393
+ const variableNode = VAR4.getVariableNode(variable, 0);
570
394
  if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) {
571
395
  return false;
572
396
  }
@@ -581,8 +405,8 @@ var no_unnecessary_use_callback_default = createRule({
581
405
  }
582
406
  return n;
583
407
  }).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
584
- const variable = VAR5.findVariable(n.name, initialScope);
585
- const variableNode = VAR5.getVariableNode(variable, 0);
408
+ const variable = VAR4.findVariable(n.name, initialScope);
409
+ const variableNode = VAR4.getVariableNode(variable, 0);
586
410
  if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
587
411
  return _;
588
412
  }
@@ -590,7 +414,7 @@ var no_unnecessary_use_callback_default = createRule({
590
414
  }).otherwise(() => _);
591
415
  if (arg0Node == null) return;
592
416
  const arg0NodeScope = context.sourceCode.getScope(arg0Node);
593
- const arg0NodeReferences = VAR5.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
417
+ const arg0NodeReferences = VAR4.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
594
418
  const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
595
419
  if (!isReferencedToComponentScope) {
596
420
  context.report({
@@ -648,8 +472,8 @@ var no_unnecessary_use_memo_default = createRule({
648
472
  return;
649
473
  }
650
474
  const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
651
- const variable = VAR5.findVariable(n.name, initialScope);
652
- const variableNode = VAR5.getVariableNode(variable, 0);
475
+ const variable = VAR4.findVariable(n.name, initialScope);
476
+ const variableNode = VAR4.getVariableNode(variable, 0);
653
477
  if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) {
654
478
  return false;
655
479
  }
@@ -664,8 +488,8 @@ var no_unnecessary_use_memo_default = createRule({
664
488
  }
665
489
  return n;
666
490
  }).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
667
- const variable = VAR5.findVariable(n.name, initialScope);
668
- const variableNode = VAR5.getVariableNode(variable, 0);
491
+ const variable = VAR4.findVariable(n.name, initialScope);
492
+ const variableNode = VAR4.getVariableNode(variable, 0);
669
493
  if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
670
494
  return _;
671
495
  }
@@ -673,7 +497,7 @@ var no_unnecessary_use_memo_default = createRule({
673
497
  }).otherwise(() => _);
674
498
  if (arg0Node == null) return;
675
499
  const arg0NodeScope = context.sourceCode.getScope(arg0Node);
676
- const arg0NodeReferences = VAR5.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
500
+ const arg0NodeReferences = VAR4.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
677
501
  const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
678
502
  if (!isReferencedToComponentScope) {
679
503
  context.report({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-hooks-extra",
3
- "version": "1.30.0",
3
+ "version": "1.30.1-beta.0",
4
4
  "description": "ESLint React's ESLint plugin for React Hooks related rules.",
5
5
  "keywords": [
6
6
  "react",
@@ -50,12 +50,12 @@
50
50
  "@typescript-eslint/utils": "^8.25.0",
51
51
  "string-ts": "^2.2.1",
52
52
  "ts-pattern": "^5.6.2",
53
- "@eslint-react/ast": "1.30.0",
54
- "@eslint-react/core": "1.30.0",
55
- "@eslint-react/eff": "1.30.0",
56
- "@eslint-react/jsx": "1.30.0",
57
- "@eslint-react/shared": "1.30.0",
58
- "@eslint-react/var": "1.30.0"
53
+ "@eslint-react/ast": "1.30.1-beta.0",
54
+ "@eslint-react/core": "1.30.1-beta.0",
55
+ "@eslint-react/eff": "1.30.1-beta.0",
56
+ "@eslint-react/shared": "1.30.1-beta.0",
57
+ "@eslint-react/jsx": "1.30.1-beta.0",
58
+ "@eslint-react/var": "1.30.1-beta.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/react": "^19.0.10",