eslint-plugin-effector 0.17.0 → 0.18.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 +298 -69
- package/dist/index.d.cts +67 -80
- package/dist/index.d.mts +67 -80
- package/dist/index.mjs +271 -39
- package/package.json +19 -17
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,24 @@ 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");
|
|
24
26
|
let esquery = require("esquery");
|
|
25
27
|
esquery = __toESM(esquery);
|
|
28
|
+
//#region package.json
|
|
26
29
|
var name = "eslint-plugin-effector";
|
|
27
|
-
var version = "0.
|
|
28
|
-
|
|
30
|
+
var version = "0.18.0";
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/shared/create.ts
|
|
33
|
+
const createRule = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://eslint.effector.dev/rules/${name}`);
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/shared/is.ts
|
|
29
36
|
const check = (symbol, types, from) => {
|
|
30
|
-
const name
|
|
37
|
+
const name = symbol.getName();
|
|
31
38
|
const declarations = symbol.declarations ?? [];
|
|
32
|
-
return types.includes(name
|
|
39
|
+
return types.includes(name) && declarations.map((decl) => decl.getSourceFile().fileName).some((fname) => fname.includes("node_modules") && fname.includes(from));
|
|
33
40
|
};
|
|
34
41
|
const isType = {
|
|
35
42
|
store: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
|
|
@@ -56,7 +63,8 @@ const isType = {
|
|
|
56
63
|
"StoreWritable",
|
|
57
64
|
"Event",
|
|
58
65
|
"EventCallable",
|
|
59
|
-
"Effect"
|
|
66
|
+
"Effect",
|
|
67
|
+
"Domain"
|
|
60
68
|
]
|
|
61
69
|
}, program);
|
|
62
70
|
},
|
|
@@ -94,6 +102,8 @@ const isType = {
|
|
|
94
102
|
}, program);
|
|
95
103
|
}
|
|
96
104
|
};
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.ts
|
|
97
107
|
var enforce_effect_naming_convention_default = createRule({
|
|
98
108
|
name: "enforce-effect-naming-convention",
|
|
99
109
|
meta: {
|
|
@@ -136,6 +146,8 @@ var enforce_effect_naming_convention_default = createRule({
|
|
|
136
146
|
}
|
|
137
147
|
});
|
|
138
148
|
const FxRegex = /Fx$/;
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.ts
|
|
139
151
|
var enforce_gate_naming_convention_default = createRule({
|
|
140
152
|
name: "enforce-gate-naming-convention",
|
|
141
153
|
meta: {
|
|
@@ -178,6 +190,8 @@ var enforce_gate_naming_convention_default = createRule({
|
|
|
178
190
|
}
|
|
179
191
|
});
|
|
180
192
|
const GateRegex = /^[^A-Z]/;
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/rules/enforce-store-naming-convention/enforce-store-naming-convention.ts
|
|
181
195
|
var enforce_store_naming_convention_default = createRule({
|
|
182
196
|
name: "enforce-store-naming-convention",
|
|
183
197
|
meta: {
|
|
@@ -229,11 +243,15 @@ var enforce_store_naming_convention_default = createRule({
|
|
|
229
243
|
});
|
|
230
244
|
const PrefixRegex = /^[^$]/;
|
|
231
245
|
const PostfixRegex = /[^$]$/;
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/shared/package.ts
|
|
232
248
|
const PACKAGE_NAME$1 = {
|
|
233
249
|
core: /^effector(?:\u002Fcompat)?$/,
|
|
234
250
|
react: /^effector-react$/,
|
|
235
251
|
storage: /^@?effector-storage(\u002F[\w-]+)*$/
|
|
236
252
|
};
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/rules/keep-options-order/keep-options-order.ts
|
|
237
255
|
var keep_options_order_default = createRule({
|
|
238
256
|
name: "keep-options-order",
|
|
239
257
|
meta: {
|
|
@@ -306,6 +324,8 @@ const isCorrectOrder = (current) => {
|
|
|
306
324
|
}
|
|
307
325
|
return true;
|
|
308
326
|
};
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region src/shared/name.ts
|
|
309
329
|
function functionToName(node) {
|
|
310
330
|
if (node.id) return node.id;
|
|
311
331
|
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;
|
|
@@ -315,6 +335,8 @@ function functionToName(node) {
|
|
|
315
335
|
return null;
|
|
316
336
|
}
|
|
317
337
|
const nameOf = { function: functionToName };
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/rules/mandatory-scope-binding/mandatory-scope-binding.ts
|
|
318
340
|
var mandatory_scope_binding_default = createRule({
|
|
319
341
|
name: "mandatory-scope-binding",
|
|
320
342
|
meta: {
|
|
@@ -334,14 +356,14 @@ var mandatory_scope_binding_default = createRule({
|
|
|
334
356
|
return {
|
|
335
357
|
[`FunctionDeclaration, FunctionExpression, ArrowFunctionExpression`]: (node) => {
|
|
336
358
|
if (stack.render.at(-1) ?? false) return void stack.render.push(true);
|
|
337
|
-
const name
|
|
338
|
-
if (name
|
|
359
|
+
const name = nameOf.function(node);
|
|
360
|
+
if (name && UseRegex$1.test(name.name)) return void stack.render.push(true);
|
|
339
361
|
const tsnode = services.esTreeNodeToTSNodeMap.get(node);
|
|
340
362
|
const signature = checker.getSignatureFromDeclaration(tsnode);
|
|
341
363
|
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)
|
|
344
|
-
if (inferred ? isType.component(
|
|
364
|
+
if (returnType.isUnion() ? returnType.types.some((type) => isType.jsx(type, services.program)) : isType.jsx(returnType, services.program)) return void stack.render.push(true);
|
|
365
|
+
const inferred = (0, typescript.isExpression)(tsnode) && (0, _typescript_eslint_type_utils.getContextualType)(checker, tsnode) || checker.getUnknownType();
|
|
366
|
+
if (inferred.isUnion() ? inferred.types.some((type) => isType.component(type, services.program)) : isType.component(inferred, services.program)) return void stack.render.push(true);
|
|
345
367
|
stack.render.push(false);
|
|
346
368
|
},
|
|
347
369
|
[`:matches(FunctionDeclaration, FunctionExpression, ArrowFunctionExpression):exit`]: () => void stack.render.pop(),
|
|
@@ -375,9 +397,13 @@ var mandatory_scope_binding_default = createRule({
|
|
|
375
397
|
};
|
|
376
398
|
}
|
|
377
399
|
});
|
|
378
|
-
const UseRegex = /^use[A-Z0-9].*$/;
|
|
400
|
+
const UseRegex$1 = /^use[A-Z0-9].*$/;
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/shared/locate.ts
|
|
379
403
|
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
404
|
const locate = { property };
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region src/rules/no-ambiguity-target/no-ambiguity-target.ts
|
|
381
407
|
var no_ambiguity_target_default = createRule({
|
|
382
408
|
name: "no-ambiguity-target",
|
|
383
409
|
meta: {
|
|
@@ -417,6 +443,8 @@ var no_ambiguity_target_default = createRule({
|
|
|
417
443
|
}
|
|
418
444
|
});
|
|
419
445
|
const selector$10 = { method: `ImportSpecifier[imported.name=/(sample|guard)/]` };
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region src/rules/no-domain-unit-creators/no-domain-unit-creators.ts
|
|
420
448
|
var no_domain_unit_creators_default = createRule({
|
|
421
449
|
name: "no-domain-unit-creators",
|
|
422
450
|
meta: {
|
|
@@ -429,16 +457,16 @@ var no_domain_unit_creators_default = createRule({
|
|
|
429
457
|
create: (context) => {
|
|
430
458
|
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
|
|
431
459
|
return { [`CallExpression:has(> ${selector$9.member})`]: (node) => {
|
|
432
|
-
const name
|
|
433
|
-
if (!METHODS.has(name
|
|
460
|
+
const name = node.callee.property.name;
|
|
461
|
+
if (!METHODS.has(name)) return;
|
|
434
462
|
const type = services.getTypeAtLocation(node.callee.object);
|
|
435
463
|
if (!isType.domain(type, services.program)) return;
|
|
436
|
-
const factory = ALIAS_MAP.get(name
|
|
464
|
+
const factory = ALIAS_MAP.get(name) ?? name;
|
|
437
465
|
context.report({
|
|
438
466
|
node,
|
|
439
467
|
messageId: "avoid",
|
|
440
468
|
data: {
|
|
441
|
-
method: name
|
|
469
|
+
method: name,
|
|
442
470
|
factory
|
|
443
471
|
}
|
|
444
472
|
});
|
|
@@ -448,6 +476,8 @@ var no_domain_unit_creators_default = createRule({
|
|
|
448
476
|
const ALIAS_MAP = (/* @__PURE__ */ new Map()).set("event", "createEvent").set("store", "createStore").set("effect", "createEffect").set("domain", "createDomain");
|
|
449
477
|
const METHODS = new Set([...ALIAS_MAP.values(), ...ALIAS_MAP.keys()]);
|
|
450
478
|
const selector$9 = { member: `MemberExpression.callee[property.type="Identifier"]` };
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/rules/no-duplicate-clock-or-source-array-values/no-duplicate-clock-or-source-array-values.ts
|
|
451
481
|
var no_duplicate_clock_or_source_array_values_default = createRule({
|
|
452
482
|
name: "no-duplicate-clock-or-source-array-values",
|
|
453
483
|
meta: {
|
|
@@ -470,19 +500,19 @@ var no_duplicate_clock_or_source_array_values_default = createRule({
|
|
|
470
500
|
for (const entry of entries) {
|
|
471
501
|
const root = traverseToRoot$1(entry);
|
|
472
502
|
if (!root) continue;
|
|
473
|
-
const name
|
|
474
|
-
if (seen.has(name
|
|
475
|
-
else seen.set(name
|
|
503
|
+
const name = [root.node.name, ...root.path].join(".");
|
|
504
|
+
if (seen.has(name)) report(entry, name, field);
|
|
505
|
+
else seen.set(name, entry);
|
|
476
506
|
}
|
|
477
507
|
};
|
|
478
|
-
const report = (node, name
|
|
508
|
+
const report = (node, name, field) => {
|
|
479
509
|
const data = {
|
|
480
510
|
field,
|
|
481
|
-
unit: name
|
|
511
|
+
unit: name
|
|
482
512
|
};
|
|
483
513
|
const suggestion = {
|
|
484
514
|
messageId: "remove",
|
|
485
|
-
data: { unit: name
|
|
515
|
+
data: { unit: name },
|
|
486
516
|
fix: function* (fixer) {
|
|
487
517
|
yield fixer.remove(node);
|
|
488
518
|
const before = context.sourceCode.getTokenBefore(node);
|
|
@@ -522,6 +552,8 @@ function traverseToRoot$1(node, path = []) {
|
|
|
522
552
|
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
553
|
return null;
|
|
524
554
|
}
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/rules/no-duplicate-on/no-duplicate-on.ts
|
|
525
557
|
var no_duplicate_on_default = createRule({
|
|
526
558
|
name: "no-duplicate-on",
|
|
527
559
|
meta: {
|
|
@@ -540,12 +572,12 @@ var no_duplicate_on_default = createRule({
|
|
|
540
572
|
const arg = node.arguments[0];
|
|
541
573
|
if (!arg || arg.type === _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) return;
|
|
542
574
|
const units = arg.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression ? arg.elements.filter((item) => item !== null && item.type !== _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) : [arg];
|
|
543
|
-
const scope
|
|
544
|
-
const store = identify("store", node.callee.object, scope
|
|
575
|
+
const scope = context.sourceCode.getScope(node);
|
|
576
|
+
const store = identify("store", node.callee.object, scope);
|
|
545
577
|
if (!store) return;
|
|
546
578
|
const set = map.get(store.id) ?? /* @__PURE__ */ new Set();
|
|
547
579
|
for (const unit of units) {
|
|
548
|
-
const instance = identify("unit", unit, scope
|
|
580
|
+
const instance = identify("unit", unit, scope);
|
|
549
581
|
if (!instance) continue;
|
|
550
582
|
if (set.has(instance.id)) {
|
|
551
583
|
const data = {
|
|
@@ -606,16 +638,18 @@ function findSuitableRoot(type, node) {
|
|
|
606
638
|
};
|
|
607
639
|
return null;
|
|
608
640
|
}
|
|
609
|
-
function identify(type, node, scope
|
|
641
|
+
function identify(type, node, scope) {
|
|
610
642
|
const root = findSuitableRoot(type, node);
|
|
611
643
|
if (!root) return null;
|
|
612
|
-
const variable = _typescript_eslint_utils.ASTUtils.findVariable(scope
|
|
644
|
+
const variable = _typescript_eslint_utils.ASTUtils.findVariable(scope, root.node);
|
|
613
645
|
if (!variable) return null;
|
|
614
646
|
return {
|
|
615
647
|
id: `${variable.$id}+${root.path.join(".")}`,
|
|
616
648
|
name: [variable.name, ...root.path].join(".")
|
|
617
649
|
};
|
|
618
650
|
}
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region src/rules/no-forward/no-forward.ts
|
|
619
653
|
var no_forward_default = createRule({
|
|
620
654
|
name: "no-forward",
|
|
621
655
|
meta: {
|
|
@@ -645,11 +679,11 @@ var no_forward_default = createRule({
|
|
|
645
679
|
config.clock = locate.property("from", arg)?.value;
|
|
646
680
|
config.target = locate.property("to", arg)?.value;
|
|
647
681
|
if (config.target) {
|
|
648
|
-
const [call] = esquery.default.match(config.target, query$2.prepend, { visitorKeys }).map((node
|
|
682
|
+
const [call] = esquery.default.match(config.target, query$2.prepend, { visitorKeys }).map((node) => node).filter((node) => node === config.target);
|
|
649
683
|
if (call) [config.target, config.fn] = [call.callee.object, call.arguments[0]];
|
|
650
684
|
}
|
|
651
685
|
if (config.clock && !config.fn) {
|
|
652
|
-
const [call] = esquery.default.match(config.clock, query$2.map, { visitorKeys }).map((node
|
|
686
|
+
const [call] = esquery.default.match(config.clock, query$2.map, { visitorKeys }).map((node) => node).filter((node) => node === config.clock);
|
|
653
687
|
if (call) [config.clock, config.fn] = [call.callee.object, call.arguments[0]];
|
|
654
688
|
}
|
|
655
689
|
const code = [
|
|
@@ -683,6 +717,8 @@ const query$2 = {
|
|
|
683
717
|
map: esquery.default.parse("CallExpression[arguments.length=1]:has(> :first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='map']))"),
|
|
684
718
|
prepend: esquery.default.parse("CallExpression[arguments.length=1]:has(> :first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='prepend']))")
|
|
685
719
|
};
|
|
720
|
+
//#endregion
|
|
721
|
+
//#region src/rules/no-getState/no-getState.ts
|
|
686
722
|
var no_getState_default = createRule({
|
|
687
723
|
name: "no-getState",
|
|
688
724
|
meta: {
|
|
@@ -700,11 +736,11 @@ var no_getState_default = createRule({
|
|
|
700
736
|
return { [`CallExpression[callee.type="MemberExpression"][callee.property.name="getState"]`]: (node) => {
|
|
701
737
|
const type = services.getTypeAtLocation(node.callee.object);
|
|
702
738
|
if (!isType.store(type, services.program)) return;
|
|
703
|
-
const name
|
|
704
|
-
if (name
|
|
739
|
+
const name = toName$1(node.callee.object);
|
|
740
|
+
if (name) context.report({
|
|
705
741
|
node,
|
|
706
742
|
messageId: "named",
|
|
707
|
-
data: { name
|
|
743
|
+
data: { name }
|
|
708
744
|
});
|
|
709
745
|
else context.report({
|
|
710
746
|
node,
|
|
@@ -718,6 +754,8 @@ const toName$1 = (node) => {
|
|
|
718
754
|
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && !node.computed) return node.property.name;
|
|
719
755
|
return null;
|
|
720
756
|
};
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region src/rules/no-guard/no-guard.ts
|
|
721
759
|
var no_guard_default = createRule({
|
|
722
760
|
name: "no-guard",
|
|
723
761
|
meta: {
|
|
@@ -761,7 +799,7 @@ var no_guard_default = createRule({
|
|
|
761
799
|
]) config[key] = locate.property(key, arg)?.value;
|
|
762
800
|
} else return;
|
|
763
801
|
if (config.target) {
|
|
764
|
-
const [call] = esquery.default.match(config.target, query$1.prepend, { visitorKeys }).map((node
|
|
802
|
+
const [call] = esquery.default.match(config.target, query$1.prepend, { visitorKeys }).map((node) => node).filter((node) => node === config.target);
|
|
765
803
|
if (call) [config.target, config.fn] = [call.callee.object, call.arguments[0]];
|
|
766
804
|
}
|
|
767
805
|
const code = [
|
|
@@ -793,6 +831,8 @@ const selector$6 = {
|
|
|
793
831
|
call: `[callee.type="Identifier"]`
|
|
794
832
|
};
|
|
795
833
|
const query$1 = { prepend: esquery.default.parse("CallExpression[arguments.length=1]:has(:first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='prepend']))") };
|
|
834
|
+
//#endregion
|
|
835
|
+
//#region src/rules/no-patronum-debug/no-patronum-debug.ts
|
|
796
836
|
var no_patronum_debug_default = createRule({
|
|
797
837
|
name: "no-patronum-debug",
|
|
798
838
|
meta: {
|
|
@@ -811,8 +851,8 @@ var no_patronum_debug_default = createRule({
|
|
|
811
851
|
return {
|
|
812
852
|
[`${`ImportDeclaration[source.value=${PACKAGE_NAME}]`} > ${selector$5.debug}`]: (node) => debugs.add(node.local.name),
|
|
813
853
|
[`CallExpression:matches(${selector$5.call})`]: (node) => {
|
|
814
|
-
const name
|
|
815
|
-
if (!debugs.has(name
|
|
854
|
+
const name = toName(node);
|
|
855
|
+
if (!debugs.has(name)) return;
|
|
816
856
|
context.report({
|
|
817
857
|
messageId: "unexpected",
|
|
818
858
|
node: node.callee,
|
|
@@ -839,6 +879,184 @@ const toName = (node) => {
|
|
|
839
879
|
case _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression: return node.callee.object.name;
|
|
840
880
|
}
|
|
841
881
|
};
|
|
882
|
+
//#endregion
|
|
883
|
+
//#region src/rules/no-units-spawn-in-render/no-units-spawn-in-render.ts
|
|
884
|
+
const EFFECTOR_FACTORIES = new Set([
|
|
885
|
+
"createStore",
|
|
886
|
+
"createEvent",
|
|
887
|
+
"createEffect",
|
|
888
|
+
"createDomain",
|
|
889
|
+
"createApi",
|
|
890
|
+
"restore"
|
|
891
|
+
]);
|
|
892
|
+
const EFFECTOR_OPERATORS = new Set([
|
|
893
|
+
"sample",
|
|
894
|
+
"guard",
|
|
895
|
+
"forward",
|
|
896
|
+
"merge",
|
|
897
|
+
"split",
|
|
898
|
+
"combine",
|
|
899
|
+
"attach"
|
|
900
|
+
]);
|
|
901
|
+
const REACT_HOOKS_SPEC = {
|
|
902
|
+
from: "package",
|
|
903
|
+
package: "react",
|
|
904
|
+
name: [
|
|
905
|
+
"useState",
|
|
906
|
+
"useEffect",
|
|
907
|
+
"useLayoutEffect",
|
|
908
|
+
"useCallback",
|
|
909
|
+
"useMemo",
|
|
910
|
+
"useRef",
|
|
911
|
+
"useReducer",
|
|
912
|
+
"useImperativeHandle",
|
|
913
|
+
"useDebugValue",
|
|
914
|
+
"useDeferredValue",
|
|
915
|
+
"useTransition",
|
|
916
|
+
"useId",
|
|
917
|
+
"useSyncExternalStore",
|
|
918
|
+
"useInsertionEffect",
|
|
919
|
+
"useContext"
|
|
920
|
+
]
|
|
921
|
+
};
|
|
922
|
+
const EFFECTOR_FACTORY_SPEC = {
|
|
923
|
+
from: "package",
|
|
924
|
+
package: "effector",
|
|
925
|
+
name: [...EFFECTOR_FACTORIES]
|
|
926
|
+
};
|
|
927
|
+
const EFFECTOR_OPERATOR_SPEC = {
|
|
928
|
+
from: "package",
|
|
929
|
+
package: "effector",
|
|
930
|
+
name: [...EFFECTOR_OPERATORS]
|
|
931
|
+
};
|
|
932
|
+
const EFFECTOR_FACTORIO_SHAPE = [
|
|
933
|
+
"useModel",
|
|
934
|
+
"createModel",
|
|
935
|
+
"Provider",
|
|
936
|
+
"@@unitShape"
|
|
937
|
+
];
|
|
938
|
+
var no_units_spawn_in_render_default = createRule({
|
|
939
|
+
name: "no-units-spawn-in-render",
|
|
940
|
+
meta: {
|
|
941
|
+
type: "problem",
|
|
942
|
+
docs: { description: "Forbid creating Effector units or calling operators inside React components or hooks." },
|
|
943
|
+
messages: {
|
|
944
|
+
noFactoryInRender: "Creating Effector units with \"{{ name }}\" inside React component or hook is forbidden, since it may cause memory leaks and other bugs.",
|
|
945
|
+
noOperatorInRender: "Using Effector operator \"{{ name }}\" inside React component or hook is forbidden, since it may cause memory leaks and other bugs.",
|
|
946
|
+
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."
|
|
947
|
+
},
|
|
948
|
+
schema: [{
|
|
949
|
+
type: "object",
|
|
950
|
+
properties: { detectCustomFactories: { oneOf: [{ type: "boolean" }, {
|
|
951
|
+
type: "object",
|
|
952
|
+
properties: { allowlist: {
|
|
953
|
+
type: "array",
|
|
954
|
+
items: { type: "string" },
|
|
955
|
+
uniqueItems: true
|
|
956
|
+
} },
|
|
957
|
+
required: ["allowlist"],
|
|
958
|
+
additionalProperties: false
|
|
959
|
+
}] } },
|
|
960
|
+
additionalProperties: false
|
|
961
|
+
}]
|
|
962
|
+
},
|
|
963
|
+
defaultOptions: [{ detectCustomFactories: true }],
|
|
964
|
+
create: (context, [options]) => {
|
|
965
|
+
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
|
|
966
|
+
const checker = services.program.getTypeChecker();
|
|
967
|
+
const { detectCustomFactories } = options;
|
|
968
|
+
const allowlist = typeof detectCustomFactories === "object" ? new Set(detectCustomFactories.allowlist) : void 0;
|
|
969
|
+
const stack = { render: [] };
|
|
970
|
+
const effectorImports = /* @__PURE__ */ new Map();
|
|
971
|
+
return {
|
|
972
|
+
[`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`} > ImportSpecifier[imported.type="Identifier"]`]: (node) => {
|
|
973
|
+
const imported = node.imported.name;
|
|
974
|
+
const local = node.local.name;
|
|
975
|
+
if (EFFECTOR_FACTORIES.has(imported)) effectorImports.set(local, "factory");
|
|
976
|
+
else if (EFFECTOR_OPERATORS.has(imported)) effectorImports.set(local, "operator");
|
|
977
|
+
},
|
|
978
|
+
[`FunctionDeclaration, FunctionExpression, ArrowFunctionExpression`]: (node) => {
|
|
979
|
+
if (stack.render.at(-1) ?? false) return void stack.render.push(true);
|
|
980
|
+
const name = nameOf.function(node);
|
|
981
|
+
if (name && UseRegex.test(name.name)) return void stack.render.push(true);
|
|
982
|
+
const tsnode = services.esTreeNodeToTSNodeMap.get(node);
|
|
983
|
+
const signature = checker.getSignatureFromDeclaration(tsnode);
|
|
984
|
+
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getVoidType();
|
|
985
|
+
if (returnType.isUnion() ? returnType.types.some((type) => isType.jsx(type, services.program)) : isType.jsx(returnType, services.program)) return void stack.render.push(true);
|
|
986
|
+
const inferred = (0, typescript.isExpression)(tsnode) && (0, _typescript_eslint_type_utils.getContextualType)(checker, tsnode) || checker.getUnknownType();
|
|
987
|
+
if (inferred.isUnion() ? inferred.types.some((type) => isType.component(type, services.program)) : isType.component(inferred, services.program)) return void stack.render.push(true);
|
|
988
|
+
stack.render.push(false);
|
|
989
|
+
},
|
|
990
|
+
[`:matches(FunctionDeclaration, FunctionExpression, ArrowFunctionExpression):exit`]: () => void stack.render.pop(),
|
|
991
|
+
"ClassDeclaration": () => void stack.render.push(false),
|
|
992
|
+
"ClassDeclaration:exit": () => void stack.render.pop(),
|
|
993
|
+
"CallExpression": (node) => {
|
|
994
|
+
if (!(stack.render.at(-1) ?? false)) return;
|
|
995
|
+
const calleeName = getCalleeName(node.callee);
|
|
996
|
+
switch (calleeName ? effectorImports.get(calleeName) : void 0) {
|
|
997
|
+
case "factory": return context.report({
|
|
998
|
+
node,
|
|
999
|
+
messageId: "noFactoryInRender",
|
|
1000
|
+
data: { name: calleeName }
|
|
1001
|
+
});
|
|
1002
|
+
case "operator": return context.report({
|
|
1003
|
+
node,
|
|
1004
|
+
messageId: "noOperatorInRender",
|
|
1005
|
+
data: { name: calleeName }
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
if (detectCustomFactories === false) return;
|
|
1009
|
+
const returnType = services.getTypeAtLocation(node);
|
|
1010
|
+
if (!hasEffectorUnitInType({
|
|
1011
|
+
node: services.esTreeNodeToTSNodeMap.get(node),
|
|
1012
|
+
checker,
|
|
1013
|
+
program: services.program
|
|
1014
|
+
}, returnType)) return;
|
|
1015
|
+
const calleeType = services.getTypeAtLocation(node.callee);
|
|
1016
|
+
const displayName = calleeName ?? "<expression>";
|
|
1017
|
+
if ((0, _typescript_eslint_type_utils.typeMatchesSpecifier)(calleeType, REACT_HOOKS_SPEC, services.program)) return;
|
|
1018
|
+
if (isEffectorFactorioHook(node.callee, services.getTypeAtLocation)) return;
|
|
1019
|
+
if ((0, _typescript_eslint_type_utils.typeMatchesSpecifier)(calleeType, EFFECTOR_FACTORY_SPEC, services.program)) return context.report({
|
|
1020
|
+
node,
|
|
1021
|
+
messageId: "noFactoryInRender",
|
|
1022
|
+
data: { name: displayName }
|
|
1023
|
+
});
|
|
1024
|
+
if ((0, _typescript_eslint_type_utils.typeMatchesSpecifier)(calleeType, EFFECTOR_OPERATOR_SPEC, services.program)) return context.report({
|
|
1025
|
+
node,
|
|
1026
|
+
messageId: "noOperatorInRender",
|
|
1027
|
+
data: { name: displayName }
|
|
1028
|
+
});
|
|
1029
|
+
if (allowlist && calleeName && allowlist.has(calleeName)) return;
|
|
1030
|
+
context.report({
|
|
1031
|
+
node,
|
|
1032
|
+
messageId: "noCustomFactoryInRender",
|
|
1033
|
+
data: { name: displayName }
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
const UseRegex = /^use[A-Z0-9].*$/;
|
|
1040
|
+
function getCalleeName(callee) {
|
|
1041
|
+
if (callee.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return callee.name;
|
|
1042
|
+
if (callee.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && callee.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return callee.property.name;
|
|
1043
|
+
else return null;
|
|
1044
|
+
}
|
|
1045
|
+
function hasEffectorUnitInType(ctx, type, depth = 3) {
|
|
1046
|
+
if (isType.unit(type, ctx.program)) return true;
|
|
1047
|
+
if (depth <= 0) return false;
|
|
1048
|
+
if (type.isUnion()) return type.types.some((type) => hasEffectorUnitInType(ctx, type, depth));
|
|
1049
|
+
for (const property of type.getProperties()) if (hasEffectorUnitInType(ctx, ctx.checker.getTypeOfSymbolAtLocation(property, ctx.node), depth - 1)) return true;
|
|
1050
|
+
return false;
|
|
1051
|
+
}
|
|
1052
|
+
function isEffectorFactorioHook(callee, getTypeAtLocation) {
|
|
1053
|
+
if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression) return false;
|
|
1054
|
+
const objectType = getTypeAtLocation(callee.object);
|
|
1055
|
+
const propertyNames = new Set(objectType.getProperties().map((p) => p.getName()));
|
|
1056
|
+
return EFFECTOR_FACTORIO_SHAPE.every((name) => propertyNames.has(name));
|
|
1057
|
+
}
|
|
1058
|
+
//#endregion
|
|
1059
|
+
//#region src/rules/no-unnecessary-combination/no-unnecessary-combination.ts
|
|
842
1060
|
var no_unnecessary_combination_default = createRule({
|
|
843
1061
|
name: "no-unnecessary-combination",
|
|
844
1062
|
meta: {
|
|
@@ -909,6 +1127,8 @@ function isFunction(node, services) {
|
|
|
909
1127
|
return checker.getTypeAtLocation(tsnode).getCallSignatures().length > 0;
|
|
910
1128
|
} else return false;
|
|
911
1129
|
}
|
|
1130
|
+
//#endregion
|
|
1131
|
+
//#region src/rules/no-unnecessary-duplication/no-unnecessary-duplication.ts
|
|
912
1132
|
var no_unnecessary_duplication_default = createRule({
|
|
913
1133
|
name: "no-unnecessary-duplication",
|
|
914
1134
|
meta: {
|
|
@@ -985,6 +1205,8 @@ function compare(clock, source, limit = 5) {
|
|
|
985
1205
|
}
|
|
986
1206
|
return false;
|
|
987
1207
|
}
|
|
1208
|
+
//#endregion
|
|
1209
|
+
//#region src/rules/no-useless-methods/no-useless-methods.ts
|
|
988
1210
|
var no_useless_methods_default = createRule({
|
|
989
1211
|
name: "no-useless-methods",
|
|
990
1212
|
meta: {
|
|
@@ -1035,6 +1257,8 @@ var no_useless_methods_default = createRule({
|
|
|
1035
1257
|
});
|
|
1036
1258
|
const selector$2 = { method: `ImportSpecifier[imported.name=/(sample|guard)/]` };
|
|
1037
1259
|
const query = { watch: esquery.default.parse("CallExpression:has(> MemberExpression.callee[property.name=watch]:has(> CallExpression.object))") };
|
|
1260
|
+
//#endregion
|
|
1261
|
+
//#region src/rules/no-watch/no-watch.ts
|
|
1038
1262
|
var no_watch_default = createRule({
|
|
1039
1263
|
name: "no-watch",
|
|
1040
1264
|
meta: {
|
|
@@ -1056,6 +1280,8 @@ var no_watch_default = createRule({
|
|
|
1056
1280
|
} };
|
|
1057
1281
|
}
|
|
1058
1282
|
});
|
|
1283
|
+
//#endregion
|
|
1284
|
+
//#region src/rules/prefer-useUnit/prefer-useUnit.ts
|
|
1059
1285
|
var prefer_useUnit_default = createRule({
|
|
1060
1286
|
name: "prefer-useUnit",
|
|
1061
1287
|
meta: {
|
|
@@ -1087,6 +1313,8 @@ const selector$1 = {
|
|
|
1087
1313
|
useStore: `ImportSpecifier[imported.name=useStore]`,
|
|
1088
1314
|
useEvent: `ImportSpecifier[imported.name=useEvent]`
|
|
1089
1315
|
};
|
|
1316
|
+
//#endregion
|
|
1317
|
+
//#region src/rules/require-pickup-in-persist/require-pickup-in-persist.ts
|
|
1090
1318
|
var require_pickup_in_persist_default = createRule({
|
|
1091
1319
|
name: "require-pickup-in-persist",
|
|
1092
1320
|
meta: {
|
|
@@ -1116,6 +1344,8 @@ const selector = {
|
|
|
1116
1344
|
call: `[callee.type="Identifier"]`,
|
|
1117
1345
|
config: `[arguments.length=1][arguments.0.type="ObjectExpression"]`
|
|
1118
1346
|
};
|
|
1347
|
+
//#endregion
|
|
1348
|
+
//#region src/rules/strict-effect-handlers/strict-effect-handlers.ts
|
|
1119
1349
|
var strict_effect_handlers_default = createRule({
|
|
1120
1350
|
name: "strict-effect-handlers",
|
|
1121
1351
|
meta: {
|
|
@@ -1143,9 +1373,9 @@ var strict_effect_handlers_default = createRule({
|
|
|
1143
1373
|
});
|
|
1144
1374
|
};
|
|
1145
1375
|
const exit = (node) => {
|
|
1146
|
-
const scope
|
|
1147
|
-
if (!scope
|
|
1148
|
-
if (scope
|
|
1376
|
+
const scope = stack.pop();
|
|
1377
|
+
if (!scope) return;
|
|
1378
|
+
if (scope.effect && scope.regular) context.report({
|
|
1149
1379
|
node,
|
|
1150
1380
|
messageId: "mixed"
|
|
1151
1381
|
});
|
|
@@ -1161,38 +1391,36 @@ var strict_effect_handlers_default = createRule({
|
|
|
1161
1391
|
};
|
|
1162
1392
|
}
|
|
1163
1393
|
});
|
|
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
1394
|
const ruleset = {
|
|
1190
|
-
recommended
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1395
|
+
recommended: {
|
|
1396
|
+
"effector/enforce-effect-naming-convention": "error",
|
|
1397
|
+
"effector/enforce-store-naming-convention": "error",
|
|
1398
|
+
"effector/keep-options-order": "warn",
|
|
1399
|
+
"effector/no-ambiguity-target": "warn",
|
|
1400
|
+
"effector/no-duplicate-on": "error",
|
|
1401
|
+
"effector/no-forward": "error",
|
|
1402
|
+
"effector/no-getState": "error",
|
|
1403
|
+
"effector/no-guard": "error",
|
|
1404
|
+
"effector/no-unnecessary-combination": "warn",
|
|
1405
|
+
"effector/no-unnecessary-duplication": "warn",
|
|
1406
|
+
"effector/no-useless-methods": "error",
|
|
1407
|
+
"effector/no-watch": "warn"
|
|
1408
|
+
},
|
|
1409
|
+
patronum: { "effector/no-patronum-debug": "warn" },
|
|
1410
|
+
scope: {
|
|
1411
|
+
"effector/require-pickup-in-persist": "error",
|
|
1412
|
+
"effector/strict-effect-handlers": "error"
|
|
1413
|
+
},
|
|
1414
|
+
react: {
|
|
1415
|
+
"effector/enforce-gate-naming-convention": "error",
|
|
1416
|
+
"effector/mandatory-scope-binding": "error",
|
|
1417
|
+
"effector/no-units-spawn-in-render": "error",
|
|
1418
|
+
"effector/prefer-useUnit": "error"
|
|
1419
|
+
},
|
|
1420
|
+
future: { "effector/no-domain-unit-creators": "warn" }
|
|
1195
1421
|
};
|
|
1422
|
+
//#endregion
|
|
1423
|
+
//#region src/index.ts
|
|
1196
1424
|
const base = {
|
|
1197
1425
|
meta: {
|
|
1198
1426
|
name,
|
|
@@ -1213,6 +1441,7 @@ const base = {
|
|
|
1213
1441
|
"no-getState": no_getState_default,
|
|
1214
1442
|
"no-guard": no_guard_default,
|
|
1215
1443
|
"no-patronum-debug": no_patronum_debug_default,
|
|
1444
|
+
"no-units-spawn-in-render": no_units_spawn_in_render_default,
|
|
1216
1445
|
"no-unnecessary-combination": no_unnecessary_combination_default,
|
|
1217
1446
|
"no-unnecessary-duplication": no_unnecessary_duplication_default,
|
|
1218
1447
|
"no-useless-methods": no_useless_methods_default,
|
|
@@ -1255,5 +1484,5 @@ const flatConfigs = {
|
|
|
1255
1484
|
const plugin = base;
|
|
1256
1485
|
plugin.configs = legacyConfigs;
|
|
1257
1486
|
plugin.flatConfigs = flatConfigs;
|
|
1258
|
-
|
|
1259
|
-
module.exports =
|
|
1487
|
+
//#endregion
|
|
1488
|
+
module.exports = plugin;
|