eslint-plugin-effector 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.cjs +345 -116
- package/dist/index.d.cts +28 -24
- package/dist/index.d.mts +28 -24
- package/dist/index.mjs +344 -116
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
<h1 align="center">eslint-plugin-effector</h1>
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://www.npmjs.com/package/eslint-plugin-effector"><img src="https://badgen.net/npm/v/eslint-plugin-effector?color=blue" alt="npm version"></a>
|
|
5
|
+
<a href="https://www.npmjs.com/package/eslint-plugin-effector"><img src="https://badgen.net/badge/provenance/yes?color=green&icon=npm" alt="npm provenance"></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/eslint-plugin-effector"><img src="https://badgen.net/npm/dm/eslint-plugin-effector?color=orange" alt="monthly downloads"></a>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
3
9
|
An ESLint plugin for enforcing best practices for [Effector](https://effector.dev).
|
|
4
10
|
|
|
5
11
|
For comprehensive documentation, including rules and configuration guides, visit official documentation at [eslint.effector.dev](https://eslint.effector.dev).
|
package/dist/index.cjs
CHANGED
|
@@ -23,17 +23,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
let _typescript_eslint_utils = require("@typescript-eslint/utils");
|
|
24
24
|
let _typescript_eslint_type_utils = require("@typescript-eslint/type-utils");
|
|
25
25
|
let typescript = require("typescript");
|
|
26
|
+
typescript = __toESM(typescript, 1);
|
|
26
27
|
let esquery = require("esquery");
|
|
27
|
-
esquery = __toESM(esquery);
|
|
28
|
+
esquery = __toESM(esquery, 1);
|
|
28
29
|
//#region package.json
|
|
29
30
|
var name = "eslint-plugin-effector";
|
|
30
|
-
var version = "0.
|
|
31
|
+
var version = "0.19.0";
|
|
31
32
|
//#endregion
|
|
32
33
|
//#region src/shared/create.ts
|
|
33
34
|
const createRule = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://eslint.effector.dev/rules/${name}`);
|
|
34
35
|
//#endregion
|
|
35
36
|
//#region src/shared/is.ts
|
|
36
|
-
const check = (symbol, types, from) => {
|
|
37
|
+
const check$1 = (symbol, types, from) => {
|
|
37
38
|
const name = symbol.getName();
|
|
38
39
|
const declarations = symbol.declarations ?? [];
|
|
39
40
|
return types.includes(name) && declarations.map((decl) => decl.getSourceFile().fileName).some((fname) => fname.includes("node_modules") && fname.includes(from));
|
|
@@ -54,6 +55,11 @@ const isType = {
|
|
|
54
55
|
package: "effector",
|
|
55
56
|
name: "Effect"
|
|
56
57
|
}, program),
|
|
58
|
+
domain: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
|
|
59
|
+
from: "package",
|
|
60
|
+
package: "effector",
|
|
61
|
+
name: "Domain"
|
|
62
|
+
}, program),
|
|
57
63
|
unit: (type, program) => {
|
|
58
64
|
return (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
|
|
59
65
|
from: "package",
|
|
@@ -68,14 +74,9 @@ const isType = {
|
|
|
68
74
|
]
|
|
69
75
|
}, program);
|
|
70
76
|
},
|
|
71
|
-
domain: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
|
|
72
|
-
from: "package",
|
|
73
|
-
package: "effector",
|
|
74
|
-
name: "Domain"
|
|
75
|
-
}, program),
|
|
76
77
|
gate: (type) => {
|
|
77
78
|
const symbol = type.getSymbol() ?? type.aliasSymbol;
|
|
78
|
-
return symbol ? check(symbol, ["Gate"], "effector") : false;
|
|
79
|
+
return symbol ? check$1(symbol, ["Gate"], "effector") : false;
|
|
79
80
|
},
|
|
80
81
|
jsx: (type, program) => {
|
|
81
82
|
return (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
|
|
@@ -110,7 +111,7 @@ var enforce_effect_naming_convention_default = createRule({
|
|
|
110
111
|
type: "problem",
|
|
111
112
|
docs: { description: "Enforce Fx as a suffix for any Effector Effect." },
|
|
112
113
|
messages: {
|
|
113
|
-
invalid: "Effect
|
|
114
|
+
invalid: "Effect \"{{ current }}\" should be named with `Fx` suffix, rename it to \"{{ fixed }}\"",
|
|
114
115
|
rename: "Rename \"{{ current }}\" to \"{{ fixed }}\""
|
|
115
116
|
},
|
|
116
117
|
schema: [],
|
|
@@ -119,33 +120,188 @@ var enforce_effect_naming_convention_default = createRule({
|
|
|
119
120
|
defaultOptions: [],
|
|
120
121
|
create: (context) => {
|
|
121
122
|
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
|
|
122
|
-
return {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
+
};
|
|
146
175
|
}
|
|
147
176
|
});
|
|
148
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
|
+
}
|
|
149
305
|
//#endregion
|
|
150
306
|
//#region src/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.ts
|
|
151
307
|
var enforce_gate_naming_convention_default = createRule({
|
|
@@ -213,43 +369,81 @@ var enforce_store_naming_convention_default = createRule({
|
|
|
213
369
|
defaultOptions: [{ mode: "prefix" }],
|
|
214
370
|
create: (context, [options]) => {
|
|
215
371
|
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const current = node.id.name;
|
|
220
|
-
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, "");
|
|
221
375
|
const fixed = options.mode === "prefix" ? `$${trimmed}` : `${trimmed}$`;
|
|
222
|
-
|
|
223
|
-
current,
|
|
376
|
+
return {
|
|
377
|
+
current: node.name,
|
|
224
378
|
convention: options.mode,
|
|
225
379
|
fixed
|
|
226
380
|
};
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
+
};
|
|
242
428
|
}
|
|
243
429
|
});
|
|
244
430
|
const PrefixRegex = /^[^$]/;
|
|
245
431
|
const PostfixRegex = /[^$]$/;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
});
|
|
253
447
|
//#endregion
|
|
254
448
|
//#region src/rules/keep-options-order/keep-options-order.ts
|
|
255
449
|
var keep_options_order_default = createRule({
|
|
@@ -269,8 +463,8 @@ var keep_options_order_default = createRule({
|
|
|
269
463
|
const source = context.sourceCode;
|
|
270
464
|
const imports = /* @__PURE__ */ new Set();
|
|
271
465
|
return {
|
|
272
|
-
[`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`} > ${selector$
|
|
273
|
-
[`CallExpression${selector$
|
|
466
|
+
[`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`} > ${selector$12.method}`]: (node) => imports.add(node.local.name),
|
|
467
|
+
[`CallExpression${selector$12.call}:has(${selector$12.argument})`]: (node) => {
|
|
274
468
|
if (!imports.has(node.callee.name)) return;
|
|
275
469
|
const [config] = node.arguments;
|
|
276
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;
|
|
@@ -309,7 +503,7 @@ const TRUE_ORDER = [
|
|
|
309
503
|
"batch",
|
|
310
504
|
"name"
|
|
311
505
|
];
|
|
312
|
-
const selector$
|
|
506
|
+
const selector$12 = {
|
|
313
507
|
method: `ImportSpecifier[imported.name=/(sample|guard)/]`,
|
|
314
508
|
call: `[callee.type="Identifier"][arguments.length=1]`,
|
|
315
509
|
argument: `ObjectExpression.arguments`
|
|
@@ -334,70 +528,109 @@ function functionToName(node) {
|
|
|
334
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;
|
|
335
529
|
return null;
|
|
336
530
|
}
|
|
337
|
-
|
|
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
|
+
};
|
|
338
546
|
//#endregion
|
|
339
547
|
//#region src/rules/mandatory-scope-binding/mandatory-scope-binding.ts
|
|
340
548
|
var mandatory_scope_binding_default = createRule({
|
|
341
549
|
name: "mandatory-scope-binding",
|
|
342
550
|
meta: {
|
|
343
551
|
type: "problem",
|
|
344
|
-
docs: { description: "Forbid `Event` and `Effect` usage without `useUnit` in React
|
|
345
|
-
messages: { useUnitNeeded: "\"{{ name }}\" must be wrapped with `useUnit` from `effector-react` before usage inside React
|
|
552
|
+
docs: { description: "Forbid `Event` and `Effect` usage without `useUnit` in React." },
|
|
553
|
+
messages: { useUnitNeeded: "\"{{ name }}\" must be wrapped with `useUnit` from `effector-react` before usage inside React." },
|
|
346
554
|
schema: []
|
|
347
555
|
},
|
|
348
556
|
defaultOptions: [],
|
|
349
557
|
create: (context) => {
|
|
350
558
|
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
|
|
351
559
|
const checker = services.program.getTypeChecker();
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
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
|
+
});
|
|
355
585
|
};
|
|
356
586
|
return {
|
|
357
|
-
[`
|
|
358
|
-
if (
|
|
587
|
+
[`:matches(${selector$11.function})`]: (node) => {
|
|
588
|
+
if (inRender.at(-1) ?? false) return void inRender.push(true);
|
|
359
589
|
const name = nameOf.function(node);
|
|
360
|
-
if (name && UseRegex$1.test(name.name)) return void
|
|
590
|
+
if (name && UseRegex$1.test(name.name)) return void inRender.push(true);
|
|
361
591
|
const tsnode = services.esTreeNodeToTSNodeMap.get(node);
|
|
362
592
|
const signature = checker.getSignatureFromDeclaration(tsnode);
|
|
363
593
|
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getVoidType();
|
|
364
|
-
if (returnType.isUnion() ? returnType.types.some((type) => isType.jsx(type, services.program)) : isType.jsx(returnType, services.program)) return void
|
|
365
|
-
const inferred =
|
|
366
|
-
if (inferred.isUnion() ? inferred.types.some((type) => isType.component(type, services.program)) : isType.component(inferred, services.program)) return void
|
|
367
|
-
|
|
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);
|
|
368
598
|
},
|
|
369
|
-
[`:matches(
|
|
370
|
-
"ClassDeclaration": () => void
|
|
371
|
-
"ClassDeclaration:exit": () => void
|
|
599
|
+
[`:matches(${selector$11.function}):exit`]: () => void inRender.pop(),
|
|
600
|
+
"ClassDeclaration": () => void inRender.push(false),
|
|
601
|
+
"ClassDeclaration:exit": () => void inRender.pop(),
|
|
372
602
|
"CallExpression": (node) => {
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
package: "effector-react",
|
|
376
|
-
name: [
|
|
377
|
-
"useStore",
|
|
378
|
-
"useStoreMap",
|
|
379
|
-
"useList",
|
|
380
|
-
"useEvent",
|
|
381
|
-
"useUnit"
|
|
382
|
-
]
|
|
383
|
-
}, services.program);
|
|
384
|
-
stack.hook.push(isHook);
|
|
603
|
+
const id = nameOf.callee(node.callee), isEnteringHook = id !== null && UseRegex$1.test(id.name);
|
|
604
|
+
inHook.push(isEnteringHook);
|
|
385
605
|
},
|
|
386
|
-
"
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
context.report({
|
|
392
|
-
node,
|
|
393
|
-
messageId: "useUnitNeeded",
|
|
394
|
-
data: { name: node.name }
|
|
395
|
-
});
|
|
396
|
-
}
|
|
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)
|
|
397
611
|
};
|
|
398
612
|
}
|
|
399
613
|
});
|
|
400
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
|
+
};
|
|
401
634
|
//#endregion
|
|
402
635
|
//#region src/shared/locate.ts
|
|
403
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);
|
|
@@ -736,7 +969,7 @@ var no_getState_default = createRule({
|
|
|
736
969
|
return { [`CallExpression[callee.type="MemberExpression"][callee.property.name="getState"]`]: (node) => {
|
|
737
970
|
const type = services.getTypeAtLocation(node.callee.object);
|
|
738
971
|
if (!isType.store(type, services.program)) return;
|
|
739
|
-
const name =
|
|
972
|
+
const name = nameOf.expression.simple(node.callee.object);
|
|
740
973
|
if (name) context.report({
|
|
741
974
|
node,
|
|
742
975
|
messageId: "named",
|
|
@@ -749,11 +982,6 @@ var no_getState_default = createRule({
|
|
|
749
982
|
} };
|
|
750
983
|
}
|
|
751
984
|
});
|
|
752
|
-
const toName$1 = (node) => {
|
|
753
|
-
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.name;
|
|
754
|
-
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && !node.computed) return node.property.name;
|
|
755
|
-
return null;
|
|
756
|
-
};
|
|
757
985
|
//#endregion
|
|
758
986
|
//#region src/rules/no-guard/no-guard.ts
|
|
759
987
|
var no_guard_default = createRule({
|
|
@@ -1051,6 +1279,8 @@ function hasEffectorUnitInType(ctx, type, depth = 3) {
|
|
|
1051
1279
|
}
|
|
1052
1280
|
function isEffectorFactorioHook(callee, getTypeAtLocation) {
|
|
1053
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;
|
|
1054
1284
|
const objectType = getTypeAtLocation(callee.object);
|
|
1055
1285
|
const propertyNames = new Set(objectType.getProperties().map((p) => p.getName()));
|
|
1056
1286
|
return EFFECTOR_FACTORIO_SHAPE.every((name) => propertyNames.has(name));
|
|
@@ -1241,10 +1471,8 @@ var no_useless_methods_default = createRule({
|
|
|
1241
1471
|
if (locate.property("target", config)?.value) return;
|
|
1242
1472
|
}
|
|
1243
1473
|
const grandparent = node.parent.parent;
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
if (esquery.default.matches(grandparent, query.watch, ancestry, { visitorKeys })) return;
|
|
1247
|
-
}
|
|
1474
|
+
const ancestry = source.getAncestors(grandparent);
|
|
1475
|
+
if (esquery.default.matches(grandparent, query.watch, ancestry, { visitorKeys })) return;
|
|
1248
1476
|
const method = node.callee.name;
|
|
1249
1477
|
context.report({
|
|
1250
1478
|
node,
|
|
@@ -1374,7 +1602,6 @@ var strict_effect_handlers_default = createRule({
|
|
|
1374
1602
|
};
|
|
1375
1603
|
const exit = (node) => {
|
|
1376
1604
|
const scope = stack.pop();
|
|
1377
|
-
if (!scope) return;
|
|
1378
1605
|
if (scope.effect && scope.regular) context.report({
|
|
1379
1606
|
node,
|
|
1380
1607
|
messageId: "mixed"
|
|
@@ -1413,6 +1640,7 @@ const ruleset = {
|
|
|
1413
1640
|
},
|
|
1414
1641
|
react: {
|
|
1415
1642
|
"effector/enforce-gate-naming-convention": "error",
|
|
1643
|
+
"effector/enforce-exhaustive-useUnit-destructuring": "warn",
|
|
1416
1644
|
"effector/mandatory-scope-binding": "error",
|
|
1417
1645
|
"effector/no-units-spawn-in-render": "error",
|
|
1418
1646
|
"effector/prefer-useUnit": "error"
|
|
@@ -1429,6 +1657,7 @@ const base = {
|
|
|
1429
1657
|
},
|
|
1430
1658
|
rules: {
|
|
1431
1659
|
"enforce-effect-naming-convention": enforce_effect_naming_convention_default,
|
|
1660
|
+
"enforce-exhaustive-useUnit-destructuring": enforce_exhaustive_useUnit_destructuring_default,
|
|
1432
1661
|
"enforce-gate-naming-convention": enforce_gate_naming_convention_default,
|
|
1433
1662
|
"enforce-store-naming-convention": enforce_store_naming_convention_default,
|
|
1434
1663
|
"keep-options-order": keep_options_order_default,
|