Skip to content

Commit 4105e57

Browse files
authored
Implement require-pickup-in-persist rule (#163)
1 parent 5399f72 commit 4105e57

16 files changed

+206
-0
lines changed

config/scope.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = {
22
rules: {
33
"effector/strict-effect-handlers": "error",
4+
"effector/require-pickup-in-persist": "error",
45
},
56
};
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# effector/require-pickup-in-persist
2+
3+
Requires every `persist` call of the [`effector-storage`](https://github.com/yumauri/effector-storage) library to include a `pickup` event when using [`Scope`s](https://effector.dev/api/effector/scope/). This ensures the correct initial value is loaded into the store for each `Scope`.
4+
5+
```ts
6+
import { persist } from "effector-storage/query";
7+
8+
const $store = createStore("example");
9+
10+
// 👎 no pickup, does not work with Scope
11+
persist({ store: $store });
12+
```
13+
14+
```ts
15+
const pickup = createEvent();
16+
17+
// 👍 pickup is specified
18+
persist({ store: $store, pickup });
19+
```

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = {
1818
"no-guard": require("./rules/no-guard/no-guard"),
1919
"mandatory-scope-binding": require("./rules/mandatory-scope-binding/mandatory-scope-binding"),
2020
"prefer-useUnit": require("./rules/prefer-useUnit/prefer-useUnit"),
21+
"require-pickup-in-persist": require("./rules/require-pickup-in-persist/require-pickup-in-persist"),
2122
"no-patronum-debug": require("./rules/no-patronum-debug/no-patronum-debug"),
2223
},
2324
configs: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { createStore, createEvent } from "effector";
2+
3+
import { persist } from "effector-storage/local";
4+
5+
const $store = createStore("example");
6+
const updated = createEvent();
7+
8+
const appStarted = createEvent();
9+
10+
persist({
11+
source: $store.updates,
12+
target: updated,
13+
14+
pickup: appStarted,
15+
16+
key: "store",
17+
keyPrefix: "local",
18+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createStore, createEvent } from "effector";
2+
3+
import { persist } from "effector-storage";
4+
import { local as localAdapter } from "effector-storage/local";
5+
6+
const $store = createStore("example");
7+
const pickup = createEvent();
8+
9+
persist({ store: $store, pickup, adapter: localAdapter });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createStore } from "effector";
2+
3+
import { persist } from "other-persist";
4+
import { persist as persistNested } from "other-persist/nested";
5+
6+
const $store = createStore("example");
7+
8+
persist({ store: $store });
9+
persistNested({ store: $store });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { createStore, createEvent } from "effector";
2+
3+
import { persist as persistQuery } from "effector-storage/query";
4+
5+
const $store = createStore("example");
6+
const pickup = createEvent();
7+
8+
persistQuery({ store: $store, pickup });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { createStore, createEvent } from "effector";
2+
3+
import { persist as persistAsync } from "@effector-storage/react-native-async-storage";
4+
5+
const $store = createStore("example");
6+
const pickup = createEvent();
7+
8+
persistAsync({ store: $store, pickup });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createStore } from "effector";
2+
3+
import { persist } from "effector-storage";
4+
5+
const randomCall = () => ({ store: createStore() });
6+
7+
persist();
8+
persist("invalid");
9+
persist(randomCall());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createStore, createEvent } from "effector";
2+
3+
import { persist } from "effector-storage/local";
4+
5+
const $store = createStore("example");
6+
const updated = createEvent();
7+
8+
persist({
9+
source: $store,
10+
target: updated,
11+
12+
key: "store",
13+
keyPrefix: "local",
14+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createStore } from "effector";
2+
3+
import { persist } from "effector-storage";
4+
5+
const $store = createStore("example");
6+
7+
persist({ store: $store, adapter: localAdapter });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createStore, createEvent } from "effector";
2+
3+
import { persist as persistAsync } from "@effector-storage/react-native-async-storage";
4+
5+
const $store = createStore("example");
6+
7+
persistAsync({ store: $store });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { combine } from "effector";
2+
3+
import { persist } from "effector-storage/local";
4+
5+
persist({
6+
store: combine({ pickup: true }),
7+
param: { pickup: "yes" },
8+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
2+
3+
module.exports = {
4+
meta: {
5+
type: "problem",
6+
docs: {
7+
category: "Quality",
8+
url: createLinkToRule("require-pickup-in-persist"),
9+
},
10+
messages: {
11+
pickupMissing:
12+
"This `persist` call does not specify a `pickup` event that is required for scoped usage of `effector-storage`.",
13+
},
14+
schema: [],
15+
},
16+
create(context) {
17+
const pickupImports = new Set();
18+
19+
/**
20+
* Finds `effector-storage` packages, scoped and unscoped, including
21+
* contents of these packages. See examples for a full list.
22+
*/
23+
const PACKAGE_NAME = /^@?effector-storage(\u002F[\w-]+)*$/;
24+
25+
const declarationSelector = `ImportDeclaration[source.value=${PACKAGE_NAME}]`;
26+
const persistImportSelector = `ImportSpecifier[imported.name="persist"]`;
27+
28+
const configSelector = `[arguments.length=1][arguments.0.type="ObjectExpression"]`;
29+
const callSelector = `[callee.type="Identifier"]`;
30+
31+
return {
32+
[`${declarationSelector} > ${persistImportSelector}`](node) {
33+
pickupImports.add(node.local.name);
34+
},
35+
[`CallExpression${configSelector}${callSelector}`](node) {
36+
if (!pickupImports.has(node.callee.name)) return;
37+
38+
const config = node.arguments[0];
39+
40+
if (config.properties.some((prop) => prop.key?.name === "pickup"))
41+
return;
42+
43+
context.report({ node, messageId: "pickupMissing" });
44+
},
45+
};
46+
},
47+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
https://eslint.effector.dev/rules/require-pickup-in-persist.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const { RuleTester } = require("eslint");
2+
const { join } = require("path");
3+
4+
const {
5+
readExample,
6+
getCorrectExamples,
7+
getIncorrectExamples,
8+
} = require("../../utils/read-example");
9+
10+
const rule = require("./require-pickup-in-persist");
11+
12+
const ruleTester = new RuleTester({
13+
parserOptions: {
14+
ecmaVersion: 2020,
15+
sourceType: "module",
16+
},
17+
});
18+
19+
const readExampleForTheRule = (name) => ({
20+
code: readExample(__dirname, name),
21+
filename: join(__dirname, "examples", name),
22+
});
23+
24+
ruleTester.run("effector/require-pickup-in-persist.js.test", rule, {
25+
valid: getCorrectExamples(__dirname).map(readExampleForTheRule),
26+
27+
invalid:
28+
// Errors
29+
getIncorrectExamples(__dirname)
30+
.map(readExampleForTheRule)
31+
.map((result) => ({
32+
...result,
33+
errors: [
34+
{
35+
messageId: "pickupMissing",
36+
type: "CallExpression",
37+
},
38+
],
39+
})),
40+
});

0 commit comments

Comments
 (0)