eslint-plugin-react-hooks-extra 2.0.0-next.2 → 2.0.0-next.200
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 +20 -18
- package/dist/index.d.ts +16 -41
- package/dist/index.js +247 -685
- package/package.json +19 -28
package/README.md
CHANGED
|
@@ -17,28 +17,30 @@ npm install --save-dev eslint-plugin-react-hooks-extra
|
|
|
17
17
|
// @ts-check
|
|
18
18
|
import js from "@eslint/js";
|
|
19
19
|
import reactHooksExtra from "eslint-plugin-react-hooks-extra";
|
|
20
|
+
import { defineConfig } from "eslint/config";
|
|
20
21
|
import tseslint from "typescript-eslint";
|
|
21
22
|
|
|
22
|
-
export default
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
export default defineConfig([
|
|
24
|
+
{
|
|
25
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
26
|
+
extends: [
|
|
27
|
+
js.configs.recommended,
|
|
28
|
+
tseslint.configs.recommended,
|
|
29
|
+
reactHooksExtra.configs.recommended,
|
|
30
|
+
],
|
|
31
|
+
languageOptions: {
|
|
32
|
+
parser: tseslint.parser,
|
|
33
|
+
parserOptions: {
|
|
34
|
+
projectService: true,
|
|
35
|
+
tsconfigRootDir: import.meta.dirname,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
rules: {
|
|
39
|
+
// Put rules you want to override here
|
|
40
|
+
"react-hooks-extra/no-direct-set-state-in-use-effect": "warn",
|
|
34
41
|
},
|
|
35
42
|
},
|
|
36
|
-
|
|
37
|
-
// Put rules you want to override here
|
|
38
|
-
"react-hooks-extra/no-unnecessary-use-prefix": "warn",
|
|
39
|
-
"react-hooks-extra/prefer-use-state-lazy-initialization": "warn",
|
|
40
|
-
},
|
|
41
|
-
});
|
|
43
|
+
]);
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
## Rules
|
package/dist/index.d.ts
CHANGED
|
@@ -1,45 +1,20 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { RulePreset } from '@eslint-react/kit';
|
|
1
|
+
import * as _eslint_react_kit0 from "@eslint-react/kit";
|
|
3
2
|
|
|
3
|
+
//#region src/index.d.ts
|
|
4
4
|
declare const _default: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
readonly version: string;
|
|
12
|
-
};
|
|
13
|
-
readonly rules: {
|
|
14
|
-
readonly "no-direct-set-state-in-use-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
15
|
-
readonly "no-direct-set-state-in-use-layout-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseLayoutEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
16
|
-
readonly "no-unnecessary-use-callback": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUseCallback", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
17
|
-
readonly "no-unnecessary-use-memo": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUseMemo", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
18
|
-
readonly "no-unnecessary-use-prefix": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUsePrefix", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
19
|
-
readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"preferUseStateLazyInitialization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
name: string;
|
|
24
|
-
rules: RulePreset;
|
|
25
|
-
};
|
|
26
|
-
"recommended-legacy": {
|
|
27
|
-
plugins: string[];
|
|
28
|
-
rules: RulePreset;
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
meta: {
|
|
32
|
-
readonly name: string;
|
|
33
|
-
readonly version: string;
|
|
34
|
-
};
|
|
35
|
-
rules: {
|
|
36
|
-
readonly "no-direct-set-state-in-use-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
37
|
-
readonly "no-direct-set-state-in-use-layout-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseLayoutEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
38
|
-
readonly "no-unnecessary-use-callback": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUseCallback", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
39
|
-
readonly "no-unnecessary-use-memo": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUseMemo", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
40
|
-
readonly "no-unnecessary-use-prefix": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUsePrefix", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
41
|
-
readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"preferUseStateLazyInitialization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
5
|
+
configs: {
|
|
6
|
+
recommended: {
|
|
7
|
+
plugins: {};
|
|
8
|
+
name?: string;
|
|
9
|
+
rules?: Record<string, _eslint_react_kit0.RuleConfig>;
|
|
10
|
+
settings?: _eslint_react_kit0.SettingsConfig;
|
|
42
11
|
};
|
|
12
|
+
};
|
|
13
|
+
meta: {
|
|
14
|
+
name: string;
|
|
15
|
+
version: string;
|
|
16
|
+
};
|
|
17
|
+
rules: Record<string, _eslint_react_kit0.CompatibleRule>;
|
|
43
18
|
};
|
|
44
|
-
|
|
45
|
-
export { _default as default };
|
|
19
|
+
//#endregion
|
|
20
|
+
export { _default as default };
|
package/dist/index.js
CHANGED
|
@@ -1,704 +1,266 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import * as
|
|
6
|
-
import { AST_NODE_TYPES } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import { getConfigAdapters, getDocsUrl } from "@eslint-react/shared";
|
|
2
|
+
import * as AST from "@eslint-react/ast";
|
|
3
|
+
import * as ER from "@eslint-react/core";
|
|
4
|
+
import { constVoid, getOrElseUpdate, not } from "@eslint-react/eff";
|
|
5
|
+
import * as VAR from "@eslint-react/var";
|
|
6
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
7
|
+
import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
8
|
+
import { match } from "ts-pattern";
|
|
9
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
9
10
|
|
|
11
|
+
//#region rolldown:runtime
|
|
10
12
|
var __defProp = Object.defineProperty;
|
|
11
|
-
var __export = (
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
var __export = (all) => {
|
|
14
|
+
let target = {};
|
|
15
|
+
for (var name$2 in all) __defProp(target, name$2, {
|
|
16
|
+
get: all[name$2],
|
|
17
|
+
enumerable: true
|
|
18
|
+
});
|
|
19
|
+
return target;
|
|
14
20
|
};
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
__export(
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/configs/recommended.ts
|
|
24
|
+
var recommended_exports = /* @__PURE__ */ __export({
|
|
25
|
+
name: () => name$1,
|
|
26
|
+
rules: () => rules
|
|
21
27
|
});
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"react-hooks-extra/no-direct-set-state-in-use-effect": "warn",
|
|
25
|
-
"react-hooks-extra/no-unnecessary-use-prefix": "warn",
|
|
26
|
-
"react-hooks-extra/prefer-use-state-lazy-initialization": "warn"
|
|
27
|
-
};
|
|
28
|
+
const name$1 = "react-hooks-extra/recommended";
|
|
29
|
+
const rules = { "react-hooks-extra/no-direct-set-state-in-use-effect": "warn" };
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
var
|
|
32
|
-
var
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!("id" in parent) || parent.id?.type !== AST_NODE_TYPES.ArrayPattern) {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
return parent.id.elements.findIndex((e) => e?.type === AST_NODE_TYPES.Identifier && e.name === topLevelId.name) === 1;
|
|
51
|
-
};
|
|
52
|
-
return isFromHookCall(context, "useState", settings, predicate);
|
|
53
|
-
}
|
|
54
|
-
function isFunctionOfImmediatelyInvoked(node) {
|
|
55
|
-
return node.type !== AST_NODE_TYPES.FunctionDeclaration && node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node;
|
|
56
|
-
}
|
|
57
|
-
function isSetFunctionCall(context, settings) {
|
|
58
|
-
const isIdFromUseStateCall = isFromUseStateCall(context, settings);
|
|
59
|
-
return (node) => {
|
|
60
|
-
switch (node.callee.type) {
|
|
61
|
-
// const data = useState();
|
|
62
|
-
// data.at(1)();
|
|
63
|
-
case AST_NODE_TYPES.CallExpression: {
|
|
64
|
-
const { callee } = node.callee;
|
|
65
|
-
if (callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
if (!("name" in callee.object)) {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
const isAt = callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "at";
|
|
72
|
-
const [index] = node.callee.arguments;
|
|
73
|
-
if (!isAt || index == null) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
const indexScope = context.sourceCode.getScope(node);
|
|
77
|
-
const indexValue = VAR4.toStaticValue({
|
|
78
|
-
kind: "lazy",
|
|
79
|
-
node: index,
|
|
80
|
-
initialScope: indexScope
|
|
81
|
-
}).value;
|
|
82
|
-
return indexValue === 1 && isIdFromUseStateCall(callee.object);
|
|
83
|
-
}
|
|
84
|
-
// const [data, setData] = useState();
|
|
85
|
-
// setData();
|
|
86
|
-
case AST_NODE_TYPES.Identifier: {
|
|
87
|
-
return isIdFromUseStateCall(node.callee);
|
|
88
|
-
}
|
|
89
|
-
// const data = useState();
|
|
90
|
-
// data[1]();
|
|
91
|
-
case AST_NODE_TYPES.MemberExpression: {
|
|
92
|
-
if (!("name" in node.callee.object)) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
const property = node.callee.property;
|
|
96
|
-
const propertyScope = context.sourceCode.getScope(node);
|
|
97
|
-
const propertyValue = VAR4.toStaticValue({
|
|
98
|
-
kind: "lazy",
|
|
99
|
-
node: property,
|
|
100
|
-
initialScope: propertyScope
|
|
101
|
-
}).value;
|
|
102
|
-
return propertyValue === 1 && isIdFromUseStateCall(node.callee.object);
|
|
103
|
-
}
|
|
104
|
-
default: {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function isThenCall(node) {
|
|
111
|
-
return node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "then";
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region package.json
|
|
33
|
+
var name = "eslint-plugin-react-hooks-extra";
|
|
34
|
+
var version = "2.0.0-next.200";
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/utils/create-rule.ts
|
|
38
|
+
const createRule = ESLintUtils.RuleCreator(getDocsUrl("hooks-extra"));
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/utils/is-variable-declarator-from-hook-call.ts
|
|
42
|
+
function isInitFromHookCall(init) {
|
|
43
|
+
if (init?.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
44
|
+
switch (init.callee.type) {
|
|
45
|
+
case AST_NODE_TYPES.Identifier: return ER.isReactHookName(init.callee.name);
|
|
46
|
+
case AST_NODE_TYPES.MemberExpression: return init.callee.property.type === AST_NODE_TYPES.Identifier && ER.isReactHookName(init.callee.property.name);
|
|
47
|
+
default: return false;
|
|
48
|
+
}
|
|
112
49
|
}
|
|
113
50
|
function isVariableDeclaratorFromHookCall(node) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (node.id.type !== AST_NODE_TYPES.Identifier) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
if (node.init?.type !== AST_NODE_TYPES.CallExpression) {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
switch (node.init.callee.type) {
|
|
124
|
-
case AST_NODE_TYPES.Identifier:
|
|
125
|
-
return ER7.isReactHookName(node.init.callee.name);
|
|
126
|
-
case AST_NODE_TYPES.MemberExpression:
|
|
127
|
-
return node.init.callee.property.type === AST_NODE_TYPES.Identifier && ER7.isReactHookName(node.init.callee.property.name);
|
|
128
|
-
default:
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// src/hooks/use-no-direct-set-state-in-use-effect.ts
|
|
134
|
-
function useNoDirectSetStateInUseEffect(context, options) {
|
|
135
|
-
const { onViolation, useEffectKind } = options;
|
|
136
|
-
const settings = getSettingsFromContext(context);
|
|
137
|
-
const hooks = settings.additionalHooks;
|
|
138
|
-
const getText = (n) => context.sourceCode.getText(n);
|
|
139
|
-
const isUseEffectLikeCall = ER7.isReactHookCallWithNameAlias(context, useEffectKind, hooks[useEffectKind]);
|
|
140
|
-
const isUseStateCall = ER7.isReactHookCallWithNameAlias(context, "useState", hooks.useState);
|
|
141
|
-
const isUseMemoCall = ER7.isReactHookCallWithNameAlias(context, "useMemo", hooks.useMemo);
|
|
142
|
-
const isUseCallbackCall = ER7.isReactHookCallWithNameAlias(context, "useCallback", hooks.useCallback);
|
|
143
|
-
const isSetStateCall = isSetFunctionCall(context, settings);
|
|
144
|
-
const isIdFromUseStateCall = isFromUseStateCall(context, settings);
|
|
145
|
-
const functionEntries = [];
|
|
146
|
-
const setupFunctionRef = { current: null };
|
|
147
|
-
const setupFunctionIdentifiers = [];
|
|
148
|
-
const indFunctionCalls = [];
|
|
149
|
-
const indSetStateCalls = /* @__PURE__ */ new WeakMap();
|
|
150
|
-
const indSetStateCallsInUseEffectArg0 = /* @__PURE__ */ new WeakMap();
|
|
151
|
-
const indSetStateCallsInUseEffectSetup = /* @__PURE__ */ new Map();
|
|
152
|
-
const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new WeakMap();
|
|
153
|
-
const onSetupFunctionEnter = (node) => {
|
|
154
|
-
setupFunctionRef.current = node;
|
|
155
|
-
};
|
|
156
|
-
const onSetupFunctionExit = (node) => {
|
|
157
|
-
if (setupFunctionRef.current === node) {
|
|
158
|
-
setupFunctionRef.current = null;
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
function isFunctionOfUseEffectSetup(node) {
|
|
162
|
-
return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
|
|
163
|
-
}
|
|
164
|
-
function getCallName(node) {
|
|
165
|
-
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
166
|
-
return AST.toString(node.callee, getText);
|
|
167
|
-
}
|
|
168
|
-
return AST.toString(node, getText);
|
|
169
|
-
}
|
|
170
|
-
function getCallKind(node) {
|
|
171
|
-
return match(node).when(isUseStateCall, () => "useState").when(isUseEffectLikeCall, () => useEffectKind).when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
|
|
172
|
-
}
|
|
173
|
-
function getFunctionKind(node) {
|
|
174
|
-
return match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
":function"(node) {
|
|
178
|
-
const kind = getFunctionKind(node);
|
|
179
|
-
functionEntries.push({ kind, node });
|
|
180
|
-
if (kind === "setup") {
|
|
181
|
-
onSetupFunctionEnter(node);
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
":function:exit"(node) {
|
|
185
|
-
const { kind } = functionEntries.at(-1) ?? {};
|
|
186
|
-
if (kind === "setup") {
|
|
187
|
-
onSetupFunctionExit(node);
|
|
188
|
-
}
|
|
189
|
-
functionEntries.pop();
|
|
190
|
-
},
|
|
191
|
-
CallExpression(node) {
|
|
192
|
-
const setupFunction = setupFunctionRef.current;
|
|
193
|
-
const pEntry = functionEntries.at(-1);
|
|
194
|
-
if (pEntry == null || pEntry.node.async) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
match(getCallKind(node)).with("setState", () => {
|
|
198
|
-
switch (true) {
|
|
199
|
-
case pEntry.node === setupFunction:
|
|
200
|
-
case (pEntry.kind === "immediate" && AST.findParentNode(pEntry.node, AST.isFunction) === setupFunction): {
|
|
201
|
-
onViolation(context, node, {
|
|
202
|
-
name: context.sourceCode.getText(node.callee)
|
|
203
|
-
});
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
default: {
|
|
207
|
-
const vd = AST.findParentNode(node, isVariableDeclaratorFromHookCall);
|
|
208
|
-
if (vd == null) getOrElseUpdate(indSetStateCalls, pEntry.node, () => []).push(node);
|
|
209
|
-
else getOrElseUpdate(indSetStateCallsInUseMemoOrCallback, vd.init, () => []).push(node);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}).with(useEffectKind, () => {
|
|
213
|
-
if (AST.isFunction(node.arguments.at(0))) return;
|
|
214
|
-
setupFunctionIdentifiers.push(...AST.getNestedIdentifiers(node));
|
|
215
|
-
}).with("other", () => {
|
|
216
|
-
if (pEntry.node !== setupFunction) return;
|
|
217
|
-
indFunctionCalls.push(node);
|
|
218
|
-
}).otherwise(constVoid);
|
|
219
|
-
},
|
|
220
|
-
Identifier(node) {
|
|
221
|
-
if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
if (!isIdFromUseStateCall(node)) {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
switch (node.parent.type) {
|
|
228
|
-
case AST_NODE_TYPES.ArrowFunctionExpression: {
|
|
229
|
-
const parent = node.parent.parent;
|
|
230
|
-
if (parent.type !== AST_NODE_TYPES.CallExpression) {
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
if (!isUseMemoCall(parent)) {
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
const vd = AST.findParentNode(parent, isVariableDeclaratorFromHookCall);
|
|
237
|
-
if (vd != null) {
|
|
238
|
-
getOrElseUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
|
|
239
|
-
}
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
case AST_NODE_TYPES.CallExpression: {
|
|
243
|
-
if (node !== node.parent.arguments.at(0)) {
|
|
244
|
-
break;
|
|
245
|
-
}
|
|
246
|
-
if (isUseCallbackCall(node.parent)) {
|
|
247
|
-
const vd = AST.findParentNode(node.parent, isVariableDeclaratorFromHookCall);
|
|
248
|
-
if (vd != null) {
|
|
249
|
-
getOrElseUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
|
|
250
|
-
}
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
if (isUseEffectLikeCall(node.parent)) {
|
|
254
|
-
getOrElseUpdate(indSetStateCallsInUseEffectSetup, node.parent, () => []).push(node);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
},
|
|
259
|
-
"Program:exit"() {
|
|
260
|
-
const getSetStateCalls = (id, initialScope) => {
|
|
261
|
-
const node = VAR4.getVariableInitNode(VAR4.findVariable(id, initialScope), 0);
|
|
262
|
-
switch (node?.type) {
|
|
263
|
-
case AST_NODE_TYPES.ArrowFunctionExpression:
|
|
264
|
-
case AST_NODE_TYPES.FunctionDeclaration:
|
|
265
|
-
case AST_NODE_TYPES.FunctionExpression:
|
|
266
|
-
return indSetStateCalls.get(node) ?? [];
|
|
267
|
-
case AST_NODE_TYPES.CallExpression:
|
|
268
|
-
return indSetStateCallsInUseMemoOrCallback.get(node) ?? indSetStateCallsInUseEffectArg0.get(node) ?? [];
|
|
269
|
-
}
|
|
270
|
-
return [];
|
|
271
|
-
};
|
|
272
|
-
for (const [, calls] of indSetStateCallsInUseEffectSetup) {
|
|
273
|
-
for (const call of calls) {
|
|
274
|
-
onViolation(context, call, { name: call.name });
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
for (const { callee } of indFunctionCalls) {
|
|
278
|
-
if (!("name" in callee)) {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
const { name: name3 } = callee;
|
|
282
|
-
const setStateCalls = getSetStateCalls(name3, context.sourceCode.getScope(callee));
|
|
283
|
-
for (const setStateCall of setStateCalls) {
|
|
284
|
-
onViolation(context, setStateCall, {
|
|
285
|
-
name: getCallName(setStateCall)
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
for (const id of setupFunctionIdentifiers) {
|
|
290
|
-
const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
|
|
291
|
-
for (const setStateCall of setStateCalls) {
|
|
292
|
-
onViolation(context, setStateCall, {
|
|
293
|
-
name: getCallName(setStateCall)
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
};
|
|
51
|
+
if (node.type !== AST_NODE_TYPES.VariableDeclarator) return false;
|
|
52
|
+
if (node.id.type !== AST_NODE_TYPES.Identifier) return false;
|
|
53
|
+
return isInitFromHookCall(node.init);
|
|
299
54
|
}
|
|
300
55
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
];
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/rules/no-direct-set-state-in-use-effect.ts
|
|
58
|
+
const RULE_NAME = "no-direct-set-state-in-use-effect";
|
|
59
|
+
const RULE_FEATURES = ["EXP"];
|
|
306
60
|
var no_direct_set_state_in_use_effect_default = createRule({
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
create,
|
|
320
|
-
defaultOptions: []
|
|
61
|
+
meta: {
|
|
62
|
+
type: "problem",
|
|
63
|
+
docs: {
|
|
64
|
+
description: "Disallow direct calls to the `set` function of `useState` in `useEffect`.",
|
|
65
|
+
[Symbol.for("rule_features")]: RULE_FEATURES
|
|
66
|
+
},
|
|
67
|
+
messages: { noDirectSetStateInUseEffect: "Do not call the 'set' function '{{name}}' of 'useState' directly in 'useEffect'." },
|
|
68
|
+
schema: []
|
|
69
|
+
},
|
|
70
|
+
name: RULE_NAME,
|
|
71
|
+
create,
|
|
72
|
+
defaultOptions: []
|
|
321
73
|
});
|
|
322
74
|
function create(context) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
];
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return variableNode.elements.length === 0;
|
|
494
|
-
}).otherwise(() => false);
|
|
495
|
-
if (!hasEmptyDeps) {
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
|
|
499
|
-
if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
500
|
-
return n.body;
|
|
501
|
-
}
|
|
502
|
-
return n;
|
|
503
|
-
}).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
504
|
-
const variable = VAR4.findVariable(n.name, initialScope);
|
|
505
|
-
const variableNode = VAR4.getVariableInitNode(variable, 0);
|
|
506
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
507
|
-
return null;
|
|
508
|
-
}
|
|
509
|
-
return variableNode;
|
|
510
|
-
}).otherwise(() => null);
|
|
511
|
-
if (arg0Node == null) return;
|
|
512
|
-
const arg0NodeScope = context.sourceCode.getScope(arg0Node);
|
|
513
|
-
const arg0NodeReferences = VAR4.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
|
|
514
|
-
const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
|
|
515
|
-
if (!isReferencedToComponentScope) {
|
|
516
|
-
context.report({
|
|
517
|
-
messageId: "noUnnecessaryUseMemo",
|
|
518
|
-
node
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
var RULE_NAME5 = "no-unnecessary-use-prefix";
|
|
525
|
-
var RULE_FEATURES5 = [];
|
|
526
|
-
var WELL_KNOWN_HOOKS = [
|
|
527
|
-
"useMDXComponents"
|
|
528
|
-
];
|
|
529
|
-
function containsUseComments(context, node) {
|
|
530
|
-
return context.sourceCode.getCommentsInside(node).some(({ value }) => /use\([\s\S]*?\)/u.test(value) || /use[A-Z0-9]\w*\([\s\S]*?\)/u.test(value));
|
|
531
|
-
}
|
|
532
|
-
var no_unnecessary_use_prefix_default = createRule({
|
|
533
|
-
meta: {
|
|
534
|
-
type: "problem",
|
|
535
|
-
docs: {
|
|
536
|
-
description: "Enforces that a function with the `use` prefix should use at least one Hook inside of it.",
|
|
537
|
-
[Symbol.for("rule_features")]: RULE_FEATURES5
|
|
538
|
-
},
|
|
539
|
-
messages: {
|
|
540
|
-
noUnnecessaryUsePrefix: "If your function doesn't call any Hooks, avoid the 'use' prefix. Instead, write it as a regular function without the 'use' prefix."
|
|
541
|
-
},
|
|
542
|
-
schema: []
|
|
543
|
-
},
|
|
544
|
-
name: RULE_NAME5,
|
|
545
|
-
create: create5,
|
|
546
|
-
defaultOptions: []
|
|
547
|
-
});
|
|
548
|
-
function create5(context) {
|
|
549
|
-
const { ctx, listeners } = ER7.useHookCollector();
|
|
550
|
-
return {
|
|
551
|
-
...listeners,
|
|
552
|
-
"Program:exit"(program) {
|
|
553
|
-
const allHooks = ctx.getAllHooks(program);
|
|
554
|
-
for (const { id, name: name3, node, hookCalls } of allHooks.values()) {
|
|
555
|
-
if (WELL_KNOWN_HOOKS.includes(name3)) {
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
if (AST.isEmptyFunction(node)) {
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
if (hookCalls.length > 0) {
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
if (containsUseComments(context, node)) {
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
if (id != null) {
|
|
568
|
-
context.report({
|
|
569
|
-
messageId: "noUnnecessaryUsePrefix",
|
|
570
|
-
data: {
|
|
571
|
-
name: name3
|
|
572
|
-
},
|
|
573
|
-
loc: getPreferredLoc(context, id)
|
|
574
|
-
});
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
context.report({
|
|
578
|
-
messageId: "noUnnecessaryUsePrefix",
|
|
579
|
-
node,
|
|
580
|
-
data: {
|
|
581
|
-
name: name3
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
function getPreferredLoc(context, id) {
|
|
589
|
-
if (AST.isMultiLine(id)) return id.loc;
|
|
590
|
-
if (!context.sourceCode.getText(id).startsWith("use")) return id.loc;
|
|
591
|
-
return {
|
|
592
|
-
end: {
|
|
593
|
-
column: id.loc.start.column + 3,
|
|
594
|
-
line: id.loc.start.line
|
|
595
|
-
},
|
|
596
|
-
start: {
|
|
597
|
-
column: id.loc.start.column,
|
|
598
|
-
line: id.loc.start.line
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
var RULE_NAME6 = "prefer-use-state-lazy-initialization";
|
|
603
|
-
var RULE_FEATURES6 = [
|
|
604
|
-
"EXP"
|
|
605
|
-
];
|
|
606
|
-
var ALLOW_LIST = [
|
|
607
|
-
"Boolean",
|
|
608
|
-
"String",
|
|
609
|
-
"Number"
|
|
610
|
-
];
|
|
611
|
-
var prefer_use_state_lazy_initialization_default = createRule({
|
|
612
|
-
meta: {
|
|
613
|
-
type: "problem",
|
|
614
|
-
docs: {
|
|
615
|
-
description: "Enforces function calls made inside `useState` to be wrapped in an `initializer function`.",
|
|
616
|
-
[Symbol.for("rule_features")]: RULE_FEATURES6
|
|
617
|
-
},
|
|
618
|
-
messages: {
|
|
619
|
-
preferUseStateLazyInitialization: "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'."
|
|
620
|
-
},
|
|
621
|
-
schema: []
|
|
622
|
-
},
|
|
623
|
-
name: RULE_NAME6,
|
|
624
|
-
create: create6,
|
|
625
|
-
defaultOptions: []
|
|
626
|
-
});
|
|
627
|
-
function create6(context) {
|
|
628
|
-
const alias = getSettingsFromContext(context).additionalHooks.useState ?? [];
|
|
629
|
-
const isUseStateCall = ER7.isReactHookCallWithNameAlias(context, "useState", alias);
|
|
630
|
-
return {
|
|
631
|
-
CallExpression(node) {
|
|
632
|
-
if (!ER7.isReactHookCall(node)) {
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
if (!isUseStateCall(node)) {
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
const [useStateInput] = node.arguments;
|
|
639
|
-
if (useStateInput == null) {
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
for (const expr of AST.getNestedNewExpressions(useStateInput)) {
|
|
643
|
-
if (!("name" in expr.callee)) continue;
|
|
644
|
-
if (ALLOW_LIST.includes(expr.callee.name)) continue;
|
|
645
|
-
if (AST.findParentNode(expr, (n) => ER7.isUseCall(context, n)) != null) continue;
|
|
646
|
-
context.report({
|
|
647
|
-
messageId: "preferUseStateLazyInitialization",
|
|
648
|
-
node: expr
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
for (const expr of AST.getNestedCallExpressions(useStateInput)) {
|
|
652
|
-
if (!("name" in expr.callee)) continue;
|
|
653
|
-
if (ER7.isReactHookName(expr.callee.name)) continue;
|
|
654
|
-
if (ALLOW_LIST.includes(expr.callee.name)) continue;
|
|
655
|
-
if (AST.findParentNode(expr, (n) => ER7.isUseCall(context, n)) != null) continue;
|
|
656
|
-
context.report({
|
|
657
|
-
messageId: "preferUseStateLazyInitialization",
|
|
658
|
-
node: expr
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
};
|
|
75
|
+
if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
|
|
76
|
+
const functionEntries = [];
|
|
77
|
+
const setupFnRef = { current: null };
|
|
78
|
+
const setupFnIds = [];
|
|
79
|
+
const trackedFnCalls = [];
|
|
80
|
+
const setStateCallsByFn = /* @__PURE__ */ new WeakMap();
|
|
81
|
+
const setStateInEffectArg = /* @__PURE__ */ new WeakMap();
|
|
82
|
+
const setStateInEffectSetup = /* @__PURE__ */ new Map();
|
|
83
|
+
const setStateInHookCallbacks = /* @__PURE__ */ new WeakMap();
|
|
84
|
+
const getText = (n) => context.sourceCode.getText(n);
|
|
85
|
+
const onSetupFunctionEnter = (node) => {
|
|
86
|
+
setupFnRef.current = node;
|
|
87
|
+
};
|
|
88
|
+
const onSetupFunctionExit = (node) => {
|
|
89
|
+
if (setupFnRef.current === node) setupFnRef.current = null;
|
|
90
|
+
};
|
|
91
|
+
function isFunctionOfUseEffectSetup(node) {
|
|
92
|
+
return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && ER.isUseEffectLikeCall(node.parent);
|
|
93
|
+
}
|
|
94
|
+
function getCallName(node) {
|
|
95
|
+
if (node.type === AST_NODE_TYPES.CallExpression) return AST.toStringFormat(node.callee, getText);
|
|
96
|
+
return AST.toStringFormat(node, getText);
|
|
97
|
+
}
|
|
98
|
+
function getCallKind(node) {
|
|
99
|
+
return match(node).when(ER.isUseStateCall, () => "useState").when(ER.isUseEffectLikeCall, () => "useEffect").when(isSetStateCall, () => "setState").when(AST.isThenCall, () => "then").otherwise(() => "other");
|
|
100
|
+
}
|
|
101
|
+
function getFunctionKind(node) {
|
|
102
|
+
const parent = AST.findParentNode(node, not(AST.isTypeExpression)) ?? node.parent;
|
|
103
|
+
switch (true) {
|
|
104
|
+
case node.async:
|
|
105
|
+
case parent.type === AST_NODE_TYPES.CallExpression && AST.isThenCall(parent): return "deferred";
|
|
106
|
+
case node.type !== AST_NODE_TYPES.FunctionDeclaration && parent.type === AST_NODE_TYPES.CallExpression && parent.callee === node: return "immediate";
|
|
107
|
+
case isFunctionOfUseEffectSetup(node): return "setup";
|
|
108
|
+
default: return "other";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function isIdFromUseStateCall(topLevelId, at) {
|
|
112
|
+
const variable = VAR.findVariable(topLevelId, context.sourceCode.getScope(topLevelId));
|
|
113
|
+
const variableNode = VAR.getVariableDefinitionNode(variable, 0);
|
|
114
|
+
if (variableNode == null) return false;
|
|
115
|
+
if (variableNode.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
116
|
+
if (!ER.isUseStateCall(variableNode)) return false;
|
|
117
|
+
const variableNodeParent = variableNode.parent;
|
|
118
|
+
if (!("id" in variableNodeParent) || variableNodeParent.id?.type !== AST_NODE_TYPES.ArrayPattern) return true;
|
|
119
|
+
return variableNodeParent.id.elements.findIndex((e) => e?.type === AST_NODE_TYPES.Identifier && e.name === topLevelId.name) === at;
|
|
120
|
+
}
|
|
121
|
+
function isSetStateCall(node) {
|
|
122
|
+
switch (node.callee.type) {
|
|
123
|
+
case AST_NODE_TYPES.CallExpression: {
|
|
124
|
+
const { callee } = node.callee;
|
|
125
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) return false;
|
|
126
|
+
if (!("name" in callee.object)) return false;
|
|
127
|
+
const isAt = callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "at";
|
|
128
|
+
const [index] = node.callee.arguments;
|
|
129
|
+
if (!isAt || index == null) return false;
|
|
130
|
+
const indexScope = context.sourceCode.getScope(node);
|
|
131
|
+
return getStaticValue(index, indexScope)?.value === 1 && isIdFromUseStateCall(callee.object);
|
|
132
|
+
}
|
|
133
|
+
case AST_NODE_TYPES.Identifier: return isIdFromUseStateCall(node.callee, 1);
|
|
134
|
+
case AST_NODE_TYPES.MemberExpression: {
|
|
135
|
+
if (!("name" in node.callee.object)) return false;
|
|
136
|
+
const property = node.callee.property;
|
|
137
|
+
const propertyScope = context.sourceCode.getScope(node);
|
|
138
|
+
return getStaticValue(property, propertyScope)?.value === 1 && isIdFromUseStateCall(node.callee.object, 1);
|
|
139
|
+
}
|
|
140
|
+
default: return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
":function"(node) {
|
|
145
|
+
const kind = getFunctionKind(node);
|
|
146
|
+
functionEntries.push({
|
|
147
|
+
kind,
|
|
148
|
+
node
|
|
149
|
+
});
|
|
150
|
+
if (kind === "setup") onSetupFunctionEnter(node);
|
|
151
|
+
},
|
|
152
|
+
":function:exit"(node) {
|
|
153
|
+
const { kind } = functionEntries.at(-1) ?? {};
|
|
154
|
+
if (kind === "setup") onSetupFunctionExit(node);
|
|
155
|
+
functionEntries.pop();
|
|
156
|
+
},
|
|
157
|
+
CallExpression(node) {
|
|
158
|
+
const setupFunction = setupFnRef.current;
|
|
159
|
+
const pEntry = functionEntries.at(-1);
|
|
160
|
+
if (pEntry == null || pEntry.node.async) return;
|
|
161
|
+
match(getCallKind(node)).with("setState", () => {
|
|
162
|
+
switch (true) {
|
|
163
|
+
case pEntry.kind === "deferred":
|
|
164
|
+
case pEntry.node.async: break;
|
|
165
|
+
case pEntry.node === setupFunction:
|
|
166
|
+
case pEntry.kind === "immediate" && AST.findParentNode(pEntry.node, AST.isFunction) === setupFunction:
|
|
167
|
+
context.report({
|
|
168
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
169
|
+
node,
|
|
170
|
+
data: { name: context.sourceCode.getText(node.callee) }
|
|
171
|
+
});
|
|
172
|
+
return;
|
|
173
|
+
default: {
|
|
174
|
+
const init = AST.findParentNode(node, isVariableDeclaratorFromHookCall)?.init;
|
|
175
|
+
if (init == null) getOrElseUpdate(setStateCallsByFn, pEntry.node, () => []).push(node);
|
|
176
|
+
else getOrElseUpdate(setStateInHookCallbacks, init, () => []).push(node);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}).with("useEffect", () => {
|
|
180
|
+
if (AST.isFunction(node.arguments.at(0))) return;
|
|
181
|
+
setupFnIds.push(...AST.getNestedIdentifiers(node));
|
|
182
|
+
}).with("other", () => {
|
|
183
|
+
if (pEntry.node !== setupFunction) return;
|
|
184
|
+
trackedFnCalls.push(node);
|
|
185
|
+
}).otherwise(constVoid);
|
|
186
|
+
},
|
|
187
|
+
Identifier(node) {
|
|
188
|
+
if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) return;
|
|
189
|
+
if (!isIdFromUseStateCall(node, 1)) return;
|
|
190
|
+
switch (node.parent.type) {
|
|
191
|
+
case AST_NODE_TYPES.ArrowFunctionExpression: {
|
|
192
|
+
const parent = node.parent.parent;
|
|
193
|
+
if (parent.type !== AST_NODE_TYPES.CallExpression) break;
|
|
194
|
+
if (!ER.isUseMemoCall(parent)) break;
|
|
195
|
+
const init = AST.findParentNode(parent, isVariableDeclaratorFromHookCall)?.init;
|
|
196
|
+
if (init != null) getOrElseUpdate(setStateInEffectArg, init, () => []).push(node);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case AST_NODE_TYPES.CallExpression:
|
|
200
|
+
if (node !== node.parent.arguments.at(0)) break;
|
|
201
|
+
if (ER.isUseCallbackCall(node.parent)) {
|
|
202
|
+
const init = AST.findParentNode(node.parent, isVariableDeclaratorFromHookCall)?.init;
|
|
203
|
+
if (init != null) getOrElseUpdate(setStateInEffectArg, init, () => []).push(node);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
if (ER.isUseEffectLikeCall(node.parent)) getOrElseUpdate(setStateInEffectSetup, node.parent, () => []).push(node);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
"Program:exit"() {
|
|
210
|
+
const getSetStateCalls = (id, initialScope) => {
|
|
211
|
+
const node = VAR.getVariableDefinitionNode(VAR.findVariable(id, initialScope), 0);
|
|
212
|
+
switch (node?.type) {
|
|
213
|
+
case AST_NODE_TYPES.ArrowFunctionExpression:
|
|
214
|
+
case AST_NODE_TYPES.FunctionDeclaration:
|
|
215
|
+
case AST_NODE_TYPES.FunctionExpression: return setStateCallsByFn.get(node) ?? [];
|
|
216
|
+
case AST_NODE_TYPES.CallExpression: return setStateInHookCallbacks.get(node) ?? setStateInEffectArg.get(node) ?? [];
|
|
217
|
+
}
|
|
218
|
+
return [];
|
|
219
|
+
};
|
|
220
|
+
for (const [, calls] of setStateInEffectSetup) for (const call of calls) context.report({
|
|
221
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
222
|
+
node: call,
|
|
223
|
+
data: { name: call.name }
|
|
224
|
+
});
|
|
225
|
+
for (const { callee } of trackedFnCalls) {
|
|
226
|
+
if (!("name" in callee)) continue;
|
|
227
|
+
const { name: name$2 } = callee;
|
|
228
|
+
const setStateCalls = getSetStateCalls(name$2, context.sourceCode.getScope(callee));
|
|
229
|
+
for (const setStateCall of setStateCalls) context.report({
|
|
230
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
231
|
+
node: setStateCall,
|
|
232
|
+
data: { name: getCallName(setStateCall) }
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
for (const id of setupFnIds) {
|
|
236
|
+
const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
|
|
237
|
+
for (const setStateCall of setStateCalls) context.report({
|
|
238
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
239
|
+
node: setStateCall,
|
|
240
|
+
data: { name: getCallName(setStateCall) }
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
663
245
|
}
|
|
664
246
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
"no-direct-set-state-in-use-layout-effect": no_direct_set_state_in_use_layout_effect_default,
|
|
674
|
-
"no-unnecessary-use-callback": no_unnecessary_use_callback_default,
|
|
675
|
-
"no-unnecessary-use-memo": no_unnecessary_use_memo_default,
|
|
676
|
-
"no-unnecessary-use-prefix": no_unnecessary_use_prefix_default,
|
|
677
|
-
"prefer-use-state-lazy-initialization": prefer_use_state_lazy_initialization_default
|
|
678
|
-
}
|
|
247
|
+
//#endregion
|
|
248
|
+
//#region src/plugin.ts
|
|
249
|
+
const plugin = {
|
|
250
|
+
meta: {
|
|
251
|
+
name,
|
|
252
|
+
version
|
|
253
|
+
},
|
|
254
|
+
rules: { "no-direct-set-state-in-use-effect": no_direct_set_state_in_use_effect_default }
|
|
679
255
|
};
|
|
680
256
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
function makeLegacyConfig({ rules: rules2 }) {
|
|
691
|
-
return {
|
|
692
|
-
plugins: ["react-hooks-extra"],
|
|
693
|
-
rules: rules2
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
var index_default = {
|
|
697
|
-
...plugin,
|
|
698
|
-
configs: {
|
|
699
|
-
["recommended"]: makeConfig(recommended_exports),
|
|
700
|
-
["recommended-legacy"]: makeLegacyConfig(recommended_exports)
|
|
701
|
-
}
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region src/index.ts
|
|
259
|
+
const { toFlatConfig } = getConfigAdapters("react-hooks-extra", plugin);
|
|
260
|
+
var src_default = {
|
|
261
|
+
...plugin,
|
|
262
|
+
configs: { ["recommended"]: toFlatConfig(recommended_exports) }
|
|
702
263
|
};
|
|
703
264
|
|
|
704
|
-
|
|
265
|
+
//#endregion
|
|
266
|
+
export { src_default as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-hooks-extra",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.200",
|
|
4
4
|
"description": "ESLint React's ESLint plugin for React Hooks related rules.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -36,46 +36,37 @@
|
|
|
36
36
|
"./package.json"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@typescript-eslint/scope-manager": "^8.
|
|
40
|
-
"@typescript-eslint/type-utils": "^8.
|
|
41
|
-
"@typescript-eslint/types": "^8.
|
|
42
|
-
"@typescript-eslint/utils": "^8.
|
|
39
|
+
"@typescript-eslint/scope-manager": "^8.44.1",
|
|
40
|
+
"@typescript-eslint/type-utils": "^8.44.1",
|
|
41
|
+
"@typescript-eslint/types": "^8.44.1",
|
|
42
|
+
"@typescript-eslint/utils": "^8.44.1",
|
|
43
43
|
"string-ts": "^2.2.1",
|
|
44
|
-
"ts-pattern": "^5.
|
|
45
|
-
"@eslint-react/
|
|
46
|
-
"@eslint-react/
|
|
47
|
-
"@eslint-react/
|
|
48
|
-
"@eslint-react/
|
|
49
|
-
"@eslint-react/
|
|
50
|
-
"@eslint-react/
|
|
44
|
+
"ts-pattern": "^5.8.0",
|
|
45
|
+
"@eslint-react/ast": "2.0.0-next.200",
|
|
46
|
+
"@eslint-react/core": "2.0.0-next.200",
|
|
47
|
+
"@eslint-react/eff": "2.0.0-next.200",
|
|
48
|
+
"@eslint-react/kit": "2.0.0-next.200",
|
|
49
|
+
"@eslint-react/shared": "2.0.0-next.200",
|
|
50
|
+
"@eslint-react/var": "2.0.0-next.200"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@types/react": "^19.1.
|
|
54
|
-
"@types/react-dom": "^19.1.
|
|
55
|
-
"
|
|
53
|
+
"@types/react": "^19.1.13",
|
|
54
|
+
"@types/react-dom": "^19.1.9",
|
|
55
|
+
"tsdown": "^0.15.4",
|
|
56
56
|
"@local/configs": "0.0.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"eslint": "^9.
|
|
60
|
-
"typescript": "^
|
|
61
|
-
},
|
|
62
|
-
"peerDependenciesMeta": {
|
|
63
|
-
"eslint": {
|
|
64
|
-
"optional": false
|
|
65
|
-
},
|
|
66
|
-
"typescript": {
|
|
67
|
-
"optional": true
|
|
68
|
-
}
|
|
59
|
+
"eslint": "^9.36.0",
|
|
60
|
+
"typescript": "^5.9.2"
|
|
69
61
|
},
|
|
70
62
|
"engines": {
|
|
71
|
-
"
|
|
72
|
-
"node": ">=20.19.0"
|
|
63
|
+
"node": ">=20.0.0"
|
|
73
64
|
},
|
|
74
65
|
"publishConfig": {
|
|
75
66
|
"access": "public"
|
|
76
67
|
},
|
|
77
68
|
"scripts": {
|
|
78
|
-
"build": "
|
|
69
|
+
"build": "tsdown",
|
|
79
70
|
"lint:publish": "publint",
|
|
80
71
|
"lint:ts": "tsc --noEmit"
|
|
81
72
|
}
|