eslint-plugin-react-hooks-extra 1.5.25 → 1.5.26-next.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.
package/README.md CHANGED
@@ -40,9 +40,10 @@ export default [
40
40
 
41
41
  ## Rules
42
42
 
43
- | Rule | Description | 💼 | 💭 | ❌ |
44
- | :--------------------------------------- | :---------------------------------------------------------------- | :-: | :-: | :-: |
45
- | `ensure-custom-hooks-using-other-hooks` | Warns when custom Hooks that don't use other Hooks. | ✔️ | | |
46
- | `ensure-use-callback-has-non-empty-deps` | Warns when `useCallback` is called with empty dependencies array. | 🧐 | | |
47
- | `ensure-use-memo-has-non-empty-deps` | Warns when `useMemo` is called with empty dependencies array. | 🧐 | | |
48
- | `prefer-use-state-lazy-initialization` | Warns function calls made inside `useState` calls. | 🚀 | | |
43
+ | Rule | Description | 💼 | 💭 | ❌ |
44
+ | :--------------------------------------- | :------------------------------------------------------------------------ | :-: | :-: | :-: |
45
+ | `ensure-custom-hooks-using-other-hooks` | Warns when custom Hooks that don't use other Hooks. | ✔️ | | |
46
+ | `ensure-use-callback-has-non-empty-deps` | Warns when `useCallback` is called with empty dependencies array. | 🧐 | | |
47
+ | `ensure-use-memo-has-non-empty-deps` | Warns when `useMemo` is called with empty dependencies array. | 🧐 | | |
48
+ | `no-direct-set-state-in-use-effect` | Disallow direct calls to the `set` function of `useState` in `useEffect`. | ✔️ | | |
49
+ | `prefer-use-state-lazy-initialization` | Warns function calls made inside `useState` calls. | 🚀 | | |
package/dist/index.d.mts CHANGED
@@ -8,6 +8,7 @@ declare const rules: {
8
8
  readonly "ensure-custom-hooks-using-other-hooks": _typescript_eslint_utils_ts_eslint.RuleModule<"ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
9
9
  readonly "ensure-use-callback-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ENSURE_USE_CALLBACK_HAS_NON_EMPTY_DEPS", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
10
10
  readonly "ensure-use-memo-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ENSURE_USE_MEMO_HAS_NON_EMPTY_DEPS", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
11
+ readonly "no-direct-set-state-in-use-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"NO_DIRECT_SET_STATE_IN_USE_EFFECT", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
11
12
  readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"PREFER_USE_STATE_LAZY_INITIALIZATION", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
12
13
  };
13
14
 
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ declare const rules: {
8
8
  readonly "ensure-custom-hooks-using-other-hooks": _typescript_eslint_utils_ts_eslint.RuleModule<"ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
9
9
  readonly "ensure-use-callback-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ENSURE_USE_CALLBACK_HAS_NON_EMPTY_DEPS", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
10
10
  readonly "ensure-use-memo-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ENSURE_USE_MEMO_HAS_NON_EMPTY_DEPS", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
11
+ readonly "no-direct-set-state-in-use-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"NO_DIRECT_SET_STATE_IN_USE_EFFECT", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
11
12
  readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"PREFER_USE_STATE_LAZY_INITIALIZATION", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
12
13
  };
13
14
 
package/dist/index.js CHANGED
@@ -5,10 +5,11 @@ var shared = require('@eslint-react/shared');
5
5
  var ast = require('@eslint-react/ast');
6
6
  var tools = require('@eslint-react/tools');
7
7
  var _var = require('@eslint-react/var');
8
+ var astUtils = require('@typescript-eslint/utils/ast-utils');
8
9
 
9
10
  // package.json
10
11
  var name = "eslint-plugin-react-hooks-extra";
11
- var version = "1.5.25";
12
+ var version = "1.5.26-next.0";
12
13
  var createRule = shared.createRuleForPlugin("hooks-extra");
13
14
 
14
15
  // src/rules/ensure-custom-hooks-using-other-hooks.ts
@@ -316,7 +317,85 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
316
317
  },
317
318
  defaultOptions: []
318
319
  });
319
- var RULE_NAME4 = "prefer-use-state-lazy-initialization";
320
+ var RULE_NAME4 = "no-direct-set-state-in-use-effect";
321
+ var no_direct_set_state_in_use_effect_default = createRule({
322
+ meta: {
323
+ type: "problem",
324
+ docs: {
325
+ description: "disallow direct calls to the 'set' function of 'useState' in 'useEffect'"
326
+ },
327
+ messages: {
328
+ NO_DIRECT_SET_STATE_IN_USE_EFFECT: "Do not call the 'set' function of 'useState' directly in 'useEffect'."
329
+ },
330
+ schema: []
331
+ },
332
+ name: RULE_NAME4,
333
+ create(context) {
334
+ const settings = shared.getESLintReactSettings(context.settings);
335
+ const { useEffect: useEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
336
+ function isUseEffectCallWithAlias(node, context2) {
337
+ return core.isUseEffectCall(node, context2) || useEffectAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
338
+ }
339
+ function isUseStateCallWithAlias(node, context2) {
340
+ return core.isUseStateCall(node, context2) || useStateAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
341
+ }
342
+ function isEffectFunction(node) {
343
+ return node.parent?.type === ast.NodeType.CallExpression && isUseEffectCallWithAlias(node.parent, context);
344
+ }
345
+ return {
346
+ CallExpression(node) {
347
+ const effectFunction = ast.traverseUp(node, isEffectFunction);
348
+ if (tools.O.isNone(effectFunction)) return;
349
+ const scope = context.sourceCode.getScope(node);
350
+ if (scope.block !== effectFunction.value) return;
351
+ const name2 = $(node.callee).with({ type: ast.NodeType.Identifier }, (n2) => tools.O.some(n2.name)).with({ type: ast.NodeType.MemberExpression }, (n2) => {
352
+ if (!("name" in n2.object)) return tools.O.none();
353
+ const initialScope = context.sourceCode.getScope(n2);
354
+ const property = astUtils.getStaticValue(n2.property, initialScope);
355
+ if (property?.value === 1) return tools.O.fromNullable(n2.object.name);
356
+ return tools.O.none();
357
+ }).with({ type: ast.NodeType.CallExpression }, (n2) => {
358
+ if (!ast.is(ast.NodeType.MemberExpression)(n2.callee)) return tools.O.none();
359
+ if (!("name" in n2.callee.object)) return tools.O.none();
360
+ const isAt = $(n2.callee).with(
361
+ {
362
+ type: ast.NodeType.MemberExpression,
363
+ property: {
364
+ type: ast.NodeType.Identifier,
365
+ name: "at"
366
+ }
367
+ },
368
+ tools.F.constTrue
369
+ ).otherwise(tools.F.constFalse);
370
+ if (!isAt) return tools.O.none();
371
+ const [index] = n2.arguments;
372
+ if (!index) return tools.O.none();
373
+ const initialScope = context.sourceCode.getScope(n2);
374
+ const value = astUtils.getStaticValue(index, initialScope);
375
+ if (value?.value === 1) return tools.O.fromNullable(n2.callee.object.name);
376
+ return tools.O.none();
377
+ }).otherwise(tools.O.none);
378
+ tools.F.pipe(
379
+ name2,
380
+ tools.O.flatMap(_var.findVariable(scope)),
381
+ tools.O.flatMap(_var.getVariableInit(0)),
382
+ tools.O.filter(ast.is(ast.NodeType.CallExpression)),
383
+ tools.O.filter((name3) => isUseStateCallWithAlias(name3, context)),
384
+ tools.O.map((name3) => ({
385
+ data: {
386
+ setState: name3
387
+ },
388
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT",
389
+ node
390
+ })),
391
+ tools.O.map(context.report)
392
+ );
393
+ }
394
+ };
395
+ },
396
+ defaultOptions: []
397
+ });
398
+ var RULE_NAME5 = "prefer-use-state-lazy-initialization";
320
399
  var ALLOW_LIST = ["Boolean", "String", "Number"];
321
400
  var prefer_use_state_lazy_initialization_default = createRule({
322
401
  meta: {
@@ -329,7 +408,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
329
408
  },
330
409
  schema: []
331
410
  },
332
- name: RULE_NAME4,
411
+ name: RULE_NAME5,
333
412
  create(context) {
334
413
  const alias = shared.getESLintReactSettings(context.settings).additionalHooks?.useState ?? [];
335
414
  return {
@@ -362,6 +441,7 @@ var rules = {
362
441
  "ensure-custom-hooks-using-other-hooks": ensure_custom_hooks_using_other_hooks_default,
363
442
  "ensure-use-callback-has-non-empty-deps": ensure_use_callback_has_non_empty_deps_default,
364
443
  "ensure-use-memo-has-non-empty-deps": ensure_use_memo_has_non_empty_deps_default,
444
+ "no-direct-set-state-in-use-effect": no_direct_set_state_in_use_effect_default,
365
445
  "prefer-use-state-lazy-initialization": prefer_use_state_lazy_initialization_default
366
446
  };
367
447
 
package/dist/index.mjs CHANGED
@@ -1,12 +1,13 @@
1
- import { useHookCollector, isReactHookCall, isUseCallbackCall, isReactHookCallWithNameLoose, isUseMemoCall, isUseStateCall } from '@eslint-react/core';
1
+ import { useHookCollector, isReactHookCall, isUseCallbackCall, isReactHookCallWithNameLoose, isUseMemoCall, isUseStateCall, isUseEffectCall } from '@eslint-react/core';
2
2
  import { createRuleForPlugin, getESLintReactSettings } from '@eslint-react/shared';
3
- import { NodeType, is, getNestedCallExpressions } from '@eslint-react/ast';
3
+ import { NodeType, is, traverseUp, getNestedCallExpressions } from '@eslint-react/ast';
4
4
  import { F, O } from '@eslint-react/tools';
5
5
  import { findVariable, getVariableInit } from '@eslint-react/var';
6
+ import { getStaticValue } from '@typescript-eslint/utils/ast-utils';
6
7
 
7
8
  // package.json
8
9
  var name = "eslint-plugin-react-hooks-extra";
9
- var version = "1.5.25";
10
+ var version = "1.5.26-next.0";
10
11
  var createRule = createRuleForPlugin("hooks-extra");
11
12
 
12
13
  // src/rules/ensure-custom-hooks-using-other-hooks.ts
@@ -314,7 +315,85 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
314
315
  },
315
316
  defaultOptions: []
316
317
  });
317
- var RULE_NAME4 = "prefer-use-state-lazy-initialization";
318
+ var RULE_NAME4 = "no-direct-set-state-in-use-effect";
319
+ var no_direct_set_state_in_use_effect_default = createRule({
320
+ meta: {
321
+ type: "problem",
322
+ docs: {
323
+ description: "disallow direct calls to the 'set' function of 'useState' in 'useEffect'"
324
+ },
325
+ messages: {
326
+ NO_DIRECT_SET_STATE_IN_USE_EFFECT: "Do not call the 'set' function of 'useState' directly in 'useEffect'."
327
+ },
328
+ schema: []
329
+ },
330
+ name: RULE_NAME4,
331
+ create(context) {
332
+ const settings = getESLintReactSettings(context.settings);
333
+ const { useEffect: useEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
334
+ function isUseEffectCallWithAlias(node, context2) {
335
+ return isUseEffectCall(node, context2) || useEffectAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
336
+ }
337
+ function isUseStateCallWithAlias(node, context2) {
338
+ return isUseStateCall(node, context2) || useStateAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
339
+ }
340
+ function isEffectFunction(node) {
341
+ return node.parent?.type === NodeType.CallExpression && isUseEffectCallWithAlias(node.parent, context);
342
+ }
343
+ return {
344
+ CallExpression(node) {
345
+ const effectFunction = traverseUp(node, isEffectFunction);
346
+ if (O.isNone(effectFunction)) return;
347
+ const scope = context.sourceCode.getScope(node);
348
+ if (scope.block !== effectFunction.value) return;
349
+ const name2 = $(node.callee).with({ type: NodeType.Identifier }, (n2) => O.some(n2.name)).with({ type: NodeType.MemberExpression }, (n2) => {
350
+ if (!("name" in n2.object)) return O.none();
351
+ const initialScope = context.sourceCode.getScope(n2);
352
+ const property = getStaticValue(n2.property, initialScope);
353
+ if (property?.value === 1) return O.fromNullable(n2.object.name);
354
+ return O.none();
355
+ }).with({ type: NodeType.CallExpression }, (n2) => {
356
+ if (!is(NodeType.MemberExpression)(n2.callee)) return O.none();
357
+ if (!("name" in n2.callee.object)) return O.none();
358
+ const isAt = $(n2.callee).with(
359
+ {
360
+ type: NodeType.MemberExpression,
361
+ property: {
362
+ type: NodeType.Identifier,
363
+ name: "at"
364
+ }
365
+ },
366
+ F.constTrue
367
+ ).otherwise(F.constFalse);
368
+ if (!isAt) return O.none();
369
+ const [index] = n2.arguments;
370
+ if (!index) return O.none();
371
+ const initialScope = context.sourceCode.getScope(n2);
372
+ const value = getStaticValue(index, initialScope);
373
+ if (value?.value === 1) return O.fromNullable(n2.callee.object.name);
374
+ return O.none();
375
+ }).otherwise(O.none);
376
+ F.pipe(
377
+ name2,
378
+ O.flatMap(findVariable(scope)),
379
+ O.flatMap(getVariableInit(0)),
380
+ O.filter(is(NodeType.CallExpression)),
381
+ O.filter((name3) => isUseStateCallWithAlias(name3, context)),
382
+ O.map((name3) => ({
383
+ data: {
384
+ setState: name3
385
+ },
386
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT",
387
+ node
388
+ })),
389
+ O.map(context.report)
390
+ );
391
+ }
392
+ };
393
+ },
394
+ defaultOptions: []
395
+ });
396
+ var RULE_NAME5 = "prefer-use-state-lazy-initialization";
318
397
  var ALLOW_LIST = ["Boolean", "String", "Number"];
319
398
  var prefer_use_state_lazy_initialization_default = createRule({
320
399
  meta: {
@@ -327,7 +406,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
327
406
  },
328
407
  schema: []
329
408
  },
330
- name: RULE_NAME4,
409
+ name: RULE_NAME5,
331
410
  create(context) {
332
411
  const alias = getESLintReactSettings(context.settings).additionalHooks?.useState ?? [];
333
412
  return {
@@ -360,6 +439,7 @@ var rules = {
360
439
  "ensure-custom-hooks-using-other-hooks": ensure_custom_hooks_using_other_hooks_default,
361
440
  "ensure-use-callback-has-non-empty-deps": ensure_use_callback_has_non_empty_deps_default,
362
441
  "ensure-use-memo-has-non-empty-deps": ensure_use_memo_has_non_empty_deps_default,
442
+ "no-direct-set-state-in-use-effect": no_direct_set_state_in_use_effect_default,
363
443
  "prefer-use-state-lazy-initialization": prefer_use_state_lazy_initialization_default
364
444
  };
365
445
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-hooks-extra",
3
- "version": "1.5.25",
3
+ "version": "1.5.26-next.0",
4
4
  "description": "ESLint React's ESLint plugin for React Hooks related rules.",
5
5
  "homepage": "https://github.com/rel1cx/eslint-react",
6
6
  "bugs": {
@@ -39,13 +39,13 @@
39
39
  "@typescript-eslint/type-utils": "^7.16.0",
40
40
  "@typescript-eslint/types": "^7.16.0",
41
41
  "@typescript-eslint/utils": "^7.16.0",
42
- "@eslint-react/core": "1.5.25",
43
- "@eslint-react/shared": "1.5.25",
44
- "@eslint-react/jsx": "1.5.25",
45
- "@eslint-react/tools": "1.5.25",
46
- "@eslint-react/types": "1.5.25",
47
- "@eslint-react/ast": "1.5.25",
48
- "@eslint-react/var": "1.5.25"
42
+ "@eslint-react/ast": "1.5.26-next.0",
43
+ "@eslint-react/core": "1.5.26-next.0",
44
+ "@eslint-react/jsx": "1.5.26-next.0",
45
+ "@eslint-react/tools": "1.5.26-next.0",
46
+ "@eslint-react/shared": "1.5.26-next.0",
47
+ "@eslint-react/types": "1.5.26-next.0",
48
+ "@eslint-react/var": "1.5.26-next.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "string-ts": "2.2.0",