eslint-plugin-effector 0.17.0 → 0.19.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 +6 -0
- package/dist/index.cjs +634 -176
- package/dist/index.d.cts +71 -80
- package/dist/index.d.mts +71 -80
- package/dist/index.mjs +606 -146
- package/package.json +23 -21
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
1
2
|
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -18,18 +19,25 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
18
19
|
value: mod,
|
|
19
20
|
enumerable: true
|
|
20
21
|
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
21
23
|
let _typescript_eslint_utils = require("@typescript-eslint/utils");
|
|
22
24
|
let _typescript_eslint_type_utils = require("@typescript-eslint/type-utils");
|
|
23
25
|
let typescript = require("typescript");
|
|
26
|
+
typescript = __toESM(typescript, 1);
|
|
24
27
|
let esquery = require("esquery");
|
|
25
|
-
esquery = __toESM(esquery);
|
|
28
|
+
esquery = __toESM(esquery, 1);
|
|
29
|
+
//#region package.json
|
|
26
30
|
var name = "eslint-plugin-effector";
|
|
27
|
-
var version = "0.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
var version = "0.19.0";
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/shared/create.ts
|
|
34
|
+
const createRule = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://eslint.effector.dev/rules/${name}`);
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/shared/is.ts
|
|
37
|
+
const check$1 = (symbol, types, from) => {
|
|
38
|
+
const name = symbol.getName();
|
|
31
39
|
const declarations = symbol.declarations ?? [];
|
|
32
|
-
return types.includes(name
|
|
40
|
+
return types.includes(name) && declarations.map((decl) => decl.getSourceFile().fileName).some((fname) => fname.includes("node_modules") && fname.includes(from));
|
|
33
41
|
};
|
|
34
42
|
const isType = {
|
|
35
43
|
store: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
|
|
@@ -47,6 +55,11 @@ const isType = {
|
|
|
47
55
|
package: "effector",
|
|
48
56
|
name: "Effect"
|
|
49
57
|
}, program),
|
|
58
|
+
domain: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
|
|
59
|
+
from: "package",
|
|
60
|
+
package: "effector",
|
|
61
|
+
name: "Domain"
|
|
62
|
+
}, program),
|
|
50
63
|
unit: (type, program) => {
|
|
51
64
|
return (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
|
|
52
65
|
from: "package",
|
|
@@ -56,18 +69,14 @@ const isType = {
|
|
|
56
69
|
"StoreWritable",
|
|
57
70
|
"Event",
|
|
58
71
|
"EventCallable",
|
|
59
|
-
"Effect"
|
|
72
|
+
"Effect",
|
|
73
|
+
"Domain"
|
|
60
74
|
]
|
|
61
75
|
}, program);
|
|
62
76
|
},
|
|
63
|
-
domain: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
|
|
64
|
-
from: "package",
|
|
65
|
-
package: "effector",
|
|
66
|
-
name: "Domain"
|
|
67
|
-
}, program),
|
|
68
77
|
gate: (type) => {
|
|
69
78
|
const symbol = type.getSymbol() ?? type.aliasSymbol;
|
|
70
|
-
return symbol ? check(symbol, ["Gate"], "effector") : false;
|
|
79
|
+
return symbol ? check$1(symbol, ["Gate"], "effector") : false;
|
|
71
80
|
},
|
|
72
81
|
jsx: (type, program) => {
|
|
73
82
|
return (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
|
|
@@ -94,13 +103,15 @@ const isType = {
|
|
|
94
103
|
}, program);
|
|
95
104
|
}
|
|
96
105
|
};
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.ts
|
|
97
108
|
var enforce_effect_naming_convention_default = createRule({
|
|
98
109
|
name: "enforce-effect-naming-convention",
|
|
99
110
|
meta: {
|
|
100
111
|
type: "problem",
|
|
101
112
|
docs: { description: "Enforce Fx as a suffix for any Effector Effect." },
|
|
102
113
|
messages: {
|
|
103
|
-
invalid: "Effect
|
|
114
|
+
invalid: "Effect \"{{ current }}\" should be named with `Fx` suffix, rename it to \"{{ fixed }}\"",
|
|
104
115
|
rename: "Rename \"{{ current }}\" to \"{{ fixed }}\""
|
|
105
116
|
},
|
|
106
117
|
schema: [],
|
|
@@ -109,33 +120,190 @@ var enforce_effect_naming_convention_default = createRule({
|
|
|
109
120
|
defaultOptions: [],
|
|
110
121
|
create: (context) => {
|
|
111
122
|
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
|
|
112
|
-
return {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
123
|
+
return {
|
|
124
|
+
[`${selector$14.variable}, ${selector$14.array.identifier}, ${selector$14.array.assignment}, ${selector$14.function.identifier}, ${selector$14.function.assignment}`]: (node) => {
|
|
125
|
+
const type = services.getTypeAtLocation(node);
|
|
126
|
+
if (!isType.effect(type, services.program)) return;
|
|
127
|
+
const data = {
|
|
128
|
+
current: node.name,
|
|
129
|
+
fixed: node.name + "Fx"
|
|
130
|
+
};
|
|
131
|
+
if (node.typeAnnotation) return context.report({
|
|
132
|
+
node,
|
|
133
|
+
messageId: "invalid",
|
|
134
|
+
data
|
|
135
|
+
});
|
|
136
|
+
const suggestion = {
|
|
137
|
+
messageId: "rename",
|
|
138
|
+
data: {
|
|
139
|
+
current: node.name,
|
|
140
|
+
fixed: data.fixed
|
|
141
|
+
},
|
|
142
|
+
fix: (fixer) => fixer.replaceText(node, data.fixed)
|
|
143
|
+
};
|
|
144
|
+
context.report({
|
|
145
|
+
node,
|
|
146
|
+
messageId: "invalid",
|
|
147
|
+
data,
|
|
148
|
+
suggest: [suggestion]
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
[`${selector$14.shape.identifier}, ${selector$14.shape.assignment}`]: (node) => {
|
|
152
|
+
const type = services.getTypeAtLocation(node.value);
|
|
153
|
+
const ident = node.value.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier ? node.value : node.value.left;
|
|
154
|
+
if (!isType.effect(type, services.program)) return;
|
|
155
|
+
const data = {
|
|
156
|
+
current: ident.name,
|
|
157
|
+
fixed: ident.name + "Fx"
|
|
158
|
+
};
|
|
159
|
+
const suggestion = {
|
|
160
|
+
messageId: "rename",
|
|
161
|
+
data: {
|
|
162
|
+
current: ident.name,
|
|
163
|
+
fixed: data.fixed
|
|
164
|
+
},
|
|
165
|
+
fix: (fixer) => node.shorthand ? fixer.insertTextAfter(node.key, `: ${data.fixed}`) : fixer.replaceText(ident, data.fixed)
|
|
166
|
+
};
|
|
167
|
+
context.report({
|
|
168
|
+
node: ident,
|
|
169
|
+
messageId: "invalid",
|
|
170
|
+
data,
|
|
171
|
+
suggest: [suggestion]
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
};
|
|
136
175
|
}
|
|
137
176
|
});
|
|
138
177
|
const FxRegex = /Fx$/;
|
|
178
|
+
const selector$14 = {
|
|
179
|
+
variable: `VariableDeclarator > Identifier.id[name!=${FxRegex}]`,
|
|
180
|
+
array: {
|
|
181
|
+
identifier: `ArrayPattern > Identifier.elements[name!=${FxRegex}]`,
|
|
182
|
+
assignment: `ArrayPattern > AssignmentPattern > Identifier.left[name!=${FxRegex}]`
|
|
183
|
+
},
|
|
184
|
+
shape: {
|
|
185
|
+
identifier: `ObjectPattern > Property:has(> Identifier.value[name!=${FxRegex}])`,
|
|
186
|
+
assignment: `ObjectPattern > Property:has(> AssignmentPattern:has(> Identifier.left[name!=${FxRegex}]))`
|
|
187
|
+
},
|
|
188
|
+
function: {
|
|
189
|
+
identifier: `:function > Identifier.params[name!=${FxRegex}]`,
|
|
190
|
+
assignment: `:function > AssignmentPattern > Identifier.left[name!=${FxRegex}]`
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/shared/package.ts
|
|
195
|
+
const PACKAGE_NAME$1 = {
|
|
196
|
+
core: /^effector(?:\u002Fcompat)?$/,
|
|
197
|
+
react: /^effector-react$/,
|
|
198
|
+
storage: /^@?effector-storage(\u002F[\w-]+)*$/
|
|
199
|
+
};
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/rules/enforce-exhaustive-useUnit-destructuring/enforce-exhaustive-useUnit-destructuring.ts
|
|
202
|
+
var enforce_exhaustive_useUnit_destructuring_default = createRule({
|
|
203
|
+
name: "enforce-exhaustive-useUnit-destructuring",
|
|
204
|
+
meta: {
|
|
205
|
+
type: "problem",
|
|
206
|
+
docs: { description: "Ensure all units passed to useUnit are properly destructured." },
|
|
207
|
+
messages: {
|
|
208
|
+
unusedKey: "Property \"{{name}}\" is passed but not destructured.",
|
|
209
|
+
missingKey: "Property \"{{name}}\" is destructured but not passed in the unit object."
|
|
210
|
+
},
|
|
211
|
+
schema: [],
|
|
212
|
+
defaultOptions: []
|
|
213
|
+
},
|
|
214
|
+
create(context) {
|
|
215
|
+
const importedAs = /* @__PURE__ */ new Set();
|
|
216
|
+
return {
|
|
217
|
+
[selector$13.import]: (node) => void importedAs.add(node.local.name),
|
|
218
|
+
[`${selector$13.variable.shape}:has(> ${selector$13.call}:has(${selector$13.arg.shape}))`](node) {
|
|
219
|
+
if (!importedAs.has(node.init.callee.name)) return;
|
|
220
|
+
const provided = shapeToKeyMap(node.init.arguments[0]);
|
|
221
|
+
const consumed = shapeToKeyMap(node.id);
|
|
222
|
+
if (provided === null || consumed === null) return;
|
|
223
|
+
for (const { type, name } of check(provided, consumed)) if (type === "unused") context.report({
|
|
224
|
+
node: node.init.arguments[0],
|
|
225
|
+
messageId: "unusedKey",
|
|
226
|
+
data: { name }
|
|
227
|
+
});
|
|
228
|
+
else context.report({
|
|
229
|
+
node: node.id,
|
|
230
|
+
messageId: "missingKey",
|
|
231
|
+
data: { name }
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
[`${selector$13.variable.list}:has(> ${selector$13.call}:has(${selector$13.arg.list}))`](node) {
|
|
235
|
+
if (!importedAs.has(node.init.callee.name)) return;
|
|
236
|
+
const provided = listToKeyMap(node.init.arguments[0]);
|
|
237
|
+
const consumed = listToKeyMap(node.id);
|
|
238
|
+
if (provided === null || consumed === null) return;
|
|
239
|
+
for (const { type, name } of check(provided, consumed)) if (type === "unused") context.report({
|
|
240
|
+
node: node.init.arguments[0],
|
|
241
|
+
messageId: "unusedKey",
|
|
242
|
+
data: { name }
|
|
243
|
+
});
|
|
244
|
+
else context.report({
|
|
245
|
+
node: node.id,
|
|
246
|
+
messageId: "missingKey",
|
|
247
|
+
data: { name }
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
const selector$13 = {
|
|
254
|
+
import: `ImportDeclaration[source.value=${PACKAGE_NAME$1.react}] > ImportSpecifier[imported.name=useUnit]`,
|
|
255
|
+
variable: {
|
|
256
|
+
shape: "VariableDeclarator[id.type=ObjectPattern]",
|
|
257
|
+
list: "VariableDeclarator[id.type=ArrayPattern]"
|
|
258
|
+
},
|
|
259
|
+
call: "CallExpression.init[arguments.length=1][callee.type=Identifier]",
|
|
260
|
+
arg: {
|
|
261
|
+
shape: "ObjectExpression.arguments",
|
|
262
|
+
list: "ArrayExpression.arguments"
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
function toName$1(key, node) {
|
|
266
|
+
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.name;
|
|
267
|
+
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Literal) return String(node.value);
|
|
268
|
+
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && node.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return `${toName$1(key, node.object)}.${node.property.name}`;
|
|
269
|
+
return `<unknown at ${key}>`;
|
|
270
|
+
}
|
|
271
|
+
function toKey(prop) {
|
|
272
|
+
if (prop.computed) return null;
|
|
273
|
+
else if (prop.key.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return prop.key.name;
|
|
274
|
+
else return prop.key.value;
|
|
275
|
+
}
|
|
276
|
+
function* check(provided, consumed) {
|
|
277
|
+
for (const [key, node] of provided) if (!consumed.has(key)) yield {
|
|
278
|
+
type: "unused",
|
|
279
|
+
name: toName$1(key, node)
|
|
280
|
+
};
|
|
281
|
+
for (const [key, node] of consumed) if (!provided.has(key)) yield {
|
|
282
|
+
type: "missing",
|
|
283
|
+
name: toName$1(key, node)
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function shapeToKeyMap(shape) {
|
|
287
|
+
const map = /* @__PURE__ */ new Map();
|
|
288
|
+
for (const prop of shape.properties) {
|
|
289
|
+
if (prop.type !== _typescript_eslint_utils.AST_NODE_TYPES.Property) return null;
|
|
290
|
+
const key = toKey(prop);
|
|
291
|
+
if (key === null) return null;
|
|
292
|
+
else map.set(key, prop.key);
|
|
293
|
+
}
|
|
294
|
+
return map;
|
|
295
|
+
}
|
|
296
|
+
function listToKeyMap(list) {
|
|
297
|
+
const map = /* @__PURE__ */ new Map();
|
|
298
|
+
for (const [index, element] of list.elements.entries()) {
|
|
299
|
+
if (element === null) continue;
|
|
300
|
+
if (element.type === _typescript_eslint_utils.AST_NODE_TYPES.RestElement || element.type === _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) return null;
|
|
301
|
+
map.set(index, element);
|
|
302
|
+
}
|
|
303
|
+
return map;
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.ts
|
|
139
307
|
var enforce_gate_naming_convention_default = createRule({
|
|
140
308
|
name: "enforce-gate-naming-convention",
|
|
141
309
|
meta: {
|
|
@@ -178,6 +346,8 @@ var enforce_gate_naming_convention_default = createRule({
|
|
|
178
346
|
}
|
|
179
347
|
});
|
|
180
348
|
const GateRegex = /^[^A-Z]/;
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/rules/enforce-store-naming-convention/enforce-store-naming-convention.ts
|
|
181
351
|
var enforce_store_naming_convention_default = createRule({
|
|
182
352
|
name: "enforce-store-naming-convention",
|
|
183
353
|
meta: {
|
|
@@ -199,41 +369,83 @@ var enforce_store_naming_convention_default = createRule({
|
|
|
199
369
|
defaultOptions: [{ mode: "prefix" }],
|
|
200
370
|
create: (context, [options]) => {
|
|
201
371
|
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const current = node.id.name;
|
|
206
|
-
const trimmed = current.replaceAll(options.mode === "prefix" ? /\$+$/g : /^\$+/g, "");
|
|
372
|
+
const selector = createSelector(options.mode === "prefix" ? PrefixRegex : PostfixRegex);
|
|
373
|
+
const rename = (node) => {
|
|
374
|
+
const trimmed = node.name.replace(options.mode === "prefix" ? /\$+$/g : /^\$+/g, "");
|
|
207
375
|
const fixed = options.mode === "prefix" ? `$${trimmed}` : `${trimmed}$`;
|
|
208
|
-
|
|
209
|
-
current,
|
|
376
|
+
return {
|
|
377
|
+
current: node.name,
|
|
210
378
|
convention: options.mode,
|
|
211
379
|
fixed
|
|
212
380
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
381
|
+
};
|
|
382
|
+
return {
|
|
383
|
+
[`${selector.variable}, ${selector.array.identifier}, ${selector.array.assignment}, ${selector.function.identifier}, ${selector.function.assignment}`]: (node) => {
|
|
384
|
+
const type = services.getTypeAtLocation(node);
|
|
385
|
+
if (!isType.store(type, services.program)) return;
|
|
386
|
+
const data = rename(node);
|
|
387
|
+
if (node.typeAnnotation) return context.report({
|
|
388
|
+
node,
|
|
389
|
+
messageId: "invalid",
|
|
390
|
+
data
|
|
391
|
+
});
|
|
392
|
+
const suggestion = {
|
|
393
|
+
messageId: "rename",
|
|
394
|
+
data: {
|
|
395
|
+
current: node.name,
|
|
396
|
+
fixed: data.fixed
|
|
397
|
+
},
|
|
398
|
+
fix: (fixer) => fixer.replaceText(node, data.fixed)
|
|
399
|
+
};
|
|
400
|
+
context.report({
|
|
401
|
+
node,
|
|
402
|
+
messageId: "invalid",
|
|
403
|
+
data,
|
|
404
|
+
suggest: [suggestion]
|
|
405
|
+
});
|
|
406
|
+
},
|
|
407
|
+
[`${selector.shape.identifier}, ${selector.shape.assignment}`]: (node) => {
|
|
408
|
+
const type = services.getTypeAtLocation(node.value);
|
|
409
|
+
const ident = node.value.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier ? node.value : node.value.left;
|
|
410
|
+
if (!isType.store(type, services.program)) return;
|
|
411
|
+
const data = rename(ident);
|
|
412
|
+
const suggestion = {
|
|
413
|
+
messageId: "rename",
|
|
414
|
+
data: {
|
|
415
|
+
current: ident.name,
|
|
416
|
+
fixed: data.fixed
|
|
417
|
+
},
|
|
418
|
+
fix: (fixer) => node.shorthand ? fixer.insertTextAfter(node.key, `: ${data.fixed}`) : fixer.replaceText(ident, data.fixed)
|
|
419
|
+
};
|
|
420
|
+
context.report({
|
|
421
|
+
node: ident,
|
|
422
|
+
messageId: "invalid",
|
|
423
|
+
data,
|
|
424
|
+
suggest: [suggestion]
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
};
|
|
228
428
|
}
|
|
229
429
|
});
|
|
230
430
|
const PrefixRegex = /^[^$]/;
|
|
231
431
|
const PostfixRegex = /[^$]$/;
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
432
|
+
const createSelector = (regex) => ({
|
|
433
|
+
variable: `VariableDeclarator > Identifier.id[name=${regex}]`,
|
|
434
|
+
array: {
|
|
435
|
+
identifier: `ArrayPattern > Identifier.elements[name=${regex}]`,
|
|
436
|
+
assignment: `ArrayPattern > AssignmentPattern > Identifier.left[name=${regex}]`
|
|
437
|
+
},
|
|
438
|
+
shape: {
|
|
439
|
+
identifier: `ObjectPattern > Property:has(> Identifier.value[name=${regex}])`,
|
|
440
|
+
assignment: `ObjectPattern > Property:has(> AssignmentPattern:has(> Identifier.left[name=${regex}]))`
|
|
441
|
+
},
|
|
442
|
+
function: {
|
|
443
|
+
identifier: `:function > Identifier.params[name=${regex}]`,
|
|
444
|
+
assignment: `:function > AssignmentPattern > Identifier.left[name=${regex}]`
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region src/rules/keep-options-order/keep-options-order.ts
|
|
237
449
|
var keep_options_order_default = createRule({
|
|
238
450
|
name: "keep-options-order",
|
|
239
451
|
meta: {
|
|
@@ -251,8 +463,8 @@ var keep_options_order_default = createRule({
|
|
|
251
463
|
const source = context.sourceCode;
|
|
252
464
|
const imports = /* @__PURE__ */ new Set();
|
|
253
465
|
return {
|
|
254
|
-
[`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`} > ${selector$
|
|
255
|
-
[`CallExpression${selector$
|
|
466
|
+
[`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`} > ${selector$12.method}`]: (node) => imports.add(node.local.name),
|
|
467
|
+
[`CallExpression${selector$12.call}:has(${selector$12.argument})`]: (node) => {
|
|
256
468
|
if (!imports.has(node.callee.name)) return;
|
|
257
469
|
const [config] = node.arguments;
|
|
258
470
|
if (config.properties.some((prop) => prop.type === _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement || prop.key.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier)) return;
|
|
@@ -291,7 +503,7 @@ const TRUE_ORDER = [
|
|
|
291
503
|
"batch",
|
|
292
504
|
"name"
|
|
293
505
|
];
|
|
294
|
-
const selector$
|
|
506
|
+
const selector$12 = {
|
|
295
507
|
method: `ImportSpecifier[imported.name=/(sample|guard)/]`,
|
|
296
508
|
call: `[callee.type="Identifier"][arguments.length=1]`,
|
|
297
509
|
argument: `ObjectExpression.arguments`
|
|
@@ -306,6 +518,8 @@ const isCorrectOrder = (current) => {
|
|
|
306
518
|
}
|
|
307
519
|
return true;
|
|
308
520
|
};
|
|
521
|
+
//#endregion
|
|
522
|
+
//#region src/shared/name.ts
|
|
309
523
|
function functionToName(node) {
|
|
310
524
|
if (node.id) return node.id;
|
|
311
525
|
if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.VariableDeclarator && node.parent.id.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.parent.id;
|
|
@@ -314,70 +528,115 @@ function functionToName(node) {
|
|
|
314
528
|
if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.AssignmentPattern && node.parent.left.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.parent.left;
|
|
315
529
|
return null;
|
|
316
530
|
}
|
|
317
|
-
|
|
531
|
+
function calleeToName(callee) {
|
|
532
|
+
if (callee.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return callee;
|
|
533
|
+
else if (callee.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && callee.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return callee.property;
|
|
534
|
+
else return null;
|
|
535
|
+
}
|
|
536
|
+
function simpleExpressionToName(node) {
|
|
537
|
+
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.name;
|
|
538
|
+
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && !node.computed) return node.property.name;
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
const nameOf = {
|
|
542
|
+
function: functionToName,
|
|
543
|
+
callee: calleeToName,
|
|
544
|
+
expression: { simple: simpleExpressionToName }
|
|
545
|
+
};
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region src/rules/mandatory-scope-binding/mandatory-scope-binding.ts
|
|
318
548
|
var mandatory_scope_binding_default = createRule({
|
|
319
549
|
name: "mandatory-scope-binding",
|
|
320
550
|
meta: {
|
|
321
551
|
type: "problem",
|
|
322
|
-
docs: { description: "Forbid `Event` and `Effect` usage without `useUnit` in React
|
|
323
|
-
messages: { useUnitNeeded: "\"{{ name }}\" must be wrapped with `useUnit` from `effector-react` before usage inside React
|
|
552
|
+
docs: { description: "Forbid `Event` and `Effect` usage without `useUnit` in React." },
|
|
553
|
+
messages: { useUnitNeeded: "\"{{ name }}\" must be wrapped with `useUnit` from `effector-react` before usage inside React." },
|
|
324
554
|
schema: []
|
|
325
555
|
},
|
|
326
556
|
defaultOptions: [],
|
|
327
557
|
create: (context) => {
|
|
328
558
|
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
|
|
329
559
|
const checker = services.program.getTypeChecker();
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
560
|
+
const inRender = [];
|
|
561
|
+
const inHook = [];
|
|
562
|
+
/** check if the expression is used in a context specifically expecting a unit */
|
|
563
|
+
const isExpectingUnit = (slot) => {
|
|
564
|
+
const tsnode = services.esTreeNodeToTSNodeMap.get(slot);
|
|
565
|
+
const type = checker.getContextualType(tsnode);
|
|
566
|
+
if (type) return isType.event(type, services.program) || isType.effect(type, services.program);
|
|
567
|
+
else return false;
|
|
568
|
+
};
|
|
569
|
+
const check = (mode, node) => {
|
|
570
|
+
if (!(inRender.at(-1) ?? false)) return;
|
|
571
|
+
const type = services.getTypeAtLocation(node);
|
|
572
|
+
if (!isType.event(type, services.program) && !isType.effect(type, services.program)) return;
|
|
573
|
+
if (mode === "call") return report(node);
|
|
574
|
+
const delegated = isExpectingUnit(node);
|
|
575
|
+
if ((mode === "jsx" || (inHook.at(-1) ?? false)) && delegated) return;
|
|
576
|
+
else return report(node);
|
|
577
|
+
};
|
|
578
|
+
const report = (node) => {
|
|
579
|
+
const name = nameOf.expression.simple(node) ?? "<expression>";
|
|
580
|
+
context.report({
|
|
581
|
+
node,
|
|
582
|
+
messageId: "useUnitNeeded",
|
|
583
|
+
data: { name }
|
|
584
|
+
});
|
|
333
585
|
};
|
|
334
586
|
return {
|
|
335
|
-
[`
|
|
336
|
-
if (
|
|
337
|
-
const name
|
|
338
|
-
if (name
|
|
587
|
+
[`:matches(${selector$11.function})`]: (node) => {
|
|
588
|
+
if (inRender.at(-1) ?? false) return void inRender.push(true);
|
|
589
|
+
const name = nameOf.function(node);
|
|
590
|
+
if (name && UseRegex$1.test(name.name)) return void inRender.push(true);
|
|
339
591
|
const tsnode = services.esTreeNodeToTSNodeMap.get(node);
|
|
340
592
|
const signature = checker.getSignatureFromDeclaration(tsnode);
|
|
341
593
|
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getVoidType();
|
|
342
|
-
if (isType.jsx(returnType, services.program)) return void
|
|
343
|
-
const inferred =
|
|
344
|
-
if (inferred ? isType.component(
|
|
345
|
-
|
|
594
|
+
if (returnType.isUnion() ? returnType.types.some((type) => isType.jsx(type, services.program)) : isType.jsx(returnType, services.program)) return void inRender.push(true);
|
|
595
|
+
const inferred = typescript.default.isExpression(tsnode) && (0, _typescript_eslint_type_utils.getContextualType)(checker, tsnode) || checker.getUnknownType();
|
|
596
|
+
if (inferred.isUnion() ? inferred.types.some((type) => isType.component(type, services.program)) : isType.component(inferred, services.program)) return void inRender.push(true);
|
|
597
|
+
inRender.push(false);
|
|
346
598
|
},
|
|
347
|
-
[`:matches(
|
|
348
|
-
"ClassDeclaration": () => void
|
|
349
|
-
"ClassDeclaration:exit": () => void
|
|
599
|
+
[`:matches(${selector$11.function}):exit`]: () => void inRender.pop(),
|
|
600
|
+
"ClassDeclaration": () => void inRender.push(false),
|
|
601
|
+
"ClassDeclaration:exit": () => void inRender.pop(),
|
|
350
602
|
"CallExpression": (node) => {
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
package: "effector-react",
|
|
354
|
-
name: [
|
|
355
|
-
"useStore",
|
|
356
|
-
"useStoreMap",
|
|
357
|
-
"useList",
|
|
358
|
-
"useEvent",
|
|
359
|
-
"useUnit"
|
|
360
|
-
]
|
|
361
|
-
}, services.program);
|
|
362
|
-
stack.hook.push(isHook);
|
|
603
|
+
const id = nameOf.callee(node.callee), isEnteringHook = id !== null && UseRegex$1.test(id.name);
|
|
604
|
+
inHook.push(isEnteringHook);
|
|
363
605
|
},
|
|
364
|
-
"
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
context.report({
|
|
370
|
-
node,
|
|
371
|
-
messageId: "useUnitNeeded",
|
|
372
|
-
data: { name: node.name }
|
|
373
|
-
});
|
|
374
|
-
}
|
|
606
|
+
"CallExpression:exit": () => void inHook.pop(),
|
|
607
|
+
[`${selector$11.callee.direct}, ${selector$11.callee.member}`]: (node) => check("call", node),
|
|
608
|
+
[`${selector$11.arg.direct}, ${selector$11.arg.member}`]: (node) => check("arg", node),
|
|
609
|
+
[`${selector$11.prop.direct}, ${selector$11.prop.member}`]: (node) => check("prop", node),
|
|
610
|
+
[`${selector$11.jsx.direct}, ${selector$11.jsx.member}`]: (node) => check("jsx", node)
|
|
375
611
|
};
|
|
376
612
|
}
|
|
377
613
|
});
|
|
378
|
-
const UseRegex = /^use[A-Z0-9].*$/;
|
|
614
|
+
const UseRegex$1 = /^use[A-Z0-9].*$/;
|
|
615
|
+
const selector$11 = {
|
|
616
|
+
function: "FunctionDeclaration, FunctionExpression, ArrowFunctionExpression",
|
|
617
|
+
callee: {
|
|
618
|
+
direct: "CallExpression > Identifier.callee",
|
|
619
|
+
member: "CallExpression > MemberExpression[computed=false].callee"
|
|
620
|
+
},
|
|
621
|
+
arg: {
|
|
622
|
+
direct: "CallExpression > Identifier:not(.callee)",
|
|
623
|
+
member: "CallExpression > MemberExpression[computed=false]:not(.callee)"
|
|
624
|
+
},
|
|
625
|
+
prop: {
|
|
626
|
+
direct: "CallExpression > ObjectExpression > Property > Identifier.value",
|
|
627
|
+
member: "CallExpression > ObjectExpression > Property > MemberExpression[computed=false].value"
|
|
628
|
+
},
|
|
629
|
+
jsx: {
|
|
630
|
+
direct: "JSXExpressionContainer > Identifier",
|
|
631
|
+
member: "JSXExpressionContainer > MemberExpression[computed=false]"
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region src/shared/locate.ts
|
|
379
636
|
const property = (key, node) => node.properties.find((prop) => prop.type == _typescript_eslint_utils.AST_NODE_TYPES.Property && prop.key.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && prop.key.name === key);
|
|
380
637
|
const locate = { property };
|
|
638
|
+
//#endregion
|
|
639
|
+
//#region src/rules/no-ambiguity-target/no-ambiguity-target.ts
|
|
381
640
|
var no_ambiguity_target_default = createRule({
|
|
382
641
|
name: "no-ambiguity-target",
|
|
383
642
|
meta: {
|
|
@@ -417,6 +676,8 @@ var no_ambiguity_target_default = createRule({
|
|
|
417
676
|
}
|
|
418
677
|
});
|
|
419
678
|
const selector$10 = { method: `ImportSpecifier[imported.name=/(sample|guard)/]` };
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region src/rules/no-domain-unit-creators/no-domain-unit-creators.ts
|
|
420
681
|
var no_domain_unit_creators_default = createRule({
|
|
421
682
|
name: "no-domain-unit-creators",
|
|
422
683
|
meta: {
|
|
@@ -429,16 +690,16 @@ var no_domain_unit_creators_default = createRule({
|
|
|
429
690
|
create: (context) => {
|
|
430
691
|
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
|
|
431
692
|
return { [`CallExpression:has(> ${selector$9.member})`]: (node) => {
|
|
432
|
-
const name
|
|
433
|
-
if (!METHODS.has(name
|
|
693
|
+
const name = node.callee.property.name;
|
|
694
|
+
if (!METHODS.has(name)) return;
|
|
434
695
|
const type = services.getTypeAtLocation(node.callee.object);
|
|
435
696
|
if (!isType.domain(type, services.program)) return;
|
|
436
|
-
const factory = ALIAS_MAP.get(name
|
|
697
|
+
const factory = ALIAS_MAP.get(name) ?? name;
|
|
437
698
|
context.report({
|
|
438
699
|
node,
|
|
439
700
|
messageId: "avoid",
|
|
440
701
|
data: {
|
|
441
|
-
method: name
|
|
702
|
+
method: name,
|
|
442
703
|
factory
|
|
443
704
|
}
|
|
444
705
|
});
|
|
@@ -448,6 +709,8 @@ var no_domain_unit_creators_default = createRule({
|
|
|
448
709
|
const ALIAS_MAP = (/* @__PURE__ */ new Map()).set("event", "createEvent").set("store", "createStore").set("effect", "createEffect").set("domain", "createDomain");
|
|
449
710
|
const METHODS = new Set([...ALIAS_MAP.values(), ...ALIAS_MAP.keys()]);
|
|
450
711
|
const selector$9 = { member: `MemberExpression.callee[property.type="Identifier"]` };
|
|
712
|
+
//#endregion
|
|
713
|
+
//#region src/rules/no-duplicate-clock-or-source-array-values/no-duplicate-clock-or-source-array-values.ts
|
|
451
714
|
var no_duplicate_clock_or_source_array_values_default = createRule({
|
|
452
715
|
name: "no-duplicate-clock-or-source-array-values",
|
|
453
716
|
meta: {
|
|
@@ -470,19 +733,19 @@ var no_duplicate_clock_or_source_array_values_default = createRule({
|
|
|
470
733
|
for (const entry of entries) {
|
|
471
734
|
const root = traverseToRoot$1(entry);
|
|
472
735
|
if (!root) continue;
|
|
473
|
-
const name
|
|
474
|
-
if (seen.has(name
|
|
475
|
-
else seen.set(name
|
|
736
|
+
const name = [root.node.name, ...root.path].join(".");
|
|
737
|
+
if (seen.has(name)) report(entry, name, field);
|
|
738
|
+
else seen.set(name, entry);
|
|
476
739
|
}
|
|
477
740
|
};
|
|
478
|
-
const report = (node, name
|
|
741
|
+
const report = (node, name, field) => {
|
|
479
742
|
const data = {
|
|
480
743
|
field,
|
|
481
|
-
unit: name
|
|
744
|
+
unit: name
|
|
482
745
|
};
|
|
483
746
|
const suggestion = {
|
|
484
747
|
messageId: "remove",
|
|
485
|
-
data: { unit: name
|
|
748
|
+
data: { unit: name },
|
|
486
749
|
fix: function* (fixer) {
|
|
487
750
|
yield fixer.remove(node);
|
|
488
751
|
const before = context.sourceCode.getTokenBefore(node);
|
|
@@ -522,6 +785,8 @@ function traverseToRoot$1(node, path = []) {
|
|
|
522
785
|
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && node.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return traverseToRoot$1(node.object, [node.property.name, ...path]);
|
|
523
786
|
return null;
|
|
524
787
|
}
|
|
788
|
+
//#endregion
|
|
789
|
+
//#region src/rules/no-duplicate-on/no-duplicate-on.ts
|
|
525
790
|
var no_duplicate_on_default = createRule({
|
|
526
791
|
name: "no-duplicate-on",
|
|
527
792
|
meta: {
|
|
@@ -540,12 +805,12 @@ var no_duplicate_on_default = createRule({
|
|
|
540
805
|
const arg = node.arguments[0];
|
|
541
806
|
if (!arg || arg.type === _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) return;
|
|
542
807
|
const units = arg.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression ? arg.elements.filter((item) => item !== null && item.type !== _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) : [arg];
|
|
543
|
-
const scope
|
|
544
|
-
const store = identify("store", node.callee.object, scope
|
|
808
|
+
const scope = context.sourceCode.getScope(node);
|
|
809
|
+
const store = identify("store", node.callee.object, scope);
|
|
545
810
|
if (!store) return;
|
|
546
811
|
const set = map.get(store.id) ?? /* @__PURE__ */ new Set();
|
|
547
812
|
for (const unit of units) {
|
|
548
|
-
const instance = identify("unit", unit, scope
|
|
813
|
+
const instance = identify("unit", unit, scope);
|
|
549
814
|
if (!instance) continue;
|
|
550
815
|
if (set.has(instance.id)) {
|
|
551
816
|
const data = {
|
|
@@ -606,16 +871,18 @@ function findSuitableRoot(type, node) {
|
|
|
606
871
|
};
|
|
607
872
|
return null;
|
|
608
873
|
}
|
|
609
|
-
function identify(type, node, scope
|
|
874
|
+
function identify(type, node, scope) {
|
|
610
875
|
const root = findSuitableRoot(type, node);
|
|
611
876
|
if (!root) return null;
|
|
612
|
-
const variable = _typescript_eslint_utils.ASTUtils.findVariable(scope
|
|
877
|
+
const variable = _typescript_eslint_utils.ASTUtils.findVariable(scope, root.node);
|
|
613
878
|
if (!variable) return null;
|
|
614
879
|
return {
|
|
615
880
|
id: `${variable.$id}+${root.path.join(".")}`,
|
|
616
881
|
name: [variable.name, ...root.path].join(".")
|
|
617
882
|
};
|
|
618
883
|
}
|
|
884
|
+
//#endregion
|
|
885
|
+
//#region src/rules/no-forward/no-forward.ts
|
|
619
886
|
var no_forward_default = createRule({
|
|
620
887
|
name: "no-forward",
|
|
621
888
|
meta: {
|
|
@@ -645,11 +912,11 @@ var no_forward_default = createRule({
|
|
|
645
912
|
config.clock = locate.property("from", arg)?.value;
|
|
646
913
|
config.target = locate.property("to", arg)?.value;
|
|
647
914
|
if (config.target) {
|
|
648
|
-
const [call] = esquery.default.match(config.target, query$2.prepend, { visitorKeys }).map((node
|
|
915
|
+
const [call] = esquery.default.match(config.target, query$2.prepend, { visitorKeys }).map((node) => node).filter((node) => node === config.target);
|
|
649
916
|
if (call) [config.target, config.fn] = [call.callee.object, call.arguments[0]];
|
|
650
917
|
}
|
|
651
918
|
if (config.clock && !config.fn) {
|
|
652
|
-
const [call] = esquery.default.match(config.clock, query$2.map, { visitorKeys }).map((node
|
|
919
|
+
const [call] = esquery.default.match(config.clock, query$2.map, { visitorKeys }).map((node) => node).filter((node) => node === config.clock);
|
|
653
920
|
if (call) [config.clock, config.fn] = [call.callee.object, call.arguments[0]];
|
|
654
921
|
}
|
|
655
922
|
const code = [
|
|
@@ -683,6 +950,8 @@ const query$2 = {
|
|
|
683
950
|
map: esquery.default.parse("CallExpression[arguments.length=1]:has(> :first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='map']))"),
|
|
684
951
|
prepend: esquery.default.parse("CallExpression[arguments.length=1]:has(> :first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='prepend']))")
|
|
685
952
|
};
|
|
953
|
+
//#endregion
|
|
954
|
+
//#region src/rules/no-getState/no-getState.ts
|
|
686
955
|
var no_getState_default = createRule({
|
|
687
956
|
name: "no-getState",
|
|
688
957
|
meta: {
|
|
@@ -700,11 +969,11 @@ var no_getState_default = createRule({
|
|
|
700
969
|
return { [`CallExpression[callee.type="MemberExpression"][callee.property.name="getState"]`]: (node) => {
|
|
701
970
|
const type = services.getTypeAtLocation(node.callee.object);
|
|
702
971
|
if (!isType.store(type, services.program)) return;
|
|
703
|
-
const name
|
|
704
|
-
if (name
|
|
972
|
+
const name = nameOf.expression.simple(node.callee.object);
|
|
973
|
+
if (name) context.report({
|
|
705
974
|
node,
|
|
706
975
|
messageId: "named",
|
|
707
|
-
data: { name
|
|
976
|
+
data: { name }
|
|
708
977
|
});
|
|
709
978
|
else context.report({
|
|
710
979
|
node,
|
|
@@ -713,11 +982,8 @@ var no_getState_default = createRule({
|
|
|
713
982
|
} };
|
|
714
983
|
}
|
|
715
984
|
});
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && !node.computed) return node.property.name;
|
|
719
|
-
return null;
|
|
720
|
-
};
|
|
985
|
+
//#endregion
|
|
986
|
+
//#region src/rules/no-guard/no-guard.ts
|
|
721
987
|
var no_guard_default = createRule({
|
|
722
988
|
name: "no-guard",
|
|
723
989
|
meta: {
|
|
@@ -761,7 +1027,7 @@ var no_guard_default = createRule({
|
|
|
761
1027
|
]) config[key] = locate.property(key, arg)?.value;
|
|
762
1028
|
} else return;
|
|
763
1029
|
if (config.target) {
|
|
764
|
-
const [call] = esquery.default.match(config.target, query$1.prepend, { visitorKeys }).map((node
|
|
1030
|
+
const [call] = esquery.default.match(config.target, query$1.prepend, { visitorKeys }).map((node) => node).filter((node) => node === config.target);
|
|
765
1031
|
if (call) [config.target, config.fn] = [call.callee.object, call.arguments[0]];
|
|
766
1032
|
}
|
|
767
1033
|
const code = [
|
|
@@ -793,6 +1059,8 @@ const selector$6 = {
|
|
|
793
1059
|
call: `[callee.type="Identifier"]`
|
|
794
1060
|
};
|
|
795
1061
|
const query$1 = { prepend: esquery.default.parse("CallExpression[arguments.length=1]:has(:first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='prepend']))") };
|
|
1062
|
+
//#endregion
|
|
1063
|
+
//#region src/rules/no-patronum-debug/no-patronum-debug.ts
|
|
796
1064
|
var no_patronum_debug_default = createRule({
|
|
797
1065
|
name: "no-patronum-debug",
|
|
798
1066
|
meta: {
|
|
@@ -811,8 +1079,8 @@ var no_patronum_debug_default = createRule({
|
|
|
811
1079
|
return {
|
|
812
1080
|
[`${`ImportDeclaration[source.value=${PACKAGE_NAME}]`} > ${selector$5.debug}`]: (node) => debugs.add(node.local.name),
|
|
813
1081
|
[`CallExpression:matches(${selector$5.call})`]: (node) => {
|
|
814
|
-
const name
|
|
815
|
-
if (!debugs.has(name
|
|
1082
|
+
const name = toName(node);
|
|
1083
|
+
if (!debugs.has(name)) return;
|
|
816
1084
|
context.report({
|
|
817
1085
|
messageId: "unexpected",
|
|
818
1086
|
node: node.callee,
|
|
@@ -839,6 +1107,186 @@ const toName = (node) => {
|
|
|
839
1107
|
case _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression: return node.callee.object.name;
|
|
840
1108
|
}
|
|
841
1109
|
};
|
|
1110
|
+
//#endregion
|
|
1111
|
+
//#region src/rules/no-units-spawn-in-render/no-units-spawn-in-render.ts
|
|
1112
|
+
const EFFECTOR_FACTORIES = new Set([
|
|
1113
|
+
"createStore",
|
|
1114
|
+
"createEvent",
|
|
1115
|
+
"createEffect",
|
|
1116
|
+
"createDomain",
|
|
1117
|
+
"createApi",
|
|
1118
|
+
"restore"
|
|
1119
|
+
]);
|
|
1120
|
+
const EFFECTOR_OPERATORS = new Set([
|
|
1121
|
+
"sample",
|
|
1122
|
+
"guard",
|
|
1123
|
+
"forward",
|
|
1124
|
+
"merge",
|
|
1125
|
+
"split",
|
|
1126
|
+
"combine",
|
|
1127
|
+
"attach"
|
|
1128
|
+
]);
|
|
1129
|
+
const REACT_HOOKS_SPEC = {
|
|
1130
|
+
from: "package",
|
|
1131
|
+
package: "react",
|
|
1132
|
+
name: [
|
|
1133
|
+
"useState",
|
|
1134
|
+
"useEffect",
|
|
1135
|
+
"useLayoutEffect",
|
|
1136
|
+
"useCallback",
|
|
1137
|
+
"useMemo",
|
|
1138
|
+
"useRef",
|
|
1139
|
+
"useReducer",
|
|
1140
|
+
"useImperativeHandle",
|
|
1141
|
+
"useDebugValue",
|
|
1142
|
+
"useDeferredValue",
|
|
1143
|
+
"useTransition",
|
|
1144
|
+
"useId",
|
|
1145
|
+
"useSyncExternalStore",
|
|
1146
|
+
"useInsertionEffect",
|
|
1147
|
+
"useContext"
|
|
1148
|
+
]
|
|
1149
|
+
};
|
|
1150
|
+
const EFFECTOR_FACTORY_SPEC = {
|
|
1151
|
+
from: "package",
|
|
1152
|
+
package: "effector",
|
|
1153
|
+
name: [...EFFECTOR_FACTORIES]
|
|
1154
|
+
};
|
|
1155
|
+
const EFFECTOR_OPERATOR_SPEC = {
|
|
1156
|
+
from: "package",
|
|
1157
|
+
package: "effector",
|
|
1158
|
+
name: [...EFFECTOR_OPERATORS]
|
|
1159
|
+
};
|
|
1160
|
+
const EFFECTOR_FACTORIO_SHAPE = [
|
|
1161
|
+
"useModel",
|
|
1162
|
+
"createModel",
|
|
1163
|
+
"Provider",
|
|
1164
|
+
"@@unitShape"
|
|
1165
|
+
];
|
|
1166
|
+
var no_units_spawn_in_render_default = createRule({
|
|
1167
|
+
name: "no-units-spawn-in-render",
|
|
1168
|
+
meta: {
|
|
1169
|
+
type: "problem",
|
|
1170
|
+
docs: { description: "Forbid creating Effector units or calling operators inside React components or hooks." },
|
|
1171
|
+
messages: {
|
|
1172
|
+
noFactoryInRender: "Creating Effector units with \"{{ name }}\" inside React component or hook is forbidden, since it may cause memory leaks and other bugs.",
|
|
1173
|
+
noOperatorInRender: "Using Effector operator \"{{ name }}\" inside React component or hook is forbidden, since it may cause memory leaks and other bugs.",
|
|
1174
|
+
noCustomFactoryInRender: "Creating Effector units with \"{{ name }}\" inside React component or hook is forbidden, since it may cause memory leaks and other bugs. If this is a false positive, add \"{{ name }}\" to the allowlist in the detectCustomFactories option."
|
|
1175
|
+
},
|
|
1176
|
+
schema: [{
|
|
1177
|
+
type: "object",
|
|
1178
|
+
properties: { detectCustomFactories: { oneOf: [{ type: "boolean" }, {
|
|
1179
|
+
type: "object",
|
|
1180
|
+
properties: { allowlist: {
|
|
1181
|
+
type: "array",
|
|
1182
|
+
items: { type: "string" },
|
|
1183
|
+
uniqueItems: true
|
|
1184
|
+
} },
|
|
1185
|
+
required: ["allowlist"],
|
|
1186
|
+
additionalProperties: false
|
|
1187
|
+
}] } },
|
|
1188
|
+
additionalProperties: false
|
|
1189
|
+
}]
|
|
1190
|
+
},
|
|
1191
|
+
defaultOptions: [{ detectCustomFactories: true }],
|
|
1192
|
+
create: (context, [options]) => {
|
|
1193
|
+
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
|
|
1194
|
+
const checker = services.program.getTypeChecker();
|
|
1195
|
+
const { detectCustomFactories } = options;
|
|
1196
|
+
const allowlist = typeof detectCustomFactories === "object" ? new Set(detectCustomFactories.allowlist) : void 0;
|
|
1197
|
+
const stack = { render: [] };
|
|
1198
|
+
const effectorImports = /* @__PURE__ */ new Map();
|
|
1199
|
+
return {
|
|
1200
|
+
[`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`} > ImportSpecifier[imported.type="Identifier"]`]: (node) => {
|
|
1201
|
+
const imported = node.imported.name;
|
|
1202
|
+
const local = node.local.name;
|
|
1203
|
+
if (EFFECTOR_FACTORIES.has(imported)) effectorImports.set(local, "factory");
|
|
1204
|
+
else if (EFFECTOR_OPERATORS.has(imported)) effectorImports.set(local, "operator");
|
|
1205
|
+
},
|
|
1206
|
+
[`FunctionDeclaration, FunctionExpression, ArrowFunctionExpression`]: (node) => {
|
|
1207
|
+
if (stack.render.at(-1) ?? false) return void stack.render.push(true);
|
|
1208
|
+
const name = nameOf.function(node);
|
|
1209
|
+
if (name && UseRegex.test(name.name)) return void stack.render.push(true);
|
|
1210
|
+
const tsnode = services.esTreeNodeToTSNodeMap.get(node);
|
|
1211
|
+
const signature = checker.getSignatureFromDeclaration(tsnode);
|
|
1212
|
+
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getVoidType();
|
|
1213
|
+
if (returnType.isUnion() ? returnType.types.some((type) => isType.jsx(type, services.program)) : isType.jsx(returnType, services.program)) return void stack.render.push(true);
|
|
1214
|
+
const inferred = (0, typescript.isExpression)(tsnode) && (0, _typescript_eslint_type_utils.getContextualType)(checker, tsnode) || checker.getUnknownType();
|
|
1215
|
+
if (inferred.isUnion() ? inferred.types.some((type) => isType.component(type, services.program)) : isType.component(inferred, services.program)) return void stack.render.push(true);
|
|
1216
|
+
stack.render.push(false);
|
|
1217
|
+
},
|
|
1218
|
+
[`:matches(FunctionDeclaration, FunctionExpression, ArrowFunctionExpression):exit`]: () => void stack.render.pop(),
|
|
1219
|
+
"ClassDeclaration": () => void stack.render.push(false),
|
|
1220
|
+
"ClassDeclaration:exit": () => void stack.render.pop(),
|
|
1221
|
+
"CallExpression": (node) => {
|
|
1222
|
+
if (!(stack.render.at(-1) ?? false)) return;
|
|
1223
|
+
const calleeName = getCalleeName(node.callee);
|
|
1224
|
+
switch (calleeName ? effectorImports.get(calleeName) : void 0) {
|
|
1225
|
+
case "factory": return context.report({
|
|
1226
|
+
node,
|
|
1227
|
+
messageId: "noFactoryInRender",
|
|
1228
|
+
data: { name: calleeName }
|
|
1229
|
+
});
|
|
1230
|
+
case "operator": return context.report({
|
|
1231
|
+
node,
|
|
1232
|
+
messageId: "noOperatorInRender",
|
|
1233
|
+
data: { name: calleeName }
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
if (detectCustomFactories === false) return;
|
|
1237
|
+
const returnType = services.getTypeAtLocation(node);
|
|
1238
|
+
if (!hasEffectorUnitInType({
|
|
1239
|
+
node: services.esTreeNodeToTSNodeMap.get(node),
|
|
1240
|
+
checker,
|
|
1241
|
+
program: services.program
|
|
1242
|
+
}, returnType)) return;
|
|
1243
|
+
const calleeType = services.getTypeAtLocation(node.callee);
|
|
1244
|
+
const displayName = calleeName ?? "<expression>";
|
|
1245
|
+
if ((0, _typescript_eslint_type_utils.typeMatchesSpecifier)(calleeType, REACT_HOOKS_SPEC, services.program)) return;
|
|
1246
|
+
if (isEffectorFactorioHook(node.callee, services.getTypeAtLocation)) return;
|
|
1247
|
+
if ((0, _typescript_eslint_type_utils.typeMatchesSpecifier)(calleeType, EFFECTOR_FACTORY_SPEC, services.program)) return context.report({
|
|
1248
|
+
node,
|
|
1249
|
+
messageId: "noFactoryInRender",
|
|
1250
|
+
data: { name: displayName }
|
|
1251
|
+
});
|
|
1252
|
+
if ((0, _typescript_eslint_type_utils.typeMatchesSpecifier)(calleeType, EFFECTOR_OPERATOR_SPEC, services.program)) return context.report({
|
|
1253
|
+
node,
|
|
1254
|
+
messageId: "noOperatorInRender",
|
|
1255
|
+
data: { name: displayName }
|
|
1256
|
+
});
|
|
1257
|
+
if (allowlist && calleeName && allowlist.has(calleeName)) return;
|
|
1258
|
+
context.report({
|
|
1259
|
+
node,
|
|
1260
|
+
messageId: "noCustomFactoryInRender",
|
|
1261
|
+
data: { name: displayName }
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
const UseRegex = /^use[A-Z0-9].*$/;
|
|
1268
|
+
function getCalleeName(callee) {
|
|
1269
|
+
if (callee.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return callee.name;
|
|
1270
|
+
if (callee.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && callee.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return callee.property.name;
|
|
1271
|
+
else return null;
|
|
1272
|
+
}
|
|
1273
|
+
function hasEffectorUnitInType(ctx, type, depth = 3) {
|
|
1274
|
+
if (isType.unit(type, ctx.program)) return true;
|
|
1275
|
+
if (depth <= 0) return false;
|
|
1276
|
+
if (type.isUnion()) return type.types.some((type) => hasEffectorUnitInType(ctx, type, depth));
|
|
1277
|
+
for (const property of type.getProperties()) if (hasEffectorUnitInType(ctx, ctx.checker.getTypeOfSymbolAtLocation(property, ctx.node), depth - 1)) return true;
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
function isEffectorFactorioHook(callee, getTypeAtLocation) {
|
|
1281
|
+
if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression) return false;
|
|
1282
|
+
if (callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return false;
|
|
1283
|
+
if (callee.property.name !== "useModel") return false;
|
|
1284
|
+
const objectType = getTypeAtLocation(callee.object);
|
|
1285
|
+
const propertyNames = new Set(objectType.getProperties().map((p) => p.getName()));
|
|
1286
|
+
return EFFECTOR_FACTORIO_SHAPE.every((name) => propertyNames.has(name));
|
|
1287
|
+
}
|
|
1288
|
+
//#endregion
|
|
1289
|
+
//#region src/rules/no-unnecessary-combination/no-unnecessary-combination.ts
|
|
842
1290
|
var no_unnecessary_combination_default = createRule({
|
|
843
1291
|
name: "no-unnecessary-combination",
|
|
844
1292
|
meta: {
|
|
@@ -909,6 +1357,8 @@ function isFunction(node, services) {
|
|
|
909
1357
|
return checker.getTypeAtLocation(tsnode).getCallSignatures().length > 0;
|
|
910
1358
|
} else return false;
|
|
911
1359
|
}
|
|
1360
|
+
//#endregion
|
|
1361
|
+
//#region src/rules/no-unnecessary-duplication/no-unnecessary-duplication.ts
|
|
912
1362
|
var no_unnecessary_duplication_default = createRule({
|
|
913
1363
|
name: "no-unnecessary-duplication",
|
|
914
1364
|
meta: {
|
|
@@ -985,6 +1435,8 @@ function compare(clock, source, limit = 5) {
|
|
|
985
1435
|
}
|
|
986
1436
|
return false;
|
|
987
1437
|
}
|
|
1438
|
+
//#endregion
|
|
1439
|
+
//#region src/rules/no-useless-methods/no-useless-methods.ts
|
|
988
1440
|
var no_useless_methods_default = createRule({
|
|
989
1441
|
name: "no-useless-methods",
|
|
990
1442
|
meta: {
|
|
@@ -1019,10 +1471,8 @@ var no_useless_methods_default = createRule({
|
|
|
1019
1471
|
if (locate.property("target", config)?.value) return;
|
|
1020
1472
|
}
|
|
1021
1473
|
const grandparent = node.parent.parent;
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
if (esquery.default.matches(grandparent, query.watch, ancestry, { visitorKeys })) return;
|
|
1025
|
-
}
|
|
1474
|
+
const ancestry = source.getAncestors(grandparent);
|
|
1475
|
+
if (esquery.default.matches(grandparent, query.watch, ancestry, { visitorKeys })) return;
|
|
1026
1476
|
const method = node.callee.name;
|
|
1027
1477
|
context.report({
|
|
1028
1478
|
node,
|
|
@@ -1035,6 +1485,8 @@ var no_useless_methods_default = createRule({
|
|
|
1035
1485
|
});
|
|
1036
1486
|
const selector$2 = { method: `ImportSpecifier[imported.name=/(sample|guard)/]` };
|
|
1037
1487
|
const query = { watch: esquery.default.parse("CallExpression:has(> MemberExpression.callee[property.name=watch]:has(> CallExpression.object))") };
|
|
1488
|
+
//#endregion
|
|
1489
|
+
//#region src/rules/no-watch/no-watch.ts
|
|
1038
1490
|
var no_watch_default = createRule({
|
|
1039
1491
|
name: "no-watch",
|
|
1040
1492
|
meta: {
|
|
@@ -1056,6 +1508,8 @@ var no_watch_default = createRule({
|
|
|
1056
1508
|
} };
|
|
1057
1509
|
}
|
|
1058
1510
|
});
|
|
1511
|
+
//#endregion
|
|
1512
|
+
//#region src/rules/prefer-useUnit/prefer-useUnit.ts
|
|
1059
1513
|
var prefer_useUnit_default = createRule({
|
|
1060
1514
|
name: "prefer-useUnit",
|
|
1061
1515
|
meta: {
|
|
@@ -1087,6 +1541,8 @@ const selector$1 = {
|
|
|
1087
1541
|
useStore: `ImportSpecifier[imported.name=useStore]`,
|
|
1088
1542
|
useEvent: `ImportSpecifier[imported.name=useEvent]`
|
|
1089
1543
|
};
|
|
1544
|
+
//#endregion
|
|
1545
|
+
//#region src/rules/require-pickup-in-persist/require-pickup-in-persist.ts
|
|
1090
1546
|
var require_pickup_in_persist_default = createRule({
|
|
1091
1547
|
name: "require-pickup-in-persist",
|
|
1092
1548
|
meta: {
|
|
@@ -1116,6 +1572,8 @@ const selector = {
|
|
|
1116
1572
|
call: `[callee.type="Identifier"]`,
|
|
1117
1573
|
config: `[arguments.length=1][arguments.0.type="ObjectExpression"]`
|
|
1118
1574
|
};
|
|
1575
|
+
//#endregion
|
|
1576
|
+
//#region src/rules/strict-effect-handlers/strict-effect-handlers.ts
|
|
1119
1577
|
var strict_effect_handlers_default = createRule({
|
|
1120
1578
|
name: "strict-effect-handlers",
|
|
1121
1579
|
meta: {
|
|
@@ -1143,9 +1601,8 @@ var strict_effect_handlers_default = createRule({
|
|
|
1143
1601
|
});
|
|
1144
1602
|
};
|
|
1145
1603
|
const exit = (node) => {
|
|
1146
|
-
const scope
|
|
1147
|
-
if (
|
|
1148
|
-
if (scope$1.effect && scope$1.regular) context.report({
|
|
1604
|
+
const scope = stack.pop();
|
|
1605
|
+
if (scope.effect && scope.regular) context.report({
|
|
1149
1606
|
node,
|
|
1150
1607
|
messageId: "mixed"
|
|
1151
1608
|
});
|
|
@@ -1161,38 +1618,37 @@ var strict_effect_handlers_default = createRule({
|
|
|
1161
1618
|
};
|
|
1162
1619
|
}
|
|
1163
1620
|
});
|
|
1164
|
-
const recommended = {
|
|
1165
|
-
"effector/enforce-effect-naming-convention": "error",
|
|
1166
|
-
"effector/enforce-store-naming-convention": "error",
|
|
1167
|
-
"effector/keep-options-order": "warn",
|
|
1168
|
-
"effector/no-ambiguity-target": "warn",
|
|
1169
|
-
"effector/no-duplicate-on": "error",
|
|
1170
|
-
"effector/no-forward": "error",
|
|
1171
|
-
"effector/no-getState": "error",
|
|
1172
|
-
"effector/no-guard": "error",
|
|
1173
|
-
"effector/no-unnecessary-combination": "warn",
|
|
1174
|
-
"effector/no-unnecessary-duplication": "warn",
|
|
1175
|
-
"effector/no-useless-methods": "error",
|
|
1176
|
-
"effector/no-watch": "warn"
|
|
1177
|
-
};
|
|
1178
|
-
const patronum = { "effector/no-patronum-debug": "warn" };
|
|
1179
|
-
const scope = {
|
|
1180
|
-
"effector/require-pickup-in-persist": "error",
|
|
1181
|
-
"effector/strict-effect-handlers": "error"
|
|
1182
|
-
};
|
|
1183
|
-
const react = {
|
|
1184
|
-
"effector/enforce-gate-naming-convention": "error",
|
|
1185
|
-
"effector/mandatory-scope-binding": "error",
|
|
1186
|
-
"effector/prefer-useUnit": "error"
|
|
1187
|
-
};
|
|
1188
|
-
const future = { "effector/no-domain-unit-creators": "warn" };
|
|
1189
1621
|
const ruleset = {
|
|
1190
|
-
recommended
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1622
|
+
recommended: {
|
|
1623
|
+
"effector/enforce-effect-naming-convention": "error",
|
|
1624
|
+
"effector/enforce-store-naming-convention": "error",
|
|
1625
|
+
"effector/keep-options-order": "warn",
|
|
1626
|
+
"effector/no-ambiguity-target": "warn",
|
|
1627
|
+
"effector/no-duplicate-on": "error",
|
|
1628
|
+
"effector/no-forward": "error",
|
|
1629
|
+
"effector/no-getState": "error",
|
|
1630
|
+
"effector/no-guard": "error",
|
|
1631
|
+
"effector/no-unnecessary-combination": "warn",
|
|
1632
|
+
"effector/no-unnecessary-duplication": "warn",
|
|
1633
|
+
"effector/no-useless-methods": "error",
|
|
1634
|
+
"effector/no-watch": "warn"
|
|
1635
|
+
},
|
|
1636
|
+
patronum: { "effector/no-patronum-debug": "warn" },
|
|
1637
|
+
scope: {
|
|
1638
|
+
"effector/require-pickup-in-persist": "error",
|
|
1639
|
+
"effector/strict-effect-handlers": "error"
|
|
1640
|
+
},
|
|
1641
|
+
react: {
|
|
1642
|
+
"effector/enforce-gate-naming-convention": "error",
|
|
1643
|
+
"effector/enforce-exhaustive-useUnit-destructuring": "warn",
|
|
1644
|
+
"effector/mandatory-scope-binding": "error",
|
|
1645
|
+
"effector/no-units-spawn-in-render": "error",
|
|
1646
|
+
"effector/prefer-useUnit": "error"
|
|
1647
|
+
},
|
|
1648
|
+
future: { "effector/no-domain-unit-creators": "warn" }
|
|
1195
1649
|
};
|
|
1650
|
+
//#endregion
|
|
1651
|
+
//#region src/index.ts
|
|
1196
1652
|
const base = {
|
|
1197
1653
|
meta: {
|
|
1198
1654
|
name,
|
|
@@ -1201,6 +1657,7 @@ const base = {
|
|
|
1201
1657
|
},
|
|
1202
1658
|
rules: {
|
|
1203
1659
|
"enforce-effect-naming-convention": enforce_effect_naming_convention_default,
|
|
1660
|
+
"enforce-exhaustive-useUnit-destructuring": enforce_exhaustive_useUnit_destructuring_default,
|
|
1204
1661
|
"enforce-gate-naming-convention": enforce_gate_naming_convention_default,
|
|
1205
1662
|
"enforce-store-naming-convention": enforce_store_naming_convention_default,
|
|
1206
1663
|
"keep-options-order": keep_options_order_default,
|
|
@@ -1213,6 +1670,7 @@ const base = {
|
|
|
1213
1670
|
"no-getState": no_getState_default,
|
|
1214
1671
|
"no-guard": no_guard_default,
|
|
1215
1672
|
"no-patronum-debug": no_patronum_debug_default,
|
|
1673
|
+
"no-units-spawn-in-render": no_units_spawn_in_render_default,
|
|
1216
1674
|
"no-unnecessary-combination": no_unnecessary_combination_default,
|
|
1217
1675
|
"no-unnecessary-duplication": no_unnecessary_duplication_default,
|
|
1218
1676
|
"no-useless-methods": no_useless_methods_default,
|
|
@@ -1255,5 +1713,5 @@ const flatConfigs = {
|
|
|
1255
1713
|
const plugin = base;
|
|
1256
1714
|
plugin.configs = legacyConfigs;
|
|
1257
1715
|
plugin.flatConfigs = flatConfigs;
|
|
1258
|
-
|
|
1259
|
-
module.exports =
|
|
1716
|
+
//#endregion
|
|
1717
|
+
module.exports = plugin;
|