Skip to content

Commit

Permalink
rule no-duplicate-clock-or-source-array-values (#155)
Browse files Browse the repository at this point in the history
* rule `no-duplicate-clock-or-source-array-values`

* Add docs to `no-duplicate-clock-or-source-array-values`
  • Loading branch information
gearonix authored Feb 26, 2024
1 parent ab929b3 commit 255fe4a
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 0 deletions.
46 changes: 46 additions & 0 deletions docs/rules/no-duplicate-clock-or-source-array-values.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# effector/no-duplicate-clock-or-source-array-values

This rule forbids unit duplicates on `source` and `clock` with sample and guard methods.

```js
// from
import { createEvent, createStore, sample } from "effector";
import { createEffect } from "effector";

const currentOrderUpdated = createEvent();
const setUnloadDeliveryDateFx = createEffect();

const $store = createStore(null);

sample({
clock: [
setUnloadDeliveryDateFx.doneData,
setUnloadDeliveryDateFx.doneData,
$store,
],
filter: Boolean,
target: currentOrderUpdated,
});
```

---

```js
// to
import { createEvent, createStore, sample } from "effector";
import { createEffect } from "effector";

const currentOrderUpdated = createEvent();
const setUnloadDeliveryDateFx = createEffect();

const $store = createStore(null);

sample({
clock: [
setUnloadDeliveryDateFx.doneData, // dublicate removed
$store,
],
filter: Boolean,
target: currentOrderUpdated,
});
```
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
"no-getState": require("./rules/no-getState/no-getState"),
"no-unnecessary-duplication": require("./rules/no-unnecessary-duplication/no-unnecessary-duplication"),
"prefer-sample-over-forward-with-mapping": require("./rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping"),
"no-duplicate-clock-or-source-array-values": require("./rules/no-duplicate-clock-or-source-array-values/no-duplicate-clock-or-source-array-values"),
"no-useless-methods": require("./rules/no-useless-methods/no-useless-methods"),
"no-ambiguity-target": require("./rules/no-ambiguity-target/no-ambiguity-target"),
"no-watch": require("./rules/no-watch/no-watch"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createEvent, createStore, sample } from "effector";
import { createEffect } from "effector";

const currentOrderUpdated = createEvent();

const setUnloadDeliveryDateFx = createEffect();
const setLoadDeliveryDateFx = createEffect();

const $store = createStore(null);
const clickOnBtn = createEvent();

sample({
clock: [
setUnloadDeliveryDateFx.doneData,
setLoadDeliveryDateFx,
$store,
clickOnBtn,
],
filter: Boolean,
target: currentOrderUpdated,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createEvent, createStore, guard } from "effector";
import { createEffect } from "effector";

const currentOrderUpdated = createEvent();

const setUnloadDeliveryDateFx = createEffect();

const $$order = {
setUnloadDeliveryDateFx,
};

const $store = createStore(null);
const clickOnBtn = createEvent();

guard({
source: [$store],
clock: [
setUnloadDeliveryDateFx.doneData,
$$order.setUnloadDeliveryDateFx.doneData,
clickOnBtn,
setUnloadDeliveryDateFx.doneData,
clickOnBtn,
],
filter: Boolean,
target: currentOrderUpdated,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createEvent, createStore, sample } from "effector";
import { createEffect } from "effector";

const currentOrderUpdated = createEvent();

const setUnloadDeliveryDateFx = createEffect();

const $$order = {
setUnloadDeliveryDateFx,
};

const $store = createStore(null);
const clickOnBtn = createEvent();

sample({
source: [$store, $store],
clock: [
setUnloadDeliveryDateFx.doneData,
$$order.setUnloadDeliveryDateFx.doneData,
setUnloadDeliveryDateFx.doneData,
],
filter: Boolean,
target: currentOrderUpdated,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
const { extractImportedFrom } = require("../../utils/extract-imported-from");
const { createLinkToRule } = require("../../utils/create-link-to-rule");
const { method } = require("../../utils/method");
const {} = require("../../utils/traverse-nested-object-node");

module.exports = {
meta: {
type: "problem",
hasSuggestions: true,
docs: {
description: "Forbids unit duplicates on `source` and `clock``",
category: "Quality",
recommended: true,
url: createLinkToRule("no-duplicate-clock-or-source-array-values"),
},
messages: {
duplicatesInClock: "Clock contains duplicate units - {{ memberPath }}.",
duplicatesInSource: "Source contains duplicate units - {{ memberPath }}.",
removeDuplicate: "Remove duplicate {{ memberPath }}.",
},
schema: [],
},
create(context) {
const importedFromEffector = new Map();

return {
ImportDeclaration(node) {
extractImportedFrom({
importMap: importedFromEffector,
node,
packageName: "effector",
});
},
CallExpression(node) {
if (
method.isNot(["sample", "guard"], {
node,
importMap: importedFromEffector,
})
) {
return;
}

const properties = getSourceOrClockProperties(node.arguments[0]);

properties.forEach(({ key, value }) => {
const propType = key.name;
const elements = value.elements;

const usedUnits = new Set();

for (const node of elements) {
const memberPath = createMemberExpressionPath(node);

if (usedUnits.has(memberPath)) {
const messageId = getMessageIdByPropType(propType);

context.report({
node,
messageId,
data: {
memberPath,
},
suggest: [
{
messageId: "removeDuplicate",
data: { memberPath },
fix(fixer) {
return fixer.remove(node);
},
},
],
});

return;
}

usedUnits.add(memberPath);
}
});
},
};
},
};

function createMemberExpressionPath(node, chain = "") {
const compactStrings = (...args) => args.filter(Boolean).join(".");

if (node.type === "MemberExpression") {
const propertyName = node.property.name;

const updatedChain = compactStrings(propertyName, chain);

return createMemberExpressionPath(node.object, updatedChain);
}

chain = compactStrings(node.name, chain);

// remove last dot
return chain.slice(0, -1);
}

function getSourceOrClockProperties(node) {
if (node.type !== "ObjectExpression") return [];

const allowedProps = ["clock", "source"];

const isClockOrSourceArray = (prop) => {
return (
allowedProps.includes(prop.key.name) &&
prop.value.type === "ArrayExpression"
);
};

return node.properties.filter(isClockOrSourceArray);
}

function getMessageIdByPropType(propType) {
return propType === "clock" ? "duplicatesInClock" : "duplicatesInSource";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://eslint.effector.dev/rules/no-duplicate-clock-or-source-array-values.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const { RuleTester } = require("@typescript-eslint/rule-tester");
const { join } = require("path");

const { readExample } = require("../../utils/read-example");
const rule = require("./no-duplicate-clock-or-source-array-values");

const ruleTester = new RuleTester({
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
project: "./tsconfig.json",
tsconfigRootDir: join(__dirname, ".."),
},
});

const readExampleForTheRule = (name) => ({
code: readExample(__dirname, name),
filename: join(__dirname, "examples", name),
});

ruleTester.run(
"effector/no-duplicate-clock-or-source-array-values.ts.test",
rule,
{
valid: ["correct-sample.ts"].map(readExampleForTheRule),

invalid: [
...["incorrect-sample.ts"].map(readExampleForTheRule).map((result) => ({
...result,
errors: [
{
messageId: "duplicatesInSource",
type: "Identifier",
suggestions: [
{
messageId: "removeDuplicate",
},
],
},
{
messageId: "duplicatesInClock",
type: "MemberExpression",
suggestions: [
{
messageId: "removeDuplicate",
},
],
},
],
})),
...["incorrect-guard.ts"].map(readExampleForTheRule).map((result) => ({
...result,
errors: [
{
messageId: "duplicatesInClock",
type: "MemberExpression",
suggestions: [
{
messageId: "removeDuplicate",
},
],
},
],
})),
],
}
);

0 comments on commit 255fe4a

Please sign in to comment.