@krymskyimaksym/eslint-plugin-react-api-client 0.1.0 → 0.2.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 +24 -2
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +56 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +56 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @krymskyimaksym/eslint-plugin-react-api-client
|
|
2
2
|
|
|
3
|
-
ESLint-правила для [`@krymskyimaksym/react-api-client`](
|
|
3
|
+
ESLint-правила для [`@krymskyimaksym/react-api-client`](https://www.npmjs.com/package/@krymskyimaksym/react-api-client) (требуется `^2.0.0`).
|
|
4
4
|
|
|
5
5
|
## Установка
|
|
6
6
|
|
|
@@ -29,7 +29,15 @@ module.exports = {
|
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
## Правила
|
|
32
|
+
## Правила (recommended)
|
|
33
|
+
|
|
34
|
+
| Rule | Severity | Autofix |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `no-await-mutate` | error | ✓ |
|
|
37
|
+
| `no-non-serializable-params` | error | — |
|
|
38
|
+
| `require-query-key-when-endpoint-is-fn` | warn | — |
|
|
39
|
+
|
|
40
|
+
## Описание
|
|
33
41
|
|
|
34
42
|
### `no-await-mutate` (error, autofix)
|
|
35
43
|
|
|
@@ -65,6 +73,20 @@ userApi.useFetch({ id: 1 }, { queryKey: ['user', 1] });
|
|
|
65
73
|
возвращает одну и ту же строку для одних и тех же params. Явный
|
|
66
74
|
`queryKey` снимает риск.
|
|
67
75
|
|
|
76
|
+
### `no-non-serializable-params` (error)
|
|
77
|
+
|
|
78
|
+
Запрещает функции и `Symbol` в объекте `params`. Они сломают
|
|
79
|
+
`hashQueryKey` в рантайме (throw при первом mount):
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// ❌
|
|
83
|
+
api.useFetch({ id: 1, cb: () => 1 });
|
|
84
|
+
api.useFetch({ tag: Symbol('x') });
|
|
85
|
+
|
|
86
|
+
// ✅
|
|
87
|
+
api.useFetch({ id: 1 });
|
|
88
|
+
```
|
|
89
|
+
|
|
68
90
|
## License
|
|
69
91
|
|
|
70
92
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -2,6 +2,7 @@ import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts
|
|
|
2
2
|
|
|
3
3
|
declare const rules: {
|
|
4
4
|
'no-await-mutate': _typescript_eslint_utils_ts_eslint.RuleModule<"avoid", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
5
|
+
'no-non-serializable-params': _typescript_eslint_utils_ts_eslint.RuleModule<"noFunction" | "noSymbol", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
5
6
|
'require-query-key-when-endpoint-is-fn': _typescript_eslint_utils_ts_eslint.RuleModule<"missing", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
6
7
|
};
|
|
7
8
|
declare const configs: {
|
|
@@ -9,6 +10,7 @@ declare const configs: {
|
|
|
9
10
|
plugins: string[];
|
|
10
11
|
rules: {
|
|
11
12
|
'@krymskyimaksym/react-api-client/no-await-mutate': string;
|
|
13
|
+
'@krymskyimaksym/react-api-client/no-non-serializable-params': string;
|
|
12
14
|
'@krymskyimaksym/react-api-client/require-query-key-when-endpoint-is-fn': string;
|
|
13
15
|
};
|
|
14
16
|
};
|
|
@@ -16,6 +18,7 @@ declare const configs: {
|
|
|
16
18
|
declare const _default: {
|
|
17
19
|
rules: {
|
|
18
20
|
'no-await-mutate': _typescript_eslint_utils_ts_eslint.RuleModule<"avoid", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
21
|
+
'no-non-serializable-params': _typescript_eslint_utils_ts_eslint.RuleModule<"noFunction" | "noSymbol", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
19
22
|
'require-query-key-when-endpoint-is-fn': _typescript_eslint_utils_ts_eslint.RuleModule<"missing", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
20
23
|
};
|
|
21
24
|
configs: {
|
|
@@ -23,6 +26,7 @@ declare const _default: {
|
|
|
23
26
|
plugins: string[];
|
|
24
27
|
rules: {
|
|
25
28
|
'@krymskyimaksym/react-api-client/no-await-mutate': string;
|
|
29
|
+
'@krymskyimaksym/react-api-client/no-non-serializable-params': string;
|
|
26
30
|
'@krymskyimaksym/react-api-client/require-query-key-when-endpoint-is-fn': string;
|
|
27
31
|
};
|
|
28
32
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts
|
|
|
2
2
|
|
|
3
3
|
declare const rules: {
|
|
4
4
|
'no-await-mutate': _typescript_eslint_utils_ts_eslint.RuleModule<"avoid", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
5
|
+
'no-non-serializable-params': _typescript_eslint_utils_ts_eslint.RuleModule<"noFunction" | "noSymbol", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
5
6
|
'require-query-key-when-endpoint-is-fn': _typescript_eslint_utils_ts_eslint.RuleModule<"missing", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
6
7
|
};
|
|
7
8
|
declare const configs: {
|
|
@@ -9,6 +10,7 @@ declare const configs: {
|
|
|
9
10
|
plugins: string[];
|
|
10
11
|
rules: {
|
|
11
12
|
'@krymskyimaksym/react-api-client/no-await-mutate': string;
|
|
13
|
+
'@krymskyimaksym/react-api-client/no-non-serializable-params': string;
|
|
12
14
|
'@krymskyimaksym/react-api-client/require-query-key-when-endpoint-is-fn': string;
|
|
13
15
|
};
|
|
14
16
|
};
|
|
@@ -16,6 +18,7 @@ declare const configs: {
|
|
|
16
18
|
declare const _default: {
|
|
17
19
|
rules: {
|
|
18
20
|
'no-await-mutate': _typescript_eslint_utils_ts_eslint.RuleModule<"avoid", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
21
|
+
'no-non-serializable-params': _typescript_eslint_utils_ts_eslint.RuleModule<"noFunction" | "noSymbol", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
19
22
|
'require-query-key-when-endpoint-is-fn': _typescript_eslint_utils_ts_eslint.RuleModule<"missing", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
20
23
|
};
|
|
21
24
|
configs: {
|
|
@@ -23,6 +26,7 @@ declare const _default: {
|
|
|
23
26
|
plugins: string[];
|
|
24
27
|
rules: {
|
|
25
28
|
'@krymskyimaksym/react-api-client/no-await-mutate': string;
|
|
29
|
+
'@krymskyimaksym/react-api-client/no-non-serializable-params': string;
|
|
26
30
|
'@krymskyimaksym/react-api-client/require-query-key-when-endpoint-is-fn': string;
|
|
27
31
|
};
|
|
28
32
|
};
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var noAwaitMutate = createRule({
|
|
|
14
14
|
type: "problem",
|
|
15
15
|
docs: {
|
|
16
16
|
description: "mutate() returns void; await it has no effect. Use mutateAsync for awaitable mutations.",
|
|
17
|
-
recommended: "
|
|
17
|
+
recommended: "recommended"
|
|
18
18
|
},
|
|
19
19
|
fixable: "code",
|
|
20
20
|
schema: [],
|
|
@@ -46,13 +46,64 @@ var noAwaitMutate = createRule({
|
|
|
46
46
|
var createRule2 = utils.ESLintUtils.RuleCreator(
|
|
47
47
|
(name) => `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`
|
|
48
48
|
);
|
|
49
|
-
var
|
|
49
|
+
var noNonSerializableParams = createRule2({
|
|
50
|
+
name: "no-non-serializable-params",
|
|
51
|
+
meta: {
|
|
52
|
+
type: "problem",
|
|
53
|
+
docs: {
|
|
54
|
+
description: "Params for useFetch/usePaginate/useQuery must be serializable. Functions and Symbol break hashQueryKey at runtime.",
|
|
55
|
+
recommended: "recommended"
|
|
56
|
+
},
|
|
57
|
+
schema: [],
|
|
58
|
+
messages: {
|
|
59
|
+
noFunction: "\u041D\u0435\u0441\u0435\u0440\u0438\u0430\u043B\u0438\u0437\u0443\u0435\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0432 params (\u0444\u0443\u043D\u043A\u0446\u0438\u044F): \u0441\u043B\u043E\u043C\u0430\u0435\u0442 hashQueryKey \u0432 \u0440\u0430\u043D\u0442\u0430\u0439\u043C\u0435. \u0412\u044B\u043D\u0435\u0441\u0438 callback \u043D\u0430\u0440\u0443\u0436\u0443 \u0445\u0443\u043A\u0430.",
|
|
60
|
+
noSymbol: "\u041D\u0435\u0441\u0435\u0440\u0438\u0430\u043B\u0438\u0437\u0443\u0435\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0432 params (Symbol): \u0441\u043B\u043E\u043C\u0430\u0435\u0442 hashQueryKey \u0432 \u0440\u0430\u043D\u0442\u0430\u0439\u043C\u0435."
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
defaultOptions: [],
|
|
64
|
+
create(context) {
|
|
65
|
+
const HOOK_NAMES = /* @__PURE__ */ new Set(["useFetch", "usePaginate", "useQuery"]);
|
|
66
|
+
function checkObjectExpression(obj) {
|
|
67
|
+
for (const prop of obj.properties) {
|
|
68
|
+
if (prop.type !== "Property") continue;
|
|
69
|
+
const v = prop.value;
|
|
70
|
+
if (v.type === "ArrowFunctionExpression" || v.type === "FunctionExpression") {
|
|
71
|
+
context.report({ node: v, messageId: "noFunction" });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (v.type === "CallExpression" && v.callee.type === "Identifier" && v.callee.name === "Symbol") {
|
|
75
|
+
context.report({ node: v, messageId: "noSymbol" });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
CallExpression(node) {
|
|
81
|
+
if (node.callee.type !== "MemberExpression") {
|
|
82
|
+
if (node.callee.type === "Identifier" && node.callee.name === "useQuery") {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const prop = node.callee.property;
|
|
88
|
+
if (prop.type !== "Identifier" || !HOOK_NAMES.has(prop.name)) return;
|
|
89
|
+
const paramsArg = node.arguments[0];
|
|
90
|
+
if (!paramsArg) return;
|
|
91
|
+
if (paramsArg.type !== "ObjectExpression") return;
|
|
92
|
+
checkObjectExpression(paramsArg);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
var createRule3 = utils.ESLintUtils.RuleCreator(
|
|
98
|
+
(name) => `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`
|
|
99
|
+
);
|
|
100
|
+
var requireQueryKeyWhenEndpointIsFn = createRule3({
|
|
50
101
|
name: "require-query-key-when-endpoint-is-fn",
|
|
51
102
|
meta: {
|
|
52
103
|
type: "suggestion",
|
|
53
104
|
docs: {
|
|
54
105
|
description: "When apiClient is created from an endpoint function, useFetch should specify explicit queryKey.",
|
|
55
|
-
recommended: "
|
|
106
|
+
recommended: "recommended"
|
|
56
107
|
},
|
|
57
108
|
schema: [],
|
|
58
109
|
messages: {
|
|
@@ -105,6 +156,7 @@ var requireQueryKeyWhenEndpointIsFn = createRule2({
|
|
|
105
156
|
// src/index.ts
|
|
106
157
|
var rules = {
|
|
107
158
|
"no-await-mutate": noAwaitMutate,
|
|
159
|
+
"no-non-serializable-params": noNonSerializableParams,
|
|
108
160
|
"require-query-key-when-endpoint-is-fn": requireQueryKeyWhenEndpointIsFn
|
|
109
161
|
};
|
|
110
162
|
var configs = {
|
|
@@ -112,6 +164,7 @@ var configs = {
|
|
|
112
164
|
plugins: ["@krymskyimaksym/react-api-client"],
|
|
113
165
|
rules: {
|
|
114
166
|
"@krymskyimaksym/react-api-client/no-await-mutate": "error",
|
|
167
|
+
"@krymskyimaksym/react-api-client/no-non-serializable-params": "error",
|
|
115
168
|
"@krymskyimaksym/react-api-client/require-query-key-when-endpoint-is-fn": "warn"
|
|
116
169
|
}
|
|
117
170
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rules/no-await-mutate.ts","../src/rules/require-query-key-when-endpoint-is-fn.ts","../src/index.ts"],"names":["ESLintUtils","createRule"],"mappings":"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","file":"index.js","sourcesContent":["import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`,\n);\n\n/**\n * Запрещает `await x.mutate(...)` — `mutate` возвращает `void`, а не\n * `Promise`. Для последовательной логики или try/catch используй\n * `mutateAsync`.\n *\n * Эвристика: `await <expr>.mutate(...)`.\n */\nexport const noAwaitMutate = createRule({\n name: 'no-await-mutate',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'mutate() returns void; await it has no effect. Use mutateAsync for awaitable mutations.',\n recommended: 'error',\n },\n fixable: 'code',\n schema: [],\n messages: {\n avoid:\n '`mutate` возвращает void — await не сработает. Используй `mutateAsync` для await/try-catch.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n AwaitExpression(node: TSESTree.AwaitExpression) {\n const arg = node.argument;\n if (arg.type !== 'CallExpression') return;\n const callee = arg.callee;\n if (callee.type !== 'MemberExpression') return;\n const prop = callee.property;\n if (prop.type !== 'Identifier' || prop.name !== 'mutate') return;\n\n context.report({\n node,\n messageId: 'avoid',\n fix(fixer) {\n return fixer.replaceText(prop, 'mutateAsync');\n },\n });\n },\n };\n },\n});\n","import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`,\n);\n\n/**\n * Если `apiClient(fn, ...)` создан с endpoint-функцией, и где-то\n * вызывается `<api>.useFetch(params)` без `queryKey` в options —\n * предупредить. По умолчанию ключ генерится из endpoint+params, но при\n * endpoint-функции стабильность ключа зависит от того, что функция\n * возвращает одну и ту же строку для одних и тех же params. Явный\n * `queryKey` снимает риск.\n *\n * Эвристика статическая: ищем `const xxxApi = apiClient(SomethingFn, ...)`,\n * запоминаем имя, потом ругаемся на `xxxApi.useFetch(...)` без queryKey\n * в options-объекте.\n */\nexport const requireQueryKeyWhenEndpointIsFn = createRule({\n name: 'require-query-key-when-endpoint-is-fn',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'When apiClient is created from an endpoint function, useFetch should specify explicit queryKey.',\n recommended: 'warn',\n },\n schema: [],\n messages: {\n missing:\n 'apiClient с endpoint-функцией: укажи `queryKey` явно, чтобы ключ кэша был стабильным.',\n },\n },\n defaultOptions: [],\n create(context) {\n const apisWithFnEndpoint = new Set<string>();\n\n return {\n // const xApi = apiClient(fn, ...)\n VariableDeclarator(node: TSESTree.VariableDeclarator) {\n if (node.id.type !== 'Identifier') return;\n if (!node.init || node.init.type !== 'CallExpression') return;\n const call = node.init;\n if (\n call.callee.type !== 'Identifier' ||\n (call.callee.name !== 'apiClient' && call.callee.name !== 'apiPaginate')\n )\n return;\n const firstArg = call.arguments[0];\n if (!firstArg) return;\n if (\n firstArg.type === 'ArrowFunctionExpression' ||\n firstArg.type === 'FunctionExpression'\n ) {\n apisWithFnEndpoint.add(node.id.name);\n }\n },\n\n // xApi.useFetch(params, options?)\n CallExpression(node: TSESTree.CallExpression) {\n if (node.callee.type !== 'MemberExpression') return;\n const obj = node.callee.object;\n const prop = node.callee.property;\n if (obj.type !== 'Identifier') return;\n if (!apisWithFnEndpoint.has(obj.name)) return;\n if (prop.type !== 'Identifier') return;\n if (prop.name !== 'useFetch' && prop.name !== 'usePaginate') return;\n\n const optionsArg = node.arguments[1];\n if (!optionsArg) {\n context.report({ node, messageId: 'missing' });\n return;\n }\n if (optionsArg.type !== 'ObjectExpression') return; // не статически анализируем\n const hasQueryKey = optionsArg.properties.some(\n p =>\n p.type === 'Property' &&\n p.key.type === 'Identifier' &&\n p.key.name === 'queryKey',\n );\n if (!hasQueryKey) {\n context.report({ node, messageId: 'missing' });\n }\n },\n };\n },\n});\n","import { noAwaitMutate } from './rules/no-await-mutate';\nimport { requireQueryKeyWhenEndpointIsFn } from './rules/require-query-key-when-endpoint-is-fn';\n\nexport const rules = {\n 'no-await-mutate': noAwaitMutate,\n 'require-query-key-when-endpoint-is-fn': requireQueryKeyWhenEndpointIsFn,\n};\n\nexport const configs = {\n recommended: {\n plugins: ['@krymskyimaksym/react-api-client'],\n rules: {\n '@krymskyimaksym/react-api-client/no-await-mutate': 'error',\n '@krymskyimaksym/react-api-client/require-query-key-when-endpoint-is-fn':\n 'warn',\n },\n },\n};\n\nexport default { rules, configs };\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/rules/no-await-mutate.ts","../src/rules/no-non-serializable-params.ts","../src/rules/require-query-key-when-endpoint-is-fn.ts","../src/index.ts"],"names":["ESLintUtils","createRule"],"mappings":"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","file":"index.js","sourcesContent":["import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`,\n);\n\n/**\n * Запрещает `await x.mutate(...)` — `mutate` возвращает `void`, а не\n * `Promise`. Для последовательной логики или try/catch используй\n * `mutateAsync`.\n *\n * Эвристика: `await <expr>.mutate(...)`.\n */\nexport const noAwaitMutate = createRule({\n name: 'no-await-mutate',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'mutate() returns void; await it has no effect. Use mutateAsync for awaitable mutations.',\n recommended: 'recommended',\n },\n fixable: 'code',\n schema: [],\n messages: {\n avoid:\n '`mutate` возвращает void — await не сработает. Используй `mutateAsync` для await/try-catch.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n AwaitExpression(node: TSESTree.AwaitExpression) {\n const arg = node.argument;\n if (arg.type !== 'CallExpression') return;\n const callee = arg.callee;\n if (callee.type !== 'MemberExpression') return;\n const prop = callee.property;\n if (prop.type !== 'Identifier' || prop.name !== 'mutate') return;\n\n context.report({\n node,\n messageId: 'avoid',\n fix(fixer) {\n return fixer.replaceText(prop, 'mutateAsync');\n },\n });\n },\n };\n },\n});\n","import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`,\n);\n\n/**\n * Запрещает несериализуемые значения в `params` для `useFetch` /\n * `usePaginate` / `useQuery`. Функции и Symbol сломают `hashQueryKey`\n * (он `throw`-ает на них), а это приведёт к рантайм-ошибке при mount'е.\n *\n * Ловим статически:\n * - стрелочные/функциональные выражения как значения свойств;\n * - вызовы `Symbol(...)` как значения свойств.\n *\n * Эвристика — без типов; ловит самые частые случаи.\n */\nexport const noNonSerializableParams = createRule({\n name: 'no-non-serializable-params',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Params for useFetch/usePaginate/useQuery must be serializable. Functions and Symbol break hashQueryKey at runtime.',\n recommended: 'recommended',\n },\n schema: [],\n messages: {\n noFunction:\n 'Несериализуемое значение в params (функция): сломает hashQueryKey в рантайме. Вынеси callback наружу хука.',\n noSymbol:\n 'Несериализуемое значение в params (Symbol): сломает hashQueryKey в рантайме.',\n },\n },\n defaultOptions: [],\n create(context) {\n const HOOK_NAMES = new Set(['useFetch', 'usePaginate', 'useQuery']);\n\n function checkObjectExpression(obj: TSESTree.ObjectExpression) {\n for (const prop of obj.properties) {\n if (prop.type !== 'Property') continue;\n const v = prop.value;\n if (\n v.type === 'ArrowFunctionExpression' ||\n v.type === 'FunctionExpression'\n ) {\n context.report({ node: v, messageId: 'noFunction' });\n continue;\n }\n if (\n v.type === 'CallExpression' &&\n v.callee.type === 'Identifier' &&\n v.callee.name === 'Symbol'\n ) {\n context.report({ node: v, messageId: 'noSymbol' });\n }\n }\n }\n\n return {\n CallExpression(node: TSESTree.CallExpression) {\n if (node.callee.type !== 'MemberExpression') {\n // useQuery(key, fn, opts) — первый аргумент это key,\n // не params; пропускаем.\n if (\n node.callee.type === 'Identifier' &&\n node.callee.name === 'useQuery'\n ) {\n return;\n }\n return;\n }\n const prop = node.callee.property;\n if (prop.type !== 'Identifier' || !HOOK_NAMES.has(prop.name)) return;\n const paramsArg = node.arguments[0];\n if (!paramsArg) return;\n if (paramsArg.type !== 'ObjectExpression') return;\n checkObjectExpression(paramsArg);\n },\n };\n },\n});\n","import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`,\n);\n\n/**\n * Если `apiClient(fn, ...)` создан с endpoint-функцией, и где-то\n * вызывается `<api>.useFetch(params)` без `queryKey` в options —\n * предупредить. По умолчанию ключ генерится из endpoint+params, но при\n * endpoint-функции стабильность ключа зависит от того, что функция\n * возвращает одну и ту же строку для одних и тех же params. Явный\n * `queryKey` снимает риск.\n *\n * Эвристика статическая: ищем `const xxxApi = apiClient(SomethingFn, ...)`,\n * запоминаем имя, потом ругаемся на `xxxApi.useFetch(...)` без queryKey\n * в options-объекте.\n */\nexport const requireQueryKeyWhenEndpointIsFn = createRule({\n name: 'require-query-key-when-endpoint-is-fn',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'When apiClient is created from an endpoint function, useFetch should specify explicit queryKey.',\n recommended: 'recommended',\n },\n schema: [],\n messages: {\n missing:\n 'apiClient с endpoint-функцией: укажи `queryKey` явно, чтобы ключ кэша был стабильным.',\n },\n },\n defaultOptions: [],\n create(context) {\n const apisWithFnEndpoint = new Set<string>();\n\n return {\n // const xApi = apiClient(fn, ...)\n VariableDeclarator(node: TSESTree.VariableDeclarator) {\n if (node.id.type !== 'Identifier') return;\n if (!node.init || node.init.type !== 'CallExpression') return;\n const call = node.init;\n if (\n call.callee.type !== 'Identifier' ||\n (call.callee.name !== 'apiClient' && call.callee.name !== 'apiPaginate')\n )\n return;\n const firstArg = call.arguments[0];\n if (!firstArg) return;\n if (\n firstArg.type === 'ArrowFunctionExpression' ||\n firstArg.type === 'FunctionExpression'\n ) {\n apisWithFnEndpoint.add(node.id.name);\n }\n },\n\n // xApi.useFetch(params, options?)\n CallExpression(node: TSESTree.CallExpression) {\n if (node.callee.type !== 'MemberExpression') return;\n const obj = node.callee.object;\n const prop = node.callee.property;\n if (obj.type !== 'Identifier') return;\n if (!apisWithFnEndpoint.has(obj.name)) return;\n if (prop.type !== 'Identifier') return;\n if (prop.name !== 'useFetch' && prop.name !== 'usePaginate') return;\n\n const optionsArg = node.arguments[1];\n if (!optionsArg) {\n context.report({ node, messageId: 'missing' });\n return;\n }\n if (optionsArg.type !== 'ObjectExpression') return; // не статически анализируем\n const hasQueryKey = optionsArg.properties.some(\n p =>\n p.type === 'Property' &&\n p.key.type === 'Identifier' &&\n p.key.name === 'queryKey',\n );\n if (!hasQueryKey) {\n context.report({ node, messageId: 'missing' });\n }\n },\n };\n },\n});\n","import { noAwaitMutate } from './rules/no-await-mutate';\nimport { noNonSerializableParams } from './rules/no-non-serializable-params';\nimport { requireQueryKeyWhenEndpointIsFn } from './rules/require-query-key-when-endpoint-is-fn';\n\nexport const rules = {\n 'no-await-mutate': noAwaitMutate,\n 'no-non-serializable-params': noNonSerializableParams,\n 'require-query-key-when-endpoint-is-fn': requireQueryKeyWhenEndpointIsFn,\n};\n\nexport const configs = {\n recommended: {\n plugins: ['@krymskyimaksym/react-api-client'],\n rules: {\n '@krymskyimaksym/react-api-client/no-await-mutate': 'error',\n '@krymskyimaksym/react-api-client/no-non-serializable-params': 'error',\n '@krymskyimaksym/react-api-client/require-query-key-when-endpoint-is-fn':\n 'warn',\n },\n },\n};\n\nexport default { rules, configs };\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ var noAwaitMutate = createRule({
|
|
|
10
10
|
type: "problem",
|
|
11
11
|
docs: {
|
|
12
12
|
description: "mutate() returns void; await it has no effect. Use mutateAsync for awaitable mutations.",
|
|
13
|
-
recommended: "
|
|
13
|
+
recommended: "recommended"
|
|
14
14
|
},
|
|
15
15
|
fixable: "code",
|
|
16
16
|
schema: [],
|
|
@@ -42,13 +42,64 @@ var noAwaitMutate = createRule({
|
|
|
42
42
|
var createRule2 = ESLintUtils.RuleCreator(
|
|
43
43
|
(name) => `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`
|
|
44
44
|
);
|
|
45
|
-
var
|
|
45
|
+
var noNonSerializableParams = createRule2({
|
|
46
|
+
name: "no-non-serializable-params",
|
|
47
|
+
meta: {
|
|
48
|
+
type: "problem",
|
|
49
|
+
docs: {
|
|
50
|
+
description: "Params for useFetch/usePaginate/useQuery must be serializable. Functions and Symbol break hashQueryKey at runtime.",
|
|
51
|
+
recommended: "recommended"
|
|
52
|
+
},
|
|
53
|
+
schema: [],
|
|
54
|
+
messages: {
|
|
55
|
+
noFunction: "\u041D\u0435\u0441\u0435\u0440\u0438\u0430\u043B\u0438\u0437\u0443\u0435\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0432 params (\u0444\u0443\u043D\u043A\u0446\u0438\u044F): \u0441\u043B\u043E\u043C\u0430\u0435\u0442 hashQueryKey \u0432 \u0440\u0430\u043D\u0442\u0430\u0439\u043C\u0435. \u0412\u044B\u043D\u0435\u0441\u0438 callback \u043D\u0430\u0440\u0443\u0436\u0443 \u0445\u0443\u043A\u0430.",
|
|
56
|
+
noSymbol: "\u041D\u0435\u0441\u0435\u0440\u0438\u0430\u043B\u0438\u0437\u0443\u0435\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0432 params (Symbol): \u0441\u043B\u043E\u043C\u0430\u0435\u0442 hashQueryKey \u0432 \u0440\u0430\u043D\u0442\u0430\u0439\u043C\u0435."
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
defaultOptions: [],
|
|
60
|
+
create(context) {
|
|
61
|
+
const HOOK_NAMES = /* @__PURE__ */ new Set(["useFetch", "usePaginate", "useQuery"]);
|
|
62
|
+
function checkObjectExpression(obj) {
|
|
63
|
+
for (const prop of obj.properties) {
|
|
64
|
+
if (prop.type !== "Property") continue;
|
|
65
|
+
const v = prop.value;
|
|
66
|
+
if (v.type === "ArrowFunctionExpression" || v.type === "FunctionExpression") {
|
|
67
|
+
context.report({ node: v, messageId: "noFunction" });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (v.type === "CallExpression" && v.callee.type === "Identifier" && v.callee.name === "Symbol") {
|
|
71
|
+
context.report({ node: v, messageId: "noSymbol" });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
CallExpression(node) {
|
|
77
|
+
if (node.callee.type !== "MemberExpression") {
|
|
78
|
+
if (node.callee.type === "Identifier" && node.callee.name === "useQuery") {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const prop = node.callee.property;
|
|
84
|
+
if (prop.type !== "Identifier" || !HOOK_NAMES.has(prop.name)) return;
|
|
85
|
+
const paramsArg = node.arguments[0];
|
|
86
|
+
if (!paramsArg) return;
|
|
87
|
+
if (paramsArg.type !== "ObjectExpression") return;
|
|
88
|
+
checkObjectExpression(paramsArg);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
var createRule3 = ESLintUtils.RuleCreator(
|
|
94
|
+
(name) => `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`
|
|
95
|
+
);
|
|
96
|
+
var requireQueryKeyWhenEndpointIsFn = createRule3({
|
|
46
97
|
name: "require-query-key-when-endpoint-is-fn",
|
|
47
98
|
meta: {
|
|
48
99
|
type: "suggestion",
|
|
49
100
|
docs: {
|
|
50
101
|
description: "When apiClient is created from an endpoint function, useFetch should specify explicit queryKey.",
|
|
51
|
-
recommended: "
|
|
102
|
+
recommended: "recommended"
|
|
52
103
|
},
|
|
53
104
|
schema: [],
|
|
54
105
|
messages: {
|
|
@@ -101,6 +152,7 @@ var requireQueryKeyWhenEndpointIsFn = createRule2({
|
|
|
101
152
|
// src/index.ts
|
|
102
153
|
var rules = {
|
|
103
154
|
"no-await-mutate": noAwaitMutate,
|
|
155
|
+
"no-non-serializable-params": noNonSerializableParams,
|
|
104
156
|
"require-query-key-when-endpoint-is-fn": requireQueryKeyWhenEndpointIsFn
|
|
105
157
|
};
|
|
106
158
|
var configs = {
|
|
@@ -108,6 +160,7 @@ var configs = {
|
|
|
108
160
|
plugins: ["@krymskyimaksym/react-api-client"],
|
|
109
161
|
rules: {
|
|
110
162
|
"@krymskyimaksym/react-api-client/no-await-mutate": "error",
|
|
163
|
+
"@krymskyimaksym/react-api-client/no-non-serializable-params": "error",
|
|
111
164
|
"@krymskyimaksym/react-api-client/require-query-key-when-endpoint-is-fn": "warn"
|
|
112
165
|
}
|
|
113
166
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rules/no-await-mutate.ts","../src/rules/require-query-key-when-endpoint-is-fn.ts","../src/index.ts"],"names":["createRule","ESLintUtils"],"mappings":"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","file":"index.mjs","sourcesContent":["import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`,\n);\n\n/**\n * Запрещает `await x.mutate(...)` — `mutate` возвращает `void`, а не\n * `Promise`. Для последовательной логики или try/catch используй\n * `mutateAsync`.\n *\n * Эвристика: `await <expr>.mutate(...)`.\n */\nexport const noAwaitMutate = createRule({\n name: 'no-await-mutate',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'mutate() returns void; await it has no effect. Use mutateAsync for awaitable mutations.',\n recommended: 'error',\n },\n fixable: 'code',\n schema: [],\n messages: {\n avoid:\n '`mutate` возвращает void — await не сработает. Используй `mutateAsync` для await/try-catch.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n AwaitExpression(node: TSESTree.AwaitExpression) {\n const arg = node.argument;\n if (arg.type !== 'CallExpression') return;\n const callee = arg.callee;\n if (callee.type !== 'MemberExpression') return;\n const prop = callee.property;\n if (prop.type !== 'Identifier' || prop.name !== 'mutate') return;\n\n context.report({\n node,\n messageId: 'avoid',\n fix(fixer) {\n return fixer.replaceText(prop, 'mutateAsync');\n },\n });\n },\n };\n },\n});\n","import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`,\n);\n\n/**\n * Если `apiClient(fn, ...)` создан с endpoint-функцией, и где-то\n * вызывается `<api>.useFetch(params)` без `queryKey` в options —\n * предупредить. По умолчанию ключ генерится из endpoint+params, но при\n * endpoint-функции стабильность ключа зависит от того, что функция\n * возвращает одну и ту же строку для одних и тех же params. Явный\n * `queryKey` снимает риск.\n *\n * Эвристика статическая: ищем `const xxxApi = apiClient(SomethingFn, ...)`,\n * запоминаем имя, потом ругаемся на `xxxApi.useFetch(...)` без queryKey\n * в options-объекте.\n */\nexport const requireQueryKeyWhenEndpointIsFn = createRule({\n name: 'require-query-key-when-endpoint-is-fn',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'When apiClient is created from an endpoint function, useFetch should specify explicit queryKey.',\n recommended: 'warn',\n },\n schema: [],\n messages: {\n missing:\n 'apiClient с endpoint-функцией: укажи `queryKey` явно, чтобы ключ кэша был стабильным.',\n },\n },\n defaultOptions: [],\n create(context) {\n const apisWithFnEndpoint = new Set<string>();\n\n return {\n // const xApi = apiClient(fn, ...)\n VariableDeclarator(node: TSESTree.VariableDeclarator) {\n if (node.id.type !== 'Identifier') return;\n if (!node.init || node.init.type !== 'CallExpression') return;\n const call = node.init;\n if (\n call.callee.type !== 'Identifier' ||\n (call.callee.name !== 'apiClient' && call.callee.name !== 'apiPaginate')\n )\n return;\n const firstArg = call.arguments[0];\n if (!firstArg) return;\n if (\n firstArg.type === 'ArrowFunctionExpression' ||\n firstArg.type === 'FunctionExpression'\n ) {\n apisWithFnEndpoint.add(node.id.name);\n }\n },\n\n // xApi.useFetch(params, options?)\n CallExpression(node: TSESTree.CallExpression) {\n if (node.callee.type !== 'MemberExpression') return;\n const obj = node.callee.object;\n const prop = node.callee.property;\n if (obj.type !== 'Identifier') return;\n if (!apisWithFnEndpoint.has(obj.name)) return;\n if (prop.type !== 'Identifier') return;\n if (prop.name !== 'useFetch' && prop.name !== 'usePaginate') return;\n\n const optionsArg = node.arguments[1];\n if (!optionsArg) {\n context.report({ node, messageId: 'missing' });\n return;\n }\n if (optionsArg.type !== 'ObjectExpression') return; // не статически анализируем\n const hasQueryKey = optionsArg.properties.some(\n p =>\n p.type === 'Property' &&\n p.key.type === 'Identifier' &&\n p.key.name === 'queryKey',\n );\n if (!hasQueryKey) {\n context.report({ node, messageId: 'missing' });\n }\n },\n };\n },\n});\n","import { noAwaitMutate } from './rules/no-await-mutate';\nimport { requireQueryKeyWhenEndpointIsFn } from './rules/require-query-key-when-endpoint-is-fn';\n\nexport const rules = {\n 'no-await-mutate': noAwaitMutate,\n 'require-query-key-when-endpoint-is-fn': requireQueryKeyWhenEndpointIsFn,\n};\n\nexport const configs = {\n recommended: {\n plugins: ['@krymskyimaksym/react-api-client'],\n rules: {\n '@krymskyimaksym/react-api-client/no-await-mutate': 'error',\n '@krymskyimaksym/react-api-client/require-query-key-when-endpoint-is-fn':\n 'warn',\n },\n },\n};\n\nexport default { rules, configs };\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/rules/no-await-mutate.ts","../src/rules/no-non-serializable-params.ts","../src/rules/require-query-key-when-endpoint-is-fn.ts","../src/index.ts"],"names":["createRule","ESLintUtils"],"mappings":"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","file":"index.mjs","sourcesContent":["import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`,\n);\n\n/**\n * Запрещает `await x.mutate(...)` — `mutate` возвращает `void`, а не\n * `Promise`. Для последовательной логики или try/catch используй\n * `mutateAsync`.\n *\n * Эвристика: `await <expr>.mutate(...)`.\n */\nexport const noAwaitMutate = createRule({\n name: 'no-await-mutate',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'mutate() returns void; await it has no effect. Use mutateAsync for awaitable mutations.',\n recommended: 'recommended',\n },\n fixable: 'code',\n schema: [],\n messages: {\n avoid:\n '`mutate` возвращает void — await не сработает. Используй `mutateAsync` для await/try-catch.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n AwaitExpression(node: TSESTree.AwaitExpression) {\n const arg = node.argument;\n if (arg.type !== 'CallExpression') return;\n const callee = arg.callee;\n if (callee.type !== 'MemberExpression') return;\n const prop = callee.property;\n if (prop.type !== 'Identifier' || prop.name !== 'mutate') return;\n\n context.report({\n node,\n messageId: 'avoid',\n fix(fixer) {\n return fixer.replaceText(prop, 'mutateAsync');\n },\n });\n },\n };\n },\n});\n","import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`,\n);\n\n/**\n * Запрещает несериализуемые значения в `params` для `useFetch` /\n * `usePaginate` / `useQuery`. Функции и Symbol сломают `hashQueryKey`\n * (он `throw`-ает на них), а это приведёт к рантайм-ошибке при mount'е.\n *\n * Ловим статически:\n * - стрелочные/функциональные выражения как значения свойств;\n * - вызовы `Symbol(...)` как значения свойств.\n *\n * Эвристика — без типов; ловит самые частые случаи.\n */\nexport const noNonSerializableParams = createRule({\n name: 'no-non-serializable-params',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Params for useFetch/usePaginate/useQuery must be serializable. Functions and Symbol break hashQueryKey at runtime.',\n recommended: 'recommended',\n },\n schema: [],\n messages: {\n noFunction:\n 'Несериализуемое значение в params (функция): сломает hashQueryKey в рантайме. Вынеси callback наружу хука.',\n noSymbol:\n 'Несериализуемое значение в params (Symbol): сломает hashQueryKey в рантайме.',\n },\n },\n defaultOptions: [],\n create(context) {\n const HOOK_NAMES = new Set(['useFetch', 'usePaginate', 'useQuery']);\n\n function checkObjectExpression(obj: TSESTree.ObjectExpression) {\n for (const prop of obj.properties) {\n if (prop.type !== 'Property') continue;\n const v = prop.value;\n if (\n v.type === 'ArrowFunctionExpression' ||\n v.type === 'FunctionExpression'\n ) {\n context.report({ node: v, messageId: 'noFunction' });\n continue;\n }\n if (\n v.type === 'CallExpression' &&\n v.callee.type === 'Identifier' &&\n v.callee.name === 'Symbol'\n ) {\n context.report({ node: v, messageId: 'noSymbol' });\n }\n }\n }\n\n return {\n CallExpression(node: TSESTree.CallExpression) {\n if (node.callee.type !== 'MemberExpression') {\n // useQuery(key, fn, opts) — первый аргумент это key,\n // не params; пропускаем.\n if (\n node.callee.type === 'Identifier' &&\n node.callee.name === 'useQuery'\n ) {\n return;\n }\n return;\n }\n const prop = node.callee.property;\n if (prop.type !== 'Identifier' || !HOOK_NAMES.has(prop.name)) return;\n const paramsArg = node.arguments[0];\n if (!paramsArg) return;\n if (paramsArg.type !== 'ObjectExpression') return;\n checkObjectExpression(paramsArg);\n },\n };\n },\n});\n","import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/krymskyimaksym/react-api-client/tree/main/packages/eslint-plugin/docs/${name}.md`,\n);\n\n/**\n * Если `apiClient(fn, ...)` создан с endpoint-функцией, и где-то\n * вызывается `<api>.useFetch(params)` без `queryKey` в options —\n * предупредить. По умолчанию ключ генерится из endpoint+params, но при\n * endpoint-функции стабильность ключа зависит от того, что функция\n * возвращает одну и ту же строку для одних и тех же params. Явный\n * `queryKey` снимает риск.\n *\n * Эвристика статическая: ищем `const xxxApi = apiClient(SomethingFn, ...)`,\n * запоминаем имя, потом ругаемся на `xxxApi.useFetch(...)` без queryKey\n * в options-объекте.\n */\nexport const requireQueryKeyWhenEndpointIsFn = createRule({\n name: 'require-query-key-when-endpoint-is-fn',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'When apiClient is created from an endpoint function, useFetch should specify explicit queryKey.',\n recommended: 'recommended',\n },\n schema: [],\n messages: {\n missing:\n 'apiClient с endpoint-функцией: укажи `queryKey` явно, чтобы ключ кэша был стабильным.',\n },\n },\n defaultOptions: [],\n create(context) {\n const apisWithFnEndpoint = new Set<string>();\n\n return {\n // const xApi = apiClient(fn, ...)\n VariableDeclarator(node: TSESTree.VariableDeclarator) {\n if (node.id.type !== 'Identifier') return;\n if (!node.init || node.init.type !== 'CallExpression') return;\n const call = node.init;\n if (\n call.callee.type !== 'Identifier' ||\n (call.callee.name !== 'apiClient' && call.callee.name !== 'apiPaginate')\n )\n return;\n const firstArg = call.arguments[0];\n if (!firstArg) return;\n if (\n firstArg.type === 'ArrowFunctionExpression' ||\n firstArg.type === 'FunctionExpression'\n ) {\n apisWithFnEndpoint.add(node.id.name);\n }\n },\n\n // xApi.useFetch(params, options?)\n CallExpression(node: TSESTree.CallExpression) {\n if (node.callee.type !== 'MemberExpression') return;\n const obj = node.callee.object;\n const prop = node.callee.property;\n if (obj.type !== 'Identifier') return;\n if (!apisWithFnEndpoint.has(obj.name)) return;\n if (prop.type !== 'Identifier') return;\n if (prop.name !== 'useFetch' && prop.name !== 'usePaginate') return;\n\n const optionsArg = node.arguments[1];\n if (!optionsArg) {\n context.report({ node, messageId: 'missing' });\n return;\n }\n if (optionsArg.type !== 'ObjectExpression') return; // не статически анализируем\n const hasQueryKey = optionsArg.properties.some(\n p =>\n p.type === 'Property' &&\n p.key.type === 'Identifier' &&\n p.key.name === 'queryKey',\n );\n if (!hasQueryKey) {\n context.report({ node, messageId: 'missing' });\n }\n },\n };\n },\n});\n","import { noAwaitMutate } from './rules/no-await-mutate';\nimport { noNonSerializableParams } from './rules/no-non-serializable-params';\nimport { requireQueryKeyWhenEndpointIsFn } from './rules/require-query-key-when-endpoint-is-fn';\n\nexport const rules = {\n 'no-await-mutate': noAwaitMutate,\n 'no-non-serializable-params': noNonSerializableParams,\n 'require-query-key-when-endpoint-is-fn': requireQueryKeyWhenEndpointIsFn,\n};\n\nexport const configs = {\n recommended: {\n plugins: ['@krymskyimaksym/react-api-client'],\n rules: {\n '@krymskyimaksym/react-api-client/no-await-mutate': 'error',\n '@krymskyimaksym/react-api-client/no-non-serializable-params': 'error',\n '@krymskyimaksym/react-api-client/require-query-key-when-endpoint-is-fn':\n 'warn',\n },\n },\n};\n\nexport default { rules, configs };\n"]}
|