Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ To configure individual rules:
- [effector/enforce-effect-naming-convention](/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md)
- [effector/no-getState](/rules/no-getState/no-getState.md)
- [effector/no-unnecessary-duplication](/rules/no-unnecessary-duplication/no-unnecessary-duplication.md)
- [effector/no-useless-methods](/rules/no-useless-methods/no-useless-methods.md)
- [effector/prefer-sample-over-forward-with-mapping](/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md)
1 change: 1 addition & 0 deletions config/recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = {
"effector/enforce-store-naming-convention": "error",
"effector/enforce-effect-naming-convention": "error",
"effector/no-getState": "error",
"effector/no-useless-methods": "error",
"effector/no-unnecessary-duplication": "warn",
"effector/prefer-sample-over-forward-with-mapping": "warn",
},
Expand Down
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-useless-methods": require("./rules/no-useless-methods/no-useless-methods"),
},
configs: {
recommended: require("./config/recommended"),
Expand Down
17 changes: 17 additions & 0 deletions rules/no-useless-methods/examples/correct-nested.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createEvent, createStore, guard, sample } from "effector";

const emailValidationFired = createEvent();
const $isEmailFromGuestiaExist = createStore(false);
const $email = createStore("");
const $emailError = createStore(false);

sample({
clock: guard({
clock: emailValidationFired,
source: $isEmailFromGuestiaExist,
filter: (isExist) => !isExist,
}),
source: $email,
fn: Boolean,
target: $emailError,
});
33 changes: 33 additions & 0 deletions rules/no-useless-methods/examples/correct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { sample, guard, createEvent } from "effector";

const trigger = createEvent();
const target = createEvent();

// with target
sample({ clock: trigger, fn: Boolean, target });
sample({ source: trigger, fn: Boolean, target });

guard({ clock: trigger, filter: Boolean, target });
guard({ source: trigger, filter: Boolean, target });

// with simple assign
const result1 = sample({ clock: trigger, fn: Boolean });
const result2 = sample({ source: trigger, fn: Boolean });

const result3 = guard({ clock: trigger, filter: Boolean });
const result4 = guard({ source: trigger, filter: Boolean });

// with complex assign

const somplexResult = {
target: guard({ source: trigger, filter: Boolean }),
};

function createSomething() {
const otherTrigger = createEvent();

return guard({ source: otherTrigger, filter: Boolean });
}

const createBooleanGuard = (otherTrigger) =>
guard({ source: otherTrigger, filter: Boolean });
5 changes: 5 additions & 0 deletions rules/no-useless-methods/examples/incorrect-guard-clock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { guard, createEvent } from "effector";

const trigger = createEvent();

guard({ clock: trigger, filter: Boolean });
5 changes: 5 additions & 0 deletions rules/no-useless-methods/examples/incorrect-guard-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { guard, createEvent } from "effector";

const trigger = createEvent();

guard({ source: trigger, filter: Boolean });
5 changes: 5 additions & 0 deletions rules/no-useless-methods/examples/incorrect-sample-clock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sample, createEvent } from "effector";

const trigger = createEvent();

sample({ clock: trigger, fn: Boolean });
5 changes: 5 additions & 0 deletions rules/no-useless-methods/examples/incorrect-sample-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sample, createEvent } from "effector";

const trigger = createEvent();

sample({ source: trigger, fn: Boolean });
82 changes: 82 additions & 0 deletions rules/no-useless-methods/no-useless-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const {
extractImportedFromEffector,
} = require("../../utils/extract-imported-from-effector");
const { traverseParentByType } = require("../../utils/traverse-parent-by-type");

module.exports = {
meta: {
type: "problem",
docs: {
description: "Forbids useless calls of `sample` and `guard`",
category: "Quality",
recommended: true,
},
messages: {
uselessMethod:
"Method `{{ methodName }}` does nothing in this case. You should assign the result to variable or pass `target` to it.",
},
schema: [],
},
create(context) {
const importedFromEffector = new Map();

return {
ImportDeclaration(node) {
extractImportedFromEffector(importedFromEffector, node);
},
CallExpression(node) {
const POSSIBLE_USELESS_METHODS = ["sample", "guard"];
for (const method of POSSIBLE_USELESS_METHODS) {
const localMethod = importedFromEffector.get(method);
if (!localMethod) {
continue;
}

const isEffectorMethod = node?.callee?.name === localMethod;
if (!isEffectorMethod) {
continue;
}

const resultAssignedInVariable = traverseParentByType(
node,
"VariableDeclarator"
);
if (resultAssignedInVariable) {
continue;
}

const resultReturnedFromFactory = traverseParentByType(
node,
"ReturnStatement"
);
if (resultReturnedFromFactory) {
continue;
}

const resultPartOfChain = traverseParentByType(
node,
"ObjectExpression"
);
if (resultPartOfChain) {
continue;
}

const configHasTarget = node?.arguments?.[0]?.properties?.some(
(prop) => prop?.key.name === "target"
);
if (configHasTarget) {
continue;
}

context.report({
node,
messageId: "uselessMethod",
data: {
methodName: node?.callee?.name,
},
});
}
},
};
},
};
14 changes: 14 additions & 0 deletions rules/no-useless-methods/no-useless-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# effector/no-useless-methods

Call of `gaurd`/`sample` without `target` or variable assignment is useless. It can be omitted from source code.

```ts
// 👎 can be omitted
guard({ clock: trigger, filter: Boolean });

// 👍 makes sense
const target1 = guard({ clock: trigger, filter: Boolean });

// 👍 make sense too
guard({ clock: trigger, filter: Boolean, target: target2 });
```
52 changes: 52 additions & 0 deletions rules/no-useless-methods/no-useless-methods.ts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const { RuleTester } =
require("@typescript-eslint/experimental-utils").ESLintUtils;
const { join } = require("path");

const { readExample } = require("../../utils/read-example");
const rule = require("./no-useless-methods");

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-useless-methods.ts.test", rule, {
valid: ["correct.ts", "correct-nested.ts"].map(readExampleForTheRule),

invalid: [
...["incorrect-sample-clock.ts", "incorrect-sample-source.ts"]
.map(readExampleForTheRule)
.map((result) => ({
...result,
errors: [
{
messageId: "uselessMethod",
type: "CallExpression",
data: { methodName: "sample" },
},
],
})),
...["incorrect-guard-clock.ts", "incorrect-guard-source.ts"]
.map(readExampleForTheRule)
.map((result) => ({
...result,
errors: [
{
messageId: "uselessMethod",
type: "CallExpression",
data: { methodName: "guard" },
},
],
})),
],
});
13 changes: 13 additions & 0 deletions utils/traverse-parent-by-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function traverseParentByType(node, type) {
if (!node) {
return null;
}

if (node.type === type) {
return node;
}

return traverseParentByType(node.parent, type);
}

module.exports = { traverseParentByType };