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/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.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
+ 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$1) && declarations.map((decl) => decl.getSourceFile().fileName).some((fname) => fname.includes("node_modules") && fname.includes(from));
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 `{{ current }}` should be named with suffix, rename it to `{{ fixed }}`",
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 { [`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
- } };
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
- 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, "");
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
- const data = {
209
- current,
376
+ return {
377
+ current: node.name,
210
378
  convention: options.mode,
211
379
  fixed
212
380
  };
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
- } };
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 PACKAGE_NAME$1 = {
233
- core: /^effector(?:\u002Fcompat)?$/,
234
- react: /^effector-react$/,
235
- storage: /^@?effector-storage(\u002F[\w-]+)*$/
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$11.method}`]: (node) => imports.add(node.local.name),
255
- [`CallExpression${selector$11.call}:has(${selector$11.argument})`]: (node) => {
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$11 = {
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
- const nameOf = { function: functionToName };
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 components." },
323
- messages: { useUnitNeeded: "\"{{ name }}\" must be wrapped with `useUnit` from `effector-react` before usage inside React components." },
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 stack = {
331
- render: [],
332
- hook: []
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
- [`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);
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 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);
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(FunctionDeclaration, FunctionExpression, ArrowFunctionExpression):exit`]: () => void stack.render.pop(),
348
- "ClassDeclaration": () => void stack.render.push(false),
349
- "ClassDeclaration:exit": () => void stack.render.pop(),
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 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);
603
+ const id = nameOf.callee(node.callee), isEnteringHook = id !== null && UseRegex$1.test(id.name);
604
+ inHook.push(isEnteringHook);
363
605
  },
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
- }
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$1 = node.callee.property.name;
433
- if (!METHODS.has(name$1)) return;
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$1) ?? name$1;
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$1,
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$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);
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$1, field) => {
741
+ const report = (node, name, field) => {
479
742
  const data = {
480
743
  field,
481
- unit: name$1
744
+ unit: name
482
745
  };
483
746
  const suggestion = {
484
747
  messageId: "remove",
485
- data: { unit: name$1 },
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$1 = context.sourceCode.getScope(node);
544
- const store = identify("store", node.callee.object, scope$1);
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$1);
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$1) {
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$1, root.node);
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$1) => node$1).filter((node$1) => node$1 === config.target);
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$1) => node$1).filter((node$1) => node$1 === config.clock);
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$1 = toName$1(node.callee.object);
704
- if (name$1) context.report({
972
+ const name = nameOf.expression.simple(node.callee.object);
973
+ if (name) context.report({
705
974
  node,
706
975
  messageId: "named",
707
- data: { name: name$1 }
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
- 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
- };
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$1) => node$1).filter((node$1) => node$1 === config.target);
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$1 = toName(node);
815
- if (!debugs.has(name$1)) return;
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
- if (grandparent) {
1023
- const ancestry = source.getAncestors(grandparent);
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$1 = stack.pop();
1147
- if (!scope$1) return;
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
- patronum,
1192
- scope,
1193
- react,
1194
- future
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
- var src_default = plugin;
1259
- module.exports = src_default;
1716
+ //#endregion
1717
+ module.exports = plugin;