Skip to content

Commit 255fe4a

Browse files
authored
rule no-duplicate-clock-or-source-array-values (#155)
* rule `no-duplicate-clock-or-source-array-values` * Add docs to `no-duplicate-clock-or-source-array-values`
1 parent ab929b3 commit 255fe4a

8 files changed

+307
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# effector/no-duplicate-clock-or-source-array-values
2+
3+
This rule forbids unit duplicates on `source` and `clock` with sample and guard methods.
4+
5+
```js
6+
// from
7+
import { createEvent, createStore, sample } from "effector";
8+
import { createEffect } from "effector";
9+
10+
const currentOrderUpdated = createEvent();
11+
const setUnloadDeliveryDateFx = createEffect();
12+
13+
const $store = createStore(null);
14+
15+
sample({
16+
clock: [
17+
setUnloadDeliveryDateFx.doneData,
18+
setUnloadDeliveryDateFx.doneData,
19+
$store,
20+
],
21+
filter: Boolean,
22+
target: currentOrderUpdated,
23+
});
24+
```
25+
26+
---
27+
28+
```js
29+
// to
30+
import { createEvent, createStore, sample } from "effector";
31+
import { createEffect } from "effector";
32+
33+
const currentOrderUpdated = createEvent();
34+
const setUnloadDeliveryDateFx = createEffect();
35+
36+
const $store = createStore(null);
37+
38+
sample({
39+
clock: [
40+
setUnloadDeliveryDateFx.doneData, // dublicate removed
41+
$store,
42+
],
43+
filter: Boolean,
44+
target: currentOrderUpdated,
45+
});
46+
```

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = {
55
"no-getState": require("./rules/no-getState/no-getState"),
66
"no-unnecessary-duplication": require("./rules/no-unnecessary-duplication/no-unnecessary-duplication"),
77
"prefer-sample-over-forward-with-mapping": require("./rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping"),
8+
"no-duplicate-clock-or-source-array-values": require("./rules/no-duplicate-clock-or-source-array-values/no-duplicate-clock-or-source-array-values"),
89
"no-useless-methods": require("./rules/no-useless-methods/no-useless-methods"),
910
"no-ambiguity-target": require("./rules/no-ambiguity-target/no-ambiguity-target"),
1011
"no-watch": require("./rules/no-watch/no-watch"),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { createEvent, createStore, sample } from "effector";
2+
import { createEffect } from "effector";
3+
4+
const currentOrderUpdated = createEvent();
5+
6+
const setUnloadDeliveryDateFx = createEffect();
7+
const setLoadDeliveryDateFx = createEffect();
8+
9+
const $store = createStore(null);
10+
const clickOnBtn = createEvent();
11+
12+
sample({
13+
clock: [
14+
setUnloadDeliveryDateFx.doneData,
15+
setLoadDeliveryDateFx,
16+
$store,
17+
clickOnBtn,
18+
],
19+
filter: Boolean,
20+
target: currentOrderUpdated,
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { createEvent, createStore, guard } from "effector";
2+
import { createEffect } from "effector";
3+
4+
const currentOrderUpdated = createEvent();
5+
6+
const setUnloadDeliveryDateFx = createEffect();
7+
8+
const $$order = {
9+
setUnloadDeliveryDateFx,
10+
};
11+
12+
const $store = createStore(null);
13+
const clickOnBtn = createEvent();
14+
15+
guard({
16+
source: [$store],
17+
clock: [
18+
setUnloadDeliveryDateFx.doneData,
19+
$$order.setUnloadDeliveryDateFx.doneData,
20+
clickOnBtn,
21+
setUnloadDeliveryDateFx.doneData,
22+
clickOnBtn,
23+
],
24+
filter: Boolean,
25+
target: currentOrderUpdated,
26+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createEvent, createStore, sample } from "effector";
2+
import { createEffect } from "effector";
3+
4+
const currentOrderUpdated = createEvent();
5+
6+
const setUnloadDeliveryDateFx = createEffect();
7+
8+
const $$order = {
9+
setUnloadDeliveryDateFx,
10+
};
11+
12+
const $store = createStore(null);
13+
const clickOnBtn = createEvent();
14+
15+
sample({
16+
source: [$store, $store],
17+
clock: [
18+
setUnloadDeliveryDateFx.doneData,
19+
$$order.setUnloadDeliveryDateFx.doneData,
20+
setUnloadDeliveryDateFx.doneData,
21+
],
22+
filter: Boolean,
23+
target: currentOrderUpdated,
24+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
2+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
3+
const { method } = require("../../utils/method");
4+
const {} = require("../../utils/traverse-nested-object-node");
5+
6+
module.exports = {
7+
meta: {
8+
type: "problem",
9+
hasSuggestions: true,
10+
docs: {
11+
description: "Forbids unit duplicates on `source` and `clock``",
12+
category: "Quality",
13+
recommended: true,
14+
url: createLinkToRule("no-duplicate-clock-or-source-array-values"),
15+
},
16+
messages: {
17+
duplicatesInClock: "Clock contains duplicate units - {{ memberPath }}.",
18+
duplicatesInSource: "Source contains duplicate units - {{ memberPath }}.",
19+
removeDuplicate: "Remove duplicate {{ memberPath }}.",
20+
},
21+
schema: [],
22+
},
23+
create(context) {
24+
const importedFromEffector = new Map();
25+
26+
return {
27+
ImportDeclaration(node) {
28+
extractImportedFrom({
29+
importMap: importedFromEffector,
30+
node,
31+
packageName: "effector",
32+
});
33+
},
34+
CallExpression(node) {
35+
if (
36+
method.isNot(["sample", "guard"], {
37+
node,
38+
importMap: importedFromEffector,
39+
})
40+
) {
41+
return;
42+
}
43+
44+
const properties = getSourceOrClockProperties(node.arguments[0]);
45+
46+
properties.forEach(({ key, value }) => {
47+
const propType = key.name;
48+
const elements = value.elements;
49+
50+
const usedUnits = new Set();
51+
52+
for (const node of elements) {
53+
const memberPath = createMemberExpressionPath(node);
54+
55+
if (usedUnits.has(memberPath)) {
56+
const messageId = getMessageIdByPropType(propType);
57+
58+
context.report({
59+
node,
60+
messageId,
61+
data: {
62+
memberPath,
63+
},
64+
suggest: [
65+
{
66+
messageId: "removeDuplicate",
67+
data: { memberPath },
68+
fix(fixer) {
69+
return fixer.remove(node);
70+
},
71+
},
72+
],
73+
});
74+
75+
return;
76+
}
77+
78+
usedUnits.add(memberPath);
79+
}
80+
});
81+
},
82+
};
83+
},
84+
};
85+
86+
function createMemberExpressionPath(node, chain = "") {
87+
const compactStrings = (...args) => args.filter(Boolean).join(".");
88+
89+
if (node.type === "MemberExpression") {
90+
const propertyName = node.property.name;
91+
92+
const updatedChain = compactStrings(propertyName, chain);
93+
94+
return createMemberExpressionPath(node.object, updatedChain);
95+
}
96+
97+
chain = compactStrings(node.name, chain);
98+
99+
// remove last dot
100+
return chain.slice(0, -1);
101+
}
102+
103+
function getSourceOrClockProperties(node) {
104+
if (node.type !== "ObjectExpression") return [];
105+
106+
const allowedProps = ["clock", "source"];
107+
108+
const isClockOrSourceArray = (prop) => {
109+
return (
110+
allowedProps.includes(prop.key.name) &&
111+
prop.value.type === "ArrayExpression"
112+
);
113+
};
114+
115+
return node.properties.filter(isClockOrSourceArray);
116+
}
117+
118+
function getMessageIdByPropType(propType) {
119+
return propType === "clock" ? "duplicatesInClock" : "duplicatesInSource";
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
https://eslint.effector.dev/rules/no-duplicate-clock-or-source-array-values.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const { RuleTester } = require("@typescript-eslint/rule-tester");
2+
const { join } = require("path");
3+
4+
const { readExample } = require("../../utils/read-example");
5+
const rule = require("./no-duplicate-clock-or-source-array-values");
6+
7+
const ruleTester = new RuleTester({
8+
parser: "@typescript-eslint/parser",
9+
parserOptions: {
10+
ecmaVersion: 2020,
11+
sourceType: "module",
12+
project: "./tsconfig.json",
13+
tsconfigRootDir: join(__dirname, ".."),
14+
},
15+
});
16+
17+
const readExampleForTheRule = (name) => ({
18+
code: readExample(__dirname, name),
19+
filename: join(__dirname, "examples", name),
20+
});
21+
22+
ruleTester.run(
23+
"effector/no-duplicate-clock-or-source-array-values.ts.test",
24+
rule,
25+
{
26+
valid: ["correct-sample.ts"].map(readExampleForTheRule),
27+
28+
invalid: [
29+
...["incorrect-sample.ts"].map(readExampleForTheRule).map((result) => ({
30+
...result,
31+
errors: [
32+
{
33+
messageId: "duplicatesInSource",
34+
type: "Identifier",
35+
suggestions: [
36+
{
37+
messageId: "removeDuplicate",
38+
},
39+
],
40+
},
41+
{
42+
messageId: "duplicatesInClock",
43+
type: "MemberExpression",
44+
suggestions: [
45+
{
46+
messageId: "removeDuplicate",
47+
},
48+
],
49+
},
50+
],
51+
})),
52+
...["incorrect-guard.ts"].map(readExampleForTheRule).map((result) => ({
53+
...result,
54+
errors: [
55+
{
56+
messageId: "duplicatesInClock",
57+
type: "MemberExpression",
58+
suggestions: [
59+
{
60+
messageId: "removeDuplicate",
61+
},
62+
],
63+
},
64+
],
65+
})),
66+
],
67+
}
68+
);

0 commit comments

Comments
 (0)