eslint-plugin-effector 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +24 -37
  2. package/dist/index.cjs +1259 -0
  3. package/dist/index.d.cts +177 -0
  4. package/dist/index.d.mts +178 -0
  5. package/dist/index.mjs +1233 -0
  6. package/package.json +71 -17
  7. package/.nvmrc +0 -1
  8. package/config/future.js +0 -7
  9. package/config/patronum.js +0 -5
  10. package/config/react.js +0 -7
  11. package/config/recommended.js +0 -15
  12. package/config/scope.js +0 -6
  13. package/index.js +0 -31
  14. package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +0 -143
  15. package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.js +0 -122
  16. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +0 -205
  17. package/rules/keep-options-order/config.js +0 -3
  18. package/rules/keep-options-order/keep-options-order.js +0 -107
  19. package/rules/mandatory-scope-binding/mandatory-scope-binding.js +0 -81
  20. package/rules/no-ambiguity-target/no-ambiguity-target.js +0 -74
  21. package/rules/no-duplicate-clock-or-source-array-values/no-duplicate-clock-or-source-array-values.js +0 -120
  22. package/rules/no-duplicate-on/no-duplicate-on.js +0 -137
  23. package/rules/no-forward/no-forward.js +0 -73
  24. package/rules/no-getState/no-getState.js +0 -50
  25. package/rules/no-guard/no-guard.js +0 -78
  26. package/rules/no-patronum-debug/no-patronum-debug.js +0 -133
  27. package/rules/no-unnecessary-combination/no-unnecessary-combination.js +0 -88
  28. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +0 -115
  29. package/rules/no-useless-methods/no-useless-methods.js +0 -93
  30. package/rules/no-watch/no-watch.js +0 -61
  31. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +0 -111
  32. package/rules/prefer-useUnit/prefer-useUnit.js +0 -56
  33. package/rules/require-pickup-in-persist/require-pickup-in-persist.js +0 -47
  34. package/rules/strict-effect-handlers/strict-effect-handlers.js +0 -76
  35. package/utils/are-nodes-same-in-text.js +0 -22
  36. package/utils/builders.js +0 -19
  37. package/utils/create-link-to-rule.js +0 -5
  38. package/utils/extract-config.js +0 -26
  39. package/utils/extract-imported-from.js +0 -18
  40. package/utils/get-corrected-store-name.js +0 -45
  41. package/utils/get-nested-object-name.js +0 -18
  42. package/utils/get-store-name-convention.js +0 -6
  43. package/utils/is.js +0 -39
  44. package/utils/method.js +0 -23
  45. package/utils/naming.js +0 -47
  46. package/utils/node-is-type.js +0 -5
  47. package/utils/node-type-is.js +0 -106
  48. package/utils/react.js +0 -214
  49. package/utils/read-example.js +0 -63
  50. package/utils/replace-by-sample.js +0 -98
  51. package/utils/traverse-nested-object-node.js +0 -9
  52. package/utils/traverse-parent-by-type.js +0 -15
  53. package/utils/validate-store-name-convention.js +0 -13
package/dist/index.cjs ADDED
@@ -0,0 +1,1259 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __copyProps = (to, from, except, desc) => {
8
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
9
+ key = keys[i];
10
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
11
+ get: ((k) => from[k]).bind(null, key),
12
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
13
+ });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
18
+ value: mod,
19
+ enumerable: true
20
+ }) : target, mod));
21
+ let _typescript_eslint_utils = require("@typescript-eslint/utils");
22
+ let _typescript_eslint_type_utils = require("@typescript-eslint/type-utils");
23
+ let typescript = require("typescript");
24
+ let esquery = require("esquery");
25
+ esquery = __toESM(esquery);
26
+ var name = "eslint-plugin-effector";
27
+ var version = "0.17.0";
28
+ const createRule = _typescript_eslint_utils.ESLintUtils.RuleCreator((name$1) => `https://eslint.effector.dev/rules/${name$1}`);
29
+ const check = (symbol, types, from) => {
30
+ const name$1 = symbol.getName();
31
+ const declarations = symbol.declarations ?? [];
32
+ return types.includes(name$1) && declarations.map((decl) => decl.getSourceFile().fileName).some((fname) => fname.includes("node_modules") && fname.includes(from));
33
+ };
34
+ const isType = {
35
+ store: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
36
+ from: "package",
37
+ package: "effector",
38
+ name: ["Store", "StoreWritable"]
39
+ }, program),
40
+ event: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
41
+ from: "package",
42
+ package: "effector",
43
+ name: ["Event", "EventCallable"]
44
+ }, program),
45
+ effect: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
46
+ from: "package",
47
+ package: "effector",
48
+ name: "Effect"
49
+ }, program),
50
+ unit: (type, program) => {
51
+ return (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
52
+ from: "package",
53
+ package: "effector",
54
+ name: [
55
+ "Store",
56
+ "StoreWritable",
57
+ "Event",
58
+ "EventCallable",
59
+ "Effect"
60
+ ]
61
+ }, program);
62
+ },
63
+ domain: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
64
+ from: "package",
65
+ package: "effector",
66
+ name: "Domain"
67
+ }, program),
68
+ gate: (type) => {
69
+ const symbol = type.getSymbol() ?? type.aliasSymbol;
70
+ return symbol ? check(symbol, ["Gate"], "effector") : false;
71
+ },
72
+ jsx: (type, program) => {
73
+ return (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
74
+ from: "package",
75
+ package: "react",
76
+ name: [
77
+ "Element",
78
+ "ReactNode",
79
+ "ReactElement"
80
+ ]
81
+ }, program);
82
+ },
83
+ component: (type, program) => {
84
+ return (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
85
+ from: "package",
86
+ package: "react",
87
+ name: [
88
+ "FC",
89
+ "FunctionComponent",
90
+ "ComponentType",
91
+ "ComponentClass",
92
+ "ForwardRefRenderFunction"
93
+ ]
94
+ }, program);
95
+ }
96
+ };
97
+ var enforce_effect_naming_convention_default = createRule({
98
+ name: "enforce-effect-naming-convention",
99
+ meta: {
100
+ type: "problem",
101
+ docs: { description: "Enforce Fx as a suffix for any Effector Effect." },
102
+ messages: {
103
+ invalid: "Effect `{{ current }}` should be named with suffix, rename it to `{{ fixed }}`",
104
+ rename: "Rename \"{{ current }}\" to \"{{ fixed }}\""
105
+ },
106
+ schema: [],
107
+ hasSuggestions: true
108
+ },
109
+ defaultOptions: [],
110
+ create: (context) => {
111
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
112
+ return { [`VariableDeclarator[id.name!=${FxRegex}]`]: (node) => {
113
+ const type = services.getTypeAtLocation(node);
114
+ if (!isType.effect(type, services.program)) return;
115
+ const current = node.id.name;
116
+ const fixed = current + "Fx";
117
+ const data = {
118
+ current,
119
+ fixed
120
+ };
121
+ const suggestion = {
122
+ messageId: "rename",
123
+ data: {
124
+ current,
125
+ fixed
126
+ },
127
+ fix: (fixer) => fixer.replaceText(node.id, fixed)
128
+ };
129
+ context.report({
130
+ node: node.id,
131
+ messageId: "invalid",
132
+ data,
133
+ suggest: [suggestion]
134
+ });
135
+ } };
136
+ }
137
+ });
138
+ const FxRegex = /Fx$/;
139
+ var enforce_gate_naming_convention_default = createRule({
140
+ name: "enforce-gate-naming-convention",
141
+ meta: {
142
+ type: "problem",
143
+ docs: { description: "Enforce a Gate is named capitalized like a React Component" },
144
+ messages: {
145
+ invalid: "Gate \"{{ current }}\" should be named with first capital letter, rename it to \"{{ fixed }}\"",
146
+ rename: "Rename \"{{ current }}\" to \"{{ fixed }}\""
147
+ },
148
+ schema: [],
149
+ hasSuggestions: true
150
+ },
151
+ defaultOptions: [],
152
+ create: (context) => {
153
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
154
+ return { [`VariableDeclarator[id.name=${GateRegex}]`]: (node) => {
155
+ const type = services.getTypeAtLocation(node);
156
+ if (!isType.gate(type)) return;
157
+ const current = node.id.name;
158
+ const fixed = current[0].toUpperCase() + current.slice(1);
159
+ const data = {
160
+ current,
161
+ fixed
162
+ };
163
+ const suggestion = {
164
+ messageId: "rename",
165
+ data: {
166
+ current,
167
+ fixed
168
+ },
169
+ fix: (fixer) => fixer.replaceText(node.id, fixed)
170
+ };
171
+ context.report({
172
+ node: node.id,
173
+ messageId: "invalid",
174
+ data,
175
+ suggest: [suggestion]
176
+ });
177
+ } };
178
+ }
179
+ });
180
+ const GateRegex = /^[^A-Z]/;
181
+ var enforce_store_naming_convention_default = createRule({
182
+ name: "enforce-store-naming-convention",
183
+ meta: {
184
+ type: "problem",
185
+ docs: { description: "Enforce $ as a prefix/postfix for any Effector `Store`" },
186
+ messages: {
187
+ invalid: "Store \"{{ current }}\" should be named with a `$` {{ convention }}, rename it to \"{{ fixed }}\"",
188
+ rename: "Rename \"{{ current }}\" to \"{{ fixed }}\""
189
+ },
190
+ schema: [{
191
+ type: "object",
192
+ properties: { mode: {
193
+ type: "string",
194
+ enum: ["prefix", "postfix"]
195
+ } }
196
+ }],
197
+ hasSuggestions: true
198
+ },
199
+ defaultOptions: [{ mode: "prefix" }],
200
+ create: (context, [options]) => {
201
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
202
+ return { [`VariableDeclarator[id.name=${options.mode === "prefix" ? PrefixRegex : PostfixRegex}]`]: (node) => {
203
+ const type = services.getTypeAtLocation(node);
204
+ if (!isType.store(type, services.program)) return;
205
+ const current = node.id.name;
206
+ const trimmed = current.replaceAll(options.mode === "prefix" ? /\$+$/g : /^\$+/g, "");
207
+ const fixed = options.mode === "prefix" ? `$${trimmed}` : `${trimmed}$`;
208
+ const data = {
209
+ current,
210
+ convention: options.mode,
211
+ fixed
212
+ };
213
+ const suggestion = {
214
+ messageId: "rename",
215
+ data: {
216
+ current,
217
+ fixed
218
+ },
219
+ fix: (fixer) => fixer.replaceText(node.id, fixed)
220
+ };
221
+ context.report({
222
+ node: node.id,
223
+ messageId: "invalid",
224
+ data,
225
+ suggest: [suggestion]
226
+ });
227
+ } };
228
+ }
229
+ });
230
+ const PrefixRegex = /^[^$]/;
231
+ const PostfixRegex = /[^$]$/;
232
+ const PACKAGE_NAME$1 = {
233
+ core: /^effector(?:\u002Fcompat)?$/,
234
+ react: /^effector-react$/,
235
+ storage: /^@?effector-storage(\u002F[\w-]+)*$/
236
+ };
237
+ var keep_options_order_default = createRule({
238
+ name: "keep-options-order",
239
+ meta: {
240
+ type: "problem",
241
+ docs: { description: "Enforce options order for Effector methods" },
242
+ messages: {
243
+ invalidOrder: `Order of options should be \`{{ correctOrder }}\`, but found \`{{ currentOrder }}\`.`,
244
+ changeOrder: "Sort options to follow the recommended order."
245
+ },
246
+ schema: [],
247
+ hasSuggestions: true
248
+ },
249
+ defaultOptions: [],
250
+ create: (context) => {
251
+ const source = context.sourceCode;
252
+ const imports = /* @__PURE__ */ new Set();
253
+ return {
254
+ [`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`} > ${selector$11.method}`]: (node) => imports.add(node.local.name),
255
+ [`CallExpression${selector$11.call}:has(${selector$11.argument})`]: (node) => {
256
+ if (!imports.has(node.callee.name)) return;
257
+ const [config] = node.arguments;
258
+ 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;
259
+ const properties = config.properties;
260
+ const current = properties.map((prop) => prop.key.name);
261
+ if (isCorrectOrder(current)) return;
262
+ const correctOrder = TRUE_ORDER.filter((item) => current.includes(item));
263
+ const othersOrder = current.filter((item) => !TRUE_ORDER.includes(item));
264
+ const order = [...correctOrder, ...othersOrder];
265
+ const snippets = properties.toSorted((a, b) => order.indexOf(a.key.name) - order.indexOf(b.key.name)).map((prop) => source.getText(prop));
266
+ const suggestion = {
267
+ messageId: "changeOrder",
268
+ fix: (fixer) => [fixer.replaceText(config, `{ ${snippets.join(", ")} }`)]
269
+ };
270
+ const data = {
271
+ correctOrder: correctOrder.join(" -> "),
272
+ currentOrder: current.join(" -> ")
273
+ };
274
+ context.report({
275
+ node: config,
276
+ messageId: "invalidOrder",
277
+ data,
278
+ suggest: [suggestion]
279
+ });
280
+ }
281
+ };
282
+ }
283
+ });
284
+ const TRUE_ORDER = [
285
+ "clock",
286
+ "source",
287
+ "filter",
288
+ "fn",
289
+ "target",
290
+ "greedy",
291
+ "batch",
292
+ "name"
293
+ ];
294
+ const selector$11 = {
295
+ method: `ImportSpecifier[imported.name=/(sample|guard)/]`,
296
+ call: `[callee.type="Identifier"][arguments.length=1]`,
297
+ argument: `ObjectExpression.arguments`
298
+ };
299
+ const isCorrectOrder = (current) => {
300
+ let seen = -1;
301
+ for (const item of current) {
302
+ const index = TRUE_ORDER.indexOf(item);
303
+ const placement = index === -1 ? Infinity : index;
304
+ if (placement <= seen) return false;
305
+ seen = placement;
306
+ }
307
+ return true;
308
+ };
309
+ function functionToName(node) {
310
+ if (node.id) return node.id;
311
+ 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;
312
+ if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.AssignmentExpression && node.parent.left.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.parent.left;
313
+ if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.Property && node.parent.key.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.parent.key;
314
+ 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
+ return null;
316
+ }
317
+ const nameOf = { function: functionToName };
318
+ var mandatory_scope_binding_default = createRule({
319
+ name: "mandatory-scope-binding",
320
+ meta: {
321
+ type: "problem",
322
+ docs: { description: "Forbid `Event` and `Effect` usage without `useUnit` in React components." },
323
+ messages: { useUnitNeeded: "\"{{ name }}\" must be wrapped with `useUnit` from `effector-react` before usage inside React components." },
324
+ schema: []
325
+ },
326
+ defaultOptions: [],
327
+ create: (context) => {
328
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
329
+ const checker = services.program.getTypeChecker();
330
+ const stack = {
331
+ render: [],
332
+ hook: []
333
+ };
334
+ return {
335
+ [`FunctionDeclaration, FunctionExpression, ArrowFunctionExpression`]: (node) => {
336
+ if (stack.render.at(-1) ?? false) return void stack.render.push(true);
337
+ const name$1 = nameOf.function(node);
338
+ if (name$1 && UseRegex.test(name$1.name)) return void stack.render.push(true);
339
+ const tsnode = services.esTreeNodeToTSNodeMap.get(node);
340
+ const signature = checker.getSignatureFromDeclaration(tsnode);
341
+ const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getVoidType();
342
+ if (isType.jsx(returnType, services.program)) return void stack.render.push(true);
343
+ const inferred = (0, typescript.isExpression)(tsnode) ? (0, _typescript_eslint_type_utils.getContextualType)(checker, tsnode) : void 0;
344
+ if (inferred ? isType.component(inferred, services.program) : false) return void stack.render.push(true);
345
+ stack.render.push(false);
346
+ },
347
+ [`:matches(FunctionDeclaration, FunctionExpression, ArrowFunctionExpression):exit`]: () => void stack.render.pop(),
348
+ "ClassDeclaration": () => void stack.render.push(false),
349
+ "ClassDeclaration:exit": () => void stack.render.pop(),
350
+ "CallExpression": (node) => {
351
+ const isHook = (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(services.getTypeAtLocation(node.callee), {
352
+ from: "package",
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);
363
+ },
364
+ "Identifier": (node) => {
365
+ if (!(stack.render.at(-1) ?? false)) return;
366
+ if (stack.hook.at(-1) ?? false) return;
367
+ const type = services.getTypeAtLocation(node);
368
+ if (!isType.event(type, services.program) && !isType.effect(type, services.program)) return;
369
+ context.report({
370
+ node,
371
+ messageId: "useUnitNeeded",
372
+ data: { name: node.name }
373
+ });
374
+ }
375
+ };
376
+ }
377
+ });
378
+ const UseRegex = /^use[A-Z0-9].*$/;
379
+ 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
+ const locate = { property };
381
+ var no_ambiguity_target_default = createRule({
382
+ name: "no-ambiguity-target",
383
+ meta: {
384
+ type: "problem",
385
+ docs: { description: "Forbid ambiguous target in `sample` and `guard`." },
386
+ messages: { ambiguous: "Method `{{ method }}` both specifies `target` option and assigns the result to a variable. Consider removing one of them." },
387
+ schema: []
388
+ },
389
+ defaultOptions: [],
390
+ create: (context) => {
391
+ const imports = /* @__PURE__ */ new Set();
392
+ const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`;
393
+ const usageStack = [];
394
+ return {
395
+ "ReturnStatement": () => usageStack.push(true),
396
+ "ReturnStatement:exit": () => usageStack.pop(),
397
+ "VariableDeclarator": () => usageStack.push(true),
398
+ "VariableDeclarator:exit": () => usageStack.pop(),
399
+ "ObjectExpression": () => usageStack.push(true),
400
+ "ObjectExpression:exit": () => usageStack.pop(),
401
+ "BlockStatement": () => usageStack.push(false),
402
+ "BlockStatement:exit": () => usageStack.pop(),
403
+ [`${importSelector} > ${selector$10.method}`]: (node) => imports.add(node.local.name),
404
+ [`CallExpression[callee.type="Identifier"]`]: (node) => {
405
+ if (!imports.has(node.callee.name)) return;
406
+ if (!(usageStack.at(-1) ?? false)) return;
407
+ const [config] = node.arguments;
408
+ if (config?.type !== _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression) return;
409
+ if (!locate.property("target", config)) return;
410
+ context.report({
411
+ node,
412
+ messageId: "ambiguous",
413
+ data: { method: node.callee.name }
414
+ });
415
+ }
416
+ };
417
+ }
418
+ });
419
+ const selector$10 = { method: `ImportSpecifier[imported.name=/(sample|guard)/]` };
420
+ var no_domain_unit_creators_default = createRule({
421
+ name: "no-domain-unit-creators",
422
+ meta: {
423
+ type: "suggestion",
424
+ docs: { description: "Disallow using Domain methods to create units." },
425
+ messages: { avoid: "Avoid using `.{{ method }}` on a Domain instance. Use a standard factory unit creator `{{ factory }}` with a `domain` option instead." },
426
+ schema: []
427
+ },
428
+ defaultOptions: [],
429
+ create: (context) => {
430
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
431
+ return { [`CallExpression:has(> ${selector$9.member})`]: (node) => {
432
+ const name$1 = node.callee.property.name;
433
+ if (!METHODS.has(name$1)) return;
434
+ const type = services.getTypeAtLocation(node.callee.object);
435
+ if (!isType.domain(type, services.program)) return;
436
+ const factory = ALIAS_MAP.get(name$1) ?? name$1;
437
+ context.report({
438
+ node,
439
+ messageId: "avoid",
440
+ data: {
441
+ method: name$1,
442
+ factory
443
+ }
444
+ });
445
+ } };
446
+ }
447
+ });
448
+ const ALIAS_MAP = (/* @__PURE__ */ new Map()).set("event", "createEvent").set("store", "createStore").set("effect", "createEffect").set("domain", "createDomain");
449
+ const METHODS = new Set([...ALIAS_MAP.values(), ...ALIAS_MAP.keys()]);
450
+ const selector$9 = { member: `MemberExpression.callee[property.type="Identifier"]` };
451
+ var no_duplicate_clock_or_source_array_values_default = createRule({
452
+ name: "no-duplicate-clock-or-source-array-values",
453
+ meta: {
454
+ type: "problem",
455
+ docs: { description: "Forbid providing duplicate units in `clock` and `source` arrays in `sample` and `guard`." },
456
+ messages: {
457
+ duplicate: "`{{ field }}` contains a duplicate unit `{{ unit }}`.",
458
+ remove: "Remove duplicate unit `{{ unit }}`."
459
+ },
460
+ schema: [],
461
+ hasSuggestions: true
462
+ },
463
+ defaultOptions: [],
464
+ create: (context) => {
465
+ const imports = /* @__PURE__ */ new Set();
466
+ const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`;
467
+ const analyze = (node, field) => {
468
+ const seen = /* @__PURE__ */ new Map();
469
+ const entries = node.elements.filter((item) => item !== null).filter((item) => item.type !== _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement);
470
+ for (const entry of entries) {
471
+ const root = traverseToRoot$1(entry);
472
+ if (!root) continue;
473
+ const name$1 = [root.node.name, ...root.path].join(".");
474
+ if (seen.has(name$1)) report(entry, name$1, field);
475
+ else seen.set(name$1, entry);
476
+ }
477
+ };
478
+ const report = (node, name$1, field) => {
479
+ const data = {
480
+ field,
481
+ unit: name$1
482
+ };
483
+ const suggestion = {
484
+ messageId: "remove",
485
+ data: { unit: name$1 },
486
+ fix: function* (fixer) {
487
+ yield fixer.remove(node);
488
+ const before = context.sourceCode.getTokenBefore(node);
489
+ if (before?.value === ",") yield fixer.remove(before);
490
+ }
491
+ };
492
+ context.report({
493
+ node,
494
+ messageId: "duplicate",
495
+ data,
496
+ suggest: [suggestion]
497
+ });
498
+ };
499
+ return {
500
+ [`${importSelector} > ${selector$8.method}`]: (node) => imports.add(node.local.name),
501
+ [`CallExpression${selector$8.call}:has(${selector$8.argument})`]: (node) => {
502
+ if (!imports.has(node.callee.name)) return;
503
+ const [config] = node.arguments;
504
+ const clock = locate.property("clock", config);
505
+ const source = locate.property("source", config);
506
+ if (clock?.value?.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression) analyze(clock.value, "clock");
507
+ if (source?.value?.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression) analyze(source.value, "source");
508
+ }
509
+ };
510
+ }
511
+ });
512
+ const selector$8 = {
513
+ method: `ImportSpecifier[imported.name=/(sample|guard)/]`,
514
+ call: `[callee.type="Identifier"][arguments.length=1]`,
515
+ argument: `ObjectExpression.arguments`
516
+ };
517
+ function traverseToRoot$1(node, path = []) {
518
+ if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return {
519
+ node,
520
+ path
521
+ };
522
+ 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
+ return null;
524
+ }
525
+ var no_duplicate_on_default = createRule({
526
+ name: "no-duplicate-on",
527
+ meta: {
528
+ type: "problem",
529
+ docs: { description: "Forbid duplicate `.on` calls on Stores." },
530
+ messages: { duplicate: "Method `.on` is called on store `{{ store }}` more than once for `{{ unit }}`." },
531
+ schema: []
532
+ },
533
+ defaultOptions: [],
534
+ create: (context) => {
535
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
536
+ const map = /* @__PURE__ */ new Map();
537
+ return { [`CallExpression[callee.property.name="on"]`]: (node) => {
538
+ const type = services.getTypeAtLocation(node.callee.object);
539
+ if (!isType.store(type, services.program)) return;
540
+ const arg = node.arguments[0];
541
+ if (!arg || arg.type === _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) return;
542
+ 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$1 = context.sourceCode.getScope(node);
544
+ const store = identify("store", node.callee.object, scope$1);
545
+ if (!store) return;
546
+ const set = map.get(store.id) ?? /* @__PURE__ */ new Set();
547
+ for (const unit of units) {
548
+ const instance = identify("unit", unit, scope$1);
549
+ if (!instance) continue;
550
+ if (set.has(instance.id)) {
551
+ const data = {
552
+ store: store.name,
553
+ unit: instance.name
554
+ };
555
+ context.report({
556
+ messageId: "duplicate",
557
+ node: unit,
558
+ data
559
+ });
560
+ } else set.add(instance.id);
561
+ }
562
+ map.set(store.id, set);
563
+ } };
564
+ }
565
+ });
566
+ function traverseToRoot(node, path = []) {
567
+ if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return {
568
+ node,
569
+ path
570
+ };
571
+ if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && node.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return traverseToRoot(node.object, [node.property.name, ...path]);
572
+ return null;
573
+ }
574
+ const STORE_METHODS = ["on", "reset"];
575
+ function traverseStoreToRoot(node, path = []) {
576
+ if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return {
577
+ node,
578
+ path
579
+ };
580
+ if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && node.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return traverseStoreToRoot(node.object, [node.property.name, ...path]);
581
+ if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.CallExpression && node.callee.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression) {
582
+ if (node.callee.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && STORE_METHODS.includes(node.callee.property.name)) return traverseStoreToRoot(node.callee.object, path);
583
+ }
584
+ return null;
585
+ }
586
+ function raiseStoreToVariable(node) {
587
+ let current = node;
588
+ while (current.parent) {
589
+ if (current.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.VariableDeclarator) return current.parent;
590
+ if (current.parent.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || current.parent.object !== current) return null;
591
+ if (current.parent.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !STORE_METHODS.includes(current.parent.property.name)) return null;
592
+ const grandparent = current.parent.parent;
593
+ if (grandparent?.type !== _typescript_eslint_utils.AST_NODE_TYPES.CallExpression || grandparent.callee !== current.parent) return null;
594
+ current = current.parent.parent;
595
+ }
596
+ return null;
597
+ }
598
+ function findSuitableRoot(type, node) {
599
+ if (type === "unit") return traverseToRoot(node);
600
+ const root = traverseStoreToRoot(node);
601
+ if (root) return root;
602
+ const declarator = raiseStoreToVariable(node);
603
+ if (declarator && declarator.id.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return {
604
+ node: declarator.id,
605
+ path: []
606
+ };
607
+ return null;
608
+ }
609
+ function identify(type, node, scope$1) {
610
+ const root = findSuitableRoot(type, node);
611
+ if (!root) return null;
612
+ const variable = _typescript_eslint_utils.ASTUtils.findVariable(scope$1, root.node);
613
+ if (!variable) return null;
614
+ return {
615
+ id: `${variable.$id}+${root.path.join(".")}`,
616
+ name: [variable.name, ...root.path].join(".")
617
+ };
618
+ }
619
+ var no_forward_default = createRule({
620
+ name: "no-forward",
621
+ meta: {
622
+ type: "problem",
623
+ docs: { description: "Prefer `sample` over `forward`." },
624
+ messages: {
625
+ noForward: "Use `sample` operator instead of `forward` as a more universal approach.",
626
+ replaceWithSample: "Replace `forward` with `sample`."
627
+ },
628
+ hasSuggestions: true,
629
+ schema: []
630
+ },
631
+ defaultOptions: [],
632
+ create: (context) => {
633
+ let sample;
634
+ const forwards = /* @__PURE__ */ new Map();
635
+ const source = context.sourceCode;
636
+ const visitorKeys = source.visitorKeys;
637
+ const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`;
638
+ return {
639
+ [`${importSelector} > ${selector$7.forward}`]: (node) => forwards.set(node.local.name, node),
640
+ [`${importSelector} > ${selector$7.sample}`]: (node) => sample = node.local.name,
641
+ [`CallExpression${selector$7.call}:has(${selector$7.argument})`]: (node) => {
642
+ if (!forwards.has(node.callee.name)) return;
643
+ const config = {};
644
+ const arg = node.arguments[0];
645
+ config.clock = locate.property("from", arg)?.value;
646
+ config.target = locate.property("to", arg)?.value;
647
+ if (config.target) {
648
+ const [call] = esquery.default.match(config.target, query$2.prepend, { visitorKeys }).map((node$1) => node$1).filter((node$1) => node$1 === config.target);
649
+ if (call) [config.target, config.fn] = [call.callee.object, call.arguments[0]];
650
+ }
651
+ if (config.clock && !config.fn) {
652
+ const [call] = esquery.default.match(config.clock, query$2.map, { visitorKeys }).map((node$1) => node$1).filter((node$1) => node$1 === config.clock);
653
+ if (call) [config.clock, config.fn] = [call.callee.object, call.arguments[0]];
654
+ }
655
+ const code = [
656
+ "clock",
657
+ "fn",
658
+ "target"
659
+ ].filter((key) => config[key] !== void 0).map((key) => `${key}: ${source.getText(config[key])}`).join(", ");
660
+ context.report({
661
+ messageId: "noForward",
662
+ node: node.callee,
663
+ suggest: [{
664
+ messageId: "replaceWithSample",
665
+ fix: function* (fixer) {
666
+ const fn = sample ?? "sample";
667
+ yield fixer.replaceText(node, `${fn}({ ${code} })`);
668
+ if (!sample) yield fixer.insertTextAfter(forwards.get(node.callee.name), `, sample`);
669
+ }
670
+ }]
671
+ });
672
+ }
673
+ };
674
+ }
675
+ });
676
+ const selector$7 = {
677
+ forward: `ImportSpecifier[imported.name="forward"]`,
678
+ sample: `ImportSpecifier[imported.name="sample"]`,
679
+ call: `[callee.type="Identifier"][arguments.length=1]`,
680
+ argument: `ObjectExpression.arguments`
681
+ };
682
+ const query$2 = {
683
+ map: esquery.default.parse("CallExpression[arguments.length=1]:has(> :first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='map']))"),
684
+ prepend: esquery.default.parse("CallExpression[arguments.length=1]:has(> :first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='prepend']))")
685
+ };
686
+ var no_getState_default = createRule({
687
+ name: "no-getState",
688
+ meta: {
689
+ type: "problem",
690
+ docs: { description: "Forbid `.getState` calls on Effector stores." },
691
+ messages: {
692
+ named: "Method `.getState` used on store `{{ name }}` can lead to race conditions. Replace with with `sample` or `attach`.",
693
+ anonymous: "Method `.getState` used on store can lead to race conditions. Replace with with `sample` or `attach`."
694
+ },
695
+ schema: []
696
+ },
697
+ defaultOptions: [],
698
+ create: (context) => {
699
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
700
+ return { [`CallExpression[callee.type="MemberExpression"][callee.property.name="getState"]`]: (node) => {
701
+ const type = services.getTypeAtLocation(node.callee.object);
702
+ if (!isType.store(type, services.program)) return;
703
+ const name$1 = toName$1(node.callee.object);
704
+ if (name$1) context.report({
705
+ node,
706
+ messageId: "named",
707
+ data: { name: name$1 }
708
+ });
709
+ else context.report({
710
+ node,
711
+ messageId: "anonymous"
712
+ });
713
+ } };
714
+ }
715
+ });
716
+ const toName$1 = (node) => {
717
+ if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.name;
718
+ if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && !node.computed) return node.property.name;
719
+ return null;
720
+ };
721
+ var no_guard_default = createRule({
722
+ name: "no-guard",
723
+ meta: {
724
+ type: "problem",
725
+ docs: { description: "Prefer `sample` over `guard`." },
726
+ messages: {
727
+ noGuard: "Use `sample` operator instead of `guard` as a more universal approach.",
728
+ replaceWithSample: "Replace `guard` with `sample`."
729
+ },
730
+ hasSuggestions: true,
731
+ schema: []
732
+ },
733
+ defaultOptions: [],
734
+ create: (context) => {
735
+ let sample;
736
+ const guards = /* @__PURE__ */ new Map();
737
+ const source = context.sourceCode;
738
+ const visitorKeys = source.visitorKeys;
739
+ const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`;
740
+ return {
741
+ [`${importSelector} > ${selector$6.guard}`]: (node) => guards.set(node.local.name, node),
742
+ [`${importSelector} > ${selector$6.sample}`]: (node) => sample = node.local.name,
743
+ [`CallExpression${selector$6.call}`]: (node) => {
744
+ if (!guards.has(node.callee.name)) return;
745
+ const config = {};
746
+ if (node.arguments.length === 1 && node.arguments[0].type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression) {
747
+ const [arg] = node.arguments;
748
+ for (const key of [
749
+ "clock",
750
+ "source",
751
+ "filter",
752
+ "target"
753
+ ]) config[key] = locate.property(key, arg)?.value;
754
+ } else if (node.arguments.length === 2 && node.arguments[1].type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression) {
755
+ const [clock, arg] = node.arguments;
756
+ config.clock = clock;
757
+ for (const key of [
758
+ "source",
759
+ "filter",
760
+ "target"
761
+ ]) config[key] = locate.property(key, arg)?.value;
762
+ } else return;
763
+ if (config.target) {
764
+ const [call] = esquery.default.match(config.target, query$1.prepend, { visitorKeys }).map((node$1) => node$1).filter((node$1) => node$1 === config.target);
765
+ if (call) [config.target, config.fn] = [call.callee.object, call.arguments[0]];
766
+ }
767
+ const code = [
768
+ "clock",
769
+ "source",
770
+ "filter",
771
+ "fn",
772
+ "target"
773
+ ].filter((key) => config[key] !== void 0).map((key) => `${key}: ${source.getText(config[key])}`).join(", ");
774
+ context.report({
775
+ messageId: "noGuard",
776
+ node: node.callee,
777
+ suggest: [{
778
+ messageId: "replaceWithSample",
779
+ fix: function* (fixer) {
780
+ const fn = sample ?? "sample";
781
+ yield fixer.replaceText(node, `${fn}({ ${code} })`);
782
+ if (!sample) yield fixer.insertTextAfter(guards.get(node.callee.name), `, sample`);
783
+ }
784
+ }]
785
+ });
786
+ }
787
+ };
788
+ }
789
+ });
790
+ const selector$6 = {
791
+ guard: `ImportSpecifier[imported.name="guard"]`,
792
+ sample: `ImportSpecifier[imported.name="sample"]`,
793
+ call: `[callee.type="Identifier"]`
794
+ };
795
+ const query$1 = { prepend: esquery.default.parse("CallExpression[arguments.length=1]:has(:first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='prepend']))") };
796
+ var no_patronum_debug_default = createRule({
797
+ name: "no-patronum-debug",
798
+ meta: {
799
+ type: "problem",
800
+ docs: { description: "Disallow the use of `patronum` `debug`." },
801
+ messages: {
802
+ unexpected: "Unexpected `debug` call.",
803
+ remove: "Remove this `debug` call."
804
+ },
805
+ schema: [],
806
+ hasSuggestions: true
807
+ },
808
+ defaultOptions: [],
809
+ create: (context) => {
810
+ const debugs = /* @__PURE__ */ new Set();
811
+ return {
812
+ [`${`ImportDeclaration[source.value=${PACKAGE_NAME}]`} > ${selector$5.debug}`]: (node) => debugs.add(node.local.name),
813
+ [`CallExpression:matches(${selector$5.call})`]: (node) => {
814
+ const name$1 = toName(node);
815
+ if (!debugs.has(name$1)) return;
816
+ context.report({
817
+ messageId: "unexpected",
818
+ node: node.callee,
819
+ suggest: [{
820
+ messageId: "remove",
821
+ fix: (fixer) => {
822
+ if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ExpressionStatement) return fixer.remove(node.parent);
823
+ else return fixer.replaceText(node, "undefined");
824
+ }
825
+ }]
826
+ });
827
+ }
828
+ };
829
+ }
830
+ });
831
+ const PACKAGE_NAME = /^patronum(?:\u002Fdebug)?$/;
832
+ const selector$5 = {
833
+ debug: `ImportSpecifier[imported.name="debug"]`,
834
+ call: `[callee.type=Identifier], [callee.object.type=Identifier]`
835
+ };
836
+ const toName = (node) => {
837
+ switch (node.callee.type) {
838
+ case _typescript_eslint_utils.AST_NODE_TYPES.Identifier: return node.callee.name;
839
+ case _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression: return node.callee.object.name;
840
+ }
841
+ };
842
+ var no_unnecessary_combination_default = createRule({
843
+ name: "no-unnecessary-combination",
844
+ meta: {
845
+ type: "suggestion",
846
+ docs: { description: "Forbid unnecessary combinations in `clock` and `source`." },
847
+ messages: { unnecessary: "{{ method }} is used under the hood of {{ property }} in {{ operator }}, you can omit it." },
848
+ schema: []
849
+ },
850
+ defaultOptions: [],
851
+ create: (context) => {
852
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
853
+ const operators = /* @__PURE__ */ new Set();
854
+ const combinators = /* @__PURE__ */ new Map();
855
+ const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`;
856
+ return {
857
+ [`${importSelector} > ${selector$4.operator}`]: (node) => operators.add(node.local.name),
858
+ [`${importSelector} > ${selector$4.combinator}`]: (node) => combinators.set(node.local.name, node.imported.name),
859
+ [`CallExpression${selector$4.call}:has(${selector$4.argument})`]: (node) => {
860
+ if (!operators.has(node.callee.name)) return;
861
+ const [config] = node.arguments;
862
+ const clock = locate.property("clock", config)?.value;
863
+ const source = locate.property("source", config)?.value;
864
+ if (clock?.type === _typescript_eslint_utils.AST_NODE_TYPES.CallExpression && clock.callee.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) {
865
+ if (combinators.get(clock.callee.name) === "merge") {
866
+ const data = {
867
+ method: clock.callee.name,
868
+ property: "clock",
869
+ operator: node.callee.name
870
+ };
871
+ context.report({
872
+ node: clock,
873
+ messageId: "unnecessary",
874
+ data
875
+ });
876
+ }
877
+ }
878
+ if (source?.type === _typescript_eslint_utils.AST_NODE_TYPES.CallExpression && source.callee.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) {
879
+ const method = combinators.get(source.callee.name);
880
+ if (!method) return;
881
+ if (method === "combine" && source.arguments.length > 1 && isFunction(source.arguments.at(-1), services)) return;
882
+ const data = {
883
+ method: source.callee.name,
884
+ property: "source",
885
+ operator: node.callee.name
886
+ };
887
+ context.report({
888
+ node: source,
889
+ messageId: "unnecessary",
890
+ data
891
+ });
892
+ }
893
+ }
894
+ };
895
+ }
896
+ });
897
+ const selector$4 = {
898
+ operator: `ImportSpecifier[imported.name=/(sample|guard)/]`,
899
+ combinator: `ImportSpecifier[imported.name=/(combine|merge)/]`,
900
+ call: `[callee.type="Identifier"][arguments.length=1]`,
901
+ argument: `ObjectExpression.arguments`
902
+ };
903
+ function isFunction(node, services) {
904
+ if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrowFunctionExpression) return true;
905
+ else if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.FunctionExpression) return true;
906
+ else if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) {
907
+ const checker = services.program.getTypeChecker();
908
+ const tsnode = services.esTreeNodeToTSNodeMap.get(node);
909
+ return checker.getTypeAtLocation(tsnode).getCallSignatures().length > 0;
910
+ } else return false;
911
+ }
912
+ var no_unnecessary_duplication_default = createRule({
913
+ name: "no-unnecessary-duplication",
914
+ meta: {
915
+ type: "problem",
916
+ docs: { description: "Forbid duplicate `source` and `clock` in `sample` and `guard`." },
917
+ messages: {
918
+ duplicate: "Method `{{ method }}` has the same value for `source` and `clock`. Consider using only one of them.",
919
+ removeClock: "Remove the `clock`",
920
+ removeSource: "Remove the `source`"
921
+ },
922
+ schema: [],
923
+ hasSuggestions: true
924
+ },
925
+ defaultOptions: [],
926
+ create: (context) => {
927
+ const imports = /* @__PURE__ */ new Set();
928
+ return {
929
+ [`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`} > ${selector$3.method}`]: (node) => imports.add(node.local.name),
930
+ [`CallExpression${selector$3.call}:has(${selector$3.argument})`]: (node) => {
931
+ if (!imports.has(node.callee.name)) return;
932
+ const [config] = node.arguments;
933
+ const source = locate.property("source", config)?.value;
934
+ if (!source) return;
935
+ const clock = locate.property("clock", config)?.value;
936
+ if (!clock) return;
937
+ if (!compare(clock, source)) return;
938
+ const suggestions = [{
939
+ messageId: "removeClock",
940
+ fix: function* (fixer) {
941
+ yield fixer.remove(clock.parent);
942
+ const after = context.sourceCode.getTokenAfter(clock.parent);
943
+ if (after?.value === ",") yield fixer.remove(after);
944
+ }
945
+ }, {
946
+ messageId: "removeSource",
947
+ fix: function* (fixer) {
948
+ yield fixer.remove(source.parent);
949
+ const after = context.sourceCode.getTokenAfter(source.parent);
950
+ if (after?.value === ",") yield fixer.remove(after);
951
+ }
952
+ }];
953
+ const data = { method: node.callee.name };
954
+ context.report({
955
+ node: config,
956
+ messageId: "duplicate",
957
+ data,
958
+ suggest: suggestions
959
+ });
960
+ }
961
+ };
962
+ }
963
+ });
964
+ const selector$3 = {
965
+ method: `ImportSpecifier[imported.name=/(sample|guard)/]`,
966
+ call: `[callee.type="Identifier"][arguments.length=1]`,
967
+ argument: `ObjectExpression.arguments`
968
+ };
969
+ function compare(clock, source, limit = 5) {
970
+ if (limit <= 0) return false;
971
+ if (clock.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return source.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && clock.name === source.name;
972
+ if (clock.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression) {
973
+ if (clock.elements.length !== 1) return false;
974
+ let a, b;
975
+ if (source.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression) if (source.elements.length !== 1) return false;
976
+ else [a, b] = [clock.elements[0], source.elements[0]];
977
+ else [a, b] = [clock.elements[0], source];
978
+ return a.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && b.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && a.name === b.name;
979
+ }
980
+ if (clock.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression) {
981
+ if (source.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression) return false;
982
+ if (clock.computed || source.computed) return false;
983
+ if (clock.property.name !== source.property.name) return false;
984
+ return compare(clock.object, source.object, limit - 1);
985
+ }
986
+ return false;
987
+ }
988
+ var no_useless_methods_default = createRule({
989
+ name: "no-useless-methods",
990
+ meta: {
991
+ type: "problem",
992
+ docs: { description: "Forbid useless calls of `sample` and `guard`." },
993
+ messages: { uselessMethod: "Method `{{ method }}` does nothing in this case. You should assign the result to variable or pass `target` to it." },
994
+ schema: []
995
+ },
996
+ defaultOptions: [],
997
+ create: (context) => {
998
+ const imports = /* @__PURE__ */ new Set();
999
+ const source = context.sourceCode;
1000
+ const visitorKeys = source.visitorKeys;
1001
+ const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`;
1002
+ const usageStack = [];
1003
+ return {
1004
+ "ReturnStatement": () => usageStack.push(true),
1005
+ "ReturnStatement:exit": () => usageStack.pop(),
1006
+ "VariableDeclarator": () => usageStack.push(true),
1007
+ "VariableDeclarator:exit": () => usageStack.pop(),
1008
+ "ObjectExpression": () => usageStack.push(true),
1009
+ "ObjectExpression:exit": () => usageStack.pop(),
1010
+ "BlockStatement": () => usageStack.push(false),
1011
+ "BlockStatement:exit": () => usageStack.pop(),
1012
+ [`${importSelector} > ${selector$2.method}`]: (node) => imports.add(node.local.name),
1013
+ [`CallExpression[callee.type="Identifier"]`]: (node) => {
1014
+ if (!imports.has(node.callee.name)) return;
1015
+ if (usageStack.at(-1) ?? false) return;
1016
+ if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.CallExpression) return;
1017
+ const [config] = node.arguments;
1018
+ if (config?.type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression) {
1019
+ if (locate.property("target", config)?.value) return;
1020
+ }
1021
+ const grandparent = node.parent.parent;
1022
+ if (grandparent) {
1023
+ const ancestry = source.getAncestors(grandparent);
1024
+ if (esquery.default.matches(grandparent, query.watch, ancestry, { visitorKeys })) return;
1025
+ }
1026
+ const method = node.callee.name;
1027
+ context.report({
1028
+ node,
1029
+ messageId: "uselessMethod",
1030
+ data: { method }
1031
+ });
1032
+ }
1033
+ };
1034
+ }
1035
+ });
1036
+ const selector$2 = { method: `ImportSpecifier[imported.name=/(sample|guard)/]` };
1037
+ const query = { watch: esquery.default.parse("CallExpression:has(> MemberExpression.callee[property.name=watch]:has(> CallExpression.object))") };
1038
+ var no_watch_default = createRule({
1039
+ name: "no-watch",
1040
+ meta: {
1041
+ type: "suggestion",
1042
+ docs: { description: "Restrict usage of `.watch` on any Effector Unit." },
1043
+ messages: { restricted: "Using `.watch` method leads to imperative code. Replace it with an operator `sample` or use the `target` parameter of `sample` operator." },
1044
+ schema: []
1045
+ },
1046
+ defaultOptions: [],
1047
+ create: (context) => {
1048
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
1049
+ return { [`CallExpression[callee.type="MemberExpression"][callee.property.name="watch"]`]: (node) => {
1050
+ const type = services.getTypeAtLocation(node.callee.object);
1051
+ if (!isType.unit(type, services.program)) return;
1052
+ context.report({
1053
+ node,
1054
+ messageId: "restricted"
1055
+ });
1056
+ } };
1057
+ }
1058
+ });
1059
+ var prefer_useUnit_default = createRule({
1060
+ name: "prefer-useUnit",
1061
+ meta: {
1062
+ type: "suggestion",
1063
+ docs: { description: "Prefer `useUnit` over deprecated `useStore` and `useEvent` hooks." },
1064
+ messages: { useUseUnit: "`{{ name }}` should be replaced with `useUnit`." },
1065
+ schema: []
1066
+ },
1067
+ defaultOptions: [],
1068
+ create: (context) => {
1069
+ const imports = /* @__PURE__ */ new Map();
1070
+ const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.react}]`;
1071
+ return {
1072
+ [`${importSelector} > ${selector$1.useStore}`]: (node) => void imports.set(node.local.name, "useStore"),
1073
+ [`${importSelector} > ${selector$1.useEvent}`]: (node) => void imports.set(node.local.name, "useEvent"),
1074
+ [`CallExpression[callee.type="Identifier"]`]: (node) => {
1075
+ const hook = imports.get(node.callee.name);
1076
+ if (!hook) return;
1077
+ context.report({
1078
+ node,
1079
+ messageId: "useUseUnit",
1080
+ data: { name: hook }
1081
+ });
1082
+ }
1083
+ };
1084
+ }
1085
+ });
1086
+ const selector$1 = {
1087
+ useStore: `ImportSpecifier[imported.name=useStore]`,
1088
+ useEvent: `ImportSpecifier[imported.name=useEvent]`
1089
+ };
1090
+ var require_pickup_in_persist_default = createRule({
1091
+ name: "require-pickup-in-persist",
1092
+ meta: {
1093
+ type: "problem",
1094
+ docs: { description: "Require every `persist` call of `effector-storage` to use `pickup`." },
1095
+ messages: { missing: "This `persist` call does not specify a `pickup` event that is required for scoped usage of `effector-storage`." },
1096
+ schema: []
1097
+ },
1098
+ defaultOptions: [],
1099
+ create: (context) => {
1100
+ const imports = /* @__PURE__ */ new Set();
1101
+ return {
1102
+ [`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.storage}]`} > ${selector.persist}`]: (node) => imports.add(node.local.name),
1103
+ [`CallExpression${selector.call}${selector.config}`]: (node) => {
1104
+ if (!imports.has(node.callee.name)) return;
1105
+ if (node.arguments[0].properties.filter((prop) => prop.type === _typescript_eslint_utils.AST_NODE_TYPES.Property).map((prop) => prop.key).filter((key) => key.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier).some((key) => key.name === "pickup")) return;
1106
+ context.report({
1107
+ node,
1108
+ messageId: "missing"
1109
+ });
1110
+ }
1111
+ };
1112
+ }
1113
+ });
1114
+ const selector = {
1115
+ persist: `ImportSpecifier[imported.name="persist"]`,
1116
+ call: `[callee.type="Identifier"]`,
1117
+ config: `[arguments.length=1][arguments.0.type="ObjectExpression"]`
1118
+ };
1119
+ var strict_effect_handlers_default = createRule({
1120
+ name: "strict-effect-handlers",
1121
+ meta: {
1122
+ type: "problem",
1123
+ docs: { description: "Forbid mixing calls to both regular async functions and Effects in the same function." },
1124
+ messages: { mixed: "This function can lead to losing Scope in Effector Fork API." },
1125
+ schema: []
1126
+ },
1127
+ defaultOptions: [],
1128
+ create: (context) => {
1129
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
1130
+ const stack = [];
1131
+ const track = (node) => {
1132
+ const current = stack.at(-1);
1133
+ if (!current) return;
1134
+ const callee = node.argument.callee;
1135
+ const type = services.getTypeAtLocation(callee);
1136
+ if (isType.effect(type, services.program)) return current.effect = true;
1137
+ else return current.regular = true;
1138
+ };
1139
+ const enter = () => {
1140
+ stack.push({
1141
+ effect: false,
1142
+ regular: false
1143
+ });
1144
+ };
1145
+ const exit = (node) => {
1146
+ const scope$1 = stack.pop();
1147
+ if (!scope$1) return;
1148
+ if (scope$1.effect && scope$1.regular) context.report({
1149
+ node,
1150
+ messageId: "mixed"
1151
+ });
1152
+ };
1153
+ return {
1154
+ "ArrowFunctionExpression": enter,
1155
+ "ArrowFunctionExpression:exit": exit,
1156
+ "FunctionExpression": enter,
1157
+ "FunctionExpression:exit": exit,
1158
+ "FunctionDeclaration": enter,
1159
+ "FunctionDeclaration:exit": exit,
1160
+ "AwaitExpression:matches([argument.type='CallExpression'], [argument.type='NewExpression'])": track
1161
+ };
1162
+ }
1163
+ });
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
+ const ruleset = {
1190
+ recommended,
1191
+ patronum,
1192
+ scope,
1193
+ react,
1194
+ future
1195
+ };
1196
+ const base = {
1197
+ meta: {
1198
+ name,
1199
+ version,
1200
+ namespace: "effector"
1201
+ },
1202
+ rules: {
1203
+ "enforce-effect-naming-convention": enforce_effect_naming_convention_default,
1204
+ "enforce-gate-naming-convention": enforce_gate_naming_convention_default,
1205
+ "enforce-store-naming-convention": enforce_store_naming_convention_default,
1206
+ "keep-options-order": keep_options_order_default,
1207
+ "mandatory-scope-binding": mandatory_scope_binding_default,
1208
+ "no-ambiguity-target": no_ambiguity_target_default,
1209
+ "no-domain-unit-creators": no_domain_unit_creators_default,
1210
+ "no-duplicate-clock-or-source-array-values": no_duplicate_clock_or_source_array_values_default,
1211
+ "no-duplicate-on": no_duplicate_on_default,
1212
+ "no-forward": no_forward_default,
1213
+ "no-getState": no_getState_default,
1214
+ "no-guard": no_guard_default,
1215
+ "no-patronum-debug": no_patronum_debug_default,
1216
+ "no-unnecessary-combination": no_unnecessary_combination_default,
1217
+ "no-unnecessary-duplication": no_unnecessary_duplication_default,
1218
+ "no-useless-methods": no_useless_methods_default,
1219
+ "no-watch": no_watch_default,
1220
+ "prefer-useUnit": prefer_useUnit_default,
1221
+ "require-pickup-in-persist": require_pickup_in_persist_default,
1222
+ "strict-effect-handlers": strict_effect_handlers_default
1223
+ }
1224
+ };
1225
+ const legacyConfigs = {
1226
+ recommended: { rules: ruleset.recommended },
1227
+ scope: { rules: ruleset.scope },
1228
+ react: { rules: ruleset.react },
1229
+ future: { rules: ruleset.future },
1230
+ patronum: { rules: ruleset.patronum }
1231
+ };
1232
+ const self = base;
1233
+ const flatConfigs = {
1234
+ recommended: {
1235
+ plugins: { effector: self },
1236
+ rules: ruleset.recommended
1237
+ },
1238
+ scope: {
1239
+ plugins: { effector: self },
1240
+ rules: ruleset.scope
1241
+ },
1242
+ react: {
1243
+ plugins: { effector: self },
1244
+ rules: ruleset.react
1245
+ },
1246
+ future: {
1247
+ plugins: { effector: self },
1248
+ rules: ruleset.future
1249
+ },
1250
+ patronum: {
1251
+ plugins: { effector: self },
1252
+ rules: ruleset.patronum
1253
+ }
1254
+ };
1255
+ const plugin = base;
1256
+ plugin.configs = legacyConfigs;
1257
+ plugin.flatConfigs = flatConfigs;
1258
+ var src_default = plugin;
1259
+ module.exports = src_default;