From 104f8b25699da974319e7d05a8b0130efc19cc5c Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 14 Aug 2022 22:42:54 +0900 Subject: [PATCH 1/9] feat: implement stores-no-async rule --- src/rules/no-store-async.ts | 44 +++++++++++++++++++++++++++++++++++++ src/utils/rules.ts | 2 ++ 2 files changed, 46 insertions(+) create mode 100644 src/rules/no-store-async.ts diff --git a/src/rules/no-store-async.ts b/src/rules/no-store-async.ts new file mode 100644 index 000000000..1073ac0bb --- /dev/null +++ b/src/rules/no-store-async.ts @@ -0,0 +1,44 @@ +import { createRule } from "../utils" +import type * as ESTree from "estree" + +export default createRule("no-store-async", { + meta: { + docs: { + description: + "disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features", + category: "Possible Errors", + recommended: true, + default: "error", + }, + schema: [], + messages: { + unexpected: "Do not pass async functions to svelte stores.", + }, + type: "problem", + }, + create(context) { + return { + CallExpression(node: ESTree.CallExpression) { + if (node.callee.type !== "Identifier") return + const { name } = node.callee + if (name !== "writable" && name !== "readable" && name !== "derived") + return + const [, fn] = node.arguments + if (fn.type !== "ArrowFunctionExpression" || !fn.async) return + + const start = fn.loc?.start ?? { line: 1, column: 0 } + context.report({ + node: fn, + loc: { + start, + end: { + line: start.line, + column: start.column + 5, + }, + }, + messageId: "unexpected", + }) + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index e5776af24..06382d328 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -21,6 +21,7 @@ import noReactiveFunctions from "../rules/no-reactive-functions" import noReactiveLiterals from "../rules/no-reactive-literals" import noShorthandStylePropertyOverrides from "../rules/no-shorthand-style-property-overrides" import noSpacesAroundEqualSignsInAttribute from "../rules/no-spaces-around-equal-signs-in-attribute" +import noStoreAsync from "../rules/no-store-async" import noTargetBlank from "../rules/no-target-blank" import noUnknownStyleDirectiveProperty from "../rules/no-unknown-style-directive-property" import noUnusedSvelteIgnore from "../rules/no-unused-svelte-ignore" @@ -59,6 +60,7 @@ export const rules = [ noReactiveLiterals, noShorthandStylePropertyOverrides, noSpacesAroundEqualSignsInAttribute, + noStoreAsync, noTargetBlank, noUnknownStyleDirectiveProperty, noUnusedSvelteIgnore, From 7dcaa23c77490ee03a9793906e92b72e2c4cf488 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 14 Aug 2022 22:43:41 +0900 Subject: [PATCH 2/9] test: add tests --- .../rules/no-store-async/invalid/test01-errors.yaml | 12 ++++++++++++ .../rules/no-store-async/invalid/test01-input.js | 11 +++++++++++ .../rules/no-store-async/valid/test01-input.js | 11 +++++++++++ tests/src/rules/no-store-async.ts | 12 ++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 tests/fixtures/rules/no-store-async/invalid/test01-errors.yaml create mode 100644 tests/fixtures/rules/no-store-async/invalid/test01-input.js create mode 100644 tests/fixtures/rules/no-store-async/valid/test01-input.js create mode 100644 tests/src/rules/no-store-async.ts diff --git a/tests/fixtures/rules/no-store-async/invalid/test01-errors.yaml b/tests/fixtures/rules/no-store-async/invalid/test01-errors.yaml new file mode 100644 index 000000000..932632015 --- /dev/null +++ b/tests/fixtures/rules/no-store-async/invalid/test01-errors.yaml @@ -0,0 +1,12 @@ +- message: Do not pass async functions to svelte stores. + line: 3 + column: 28 + suggestions: null +- message: Do not pass async functions to svelte stores. + line: 6 + column: 28 + suggestions: null +- message: Do not pass async functions to svelte stores. + line: 9 + column: 24 + suggestions: null diff --git a/tests/fixtures/rules/no-store-async/invalid/test01-input.js b/tests/fixtures/rules/no-store-async/invalid/test01-input.js new file mode 100644 index 000000000..3ad801903 --- /dev/null +++ b/tests/fixtures/rules/no-store-async/invalid/test01-input.js @@ -0,0 +1,11 @@ +import { writable, readable, derived } from "svelte/store" + +const w2 = writable(false, async () => { + /** do nothing */ +}) +const r2 = readable(false, async () => { + /** do nothing */ +}) +const d2 = derived(a1, async ($a1) => { + /** do nothing */ +}) diff --git a/tests/fixtures/rules/no-store-async/valid/test01-input.js b/tests/fixtures/rules/no-store-async/valid/test01-input.js new file mode 100644 index 000000000..5ff7dc4e7 --- /dev/null +++ b/tests/fixtures/rules/no-store-async/valid/test01-input.js @@ -0,0 +1,11 @@ +import { writable, readable, derived } from "svelte/store" + +const w1 = writable(false, () => { + /** do nothing */ +}) +const r1 = readable(false, () => { + /** do nothing */ +}) +const d1 = derived(a1, ($a1) => { + /** do nothing */ +}) diff --git a/tests/src/rules/no-store-async.ts b/tests/src/rules/no-store-async.ts new file mode 100644 index 000000000..90d4bcbca --- /dev/null +++ b/tests/src/rules/no-store-async.ts @@ -0,0 +1,12 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/no-store-async" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run("no-store-async", rule as any, loadTestCases("no-store-async")) From 796551abdaf94926b6f6eea128e5780bbffe712e Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 14 Aug 2022 22:45:48 +0900 Subject: [PATCH 3/9] docs: add docs --- README.md | 1 + docs/rules.md | 1 + docs/rules/no-store-async.md | 57 ++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 docs/rules/no-store-async.md diff --git a/README.md b/README.md index 596f71ac1..b73f81a0a 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,7 @@ These rules relate to possible syntax or logic errors in Svelte code: | [svelte/no-not-function-handler](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-not-function-handler/) | disallow use of not function in event handler | :star: | | [svelte/no-object-in-text-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-object-in-text-mustaches/) | disallow objects in text mustache interpolation | :star: | | [svelte/no-shorthand-style-property-overrides](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-shorthand-style-property-overrides/) | disallow shorthand style properties that override related longhand properties | :star: | +| [svelte/no-store-async](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-store-async.md) | disallow using async/await inside svelte stores | :star: | | [svelte/no-unknown-style-directive-property](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unknown-style-directive-property/) | disallow unknown `style:property` | :star: | | [svelte/valid-compile](https://ota-meshi.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | :star: | diff --git a/docs/rules.md b/docs/rules.md index 223d78dca..cec9eb373 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -22,6 +22,7 @@ These rules relate to possible syntax or logic errors in Svelte code: | [svelte/no-not-function-handler](./rules/no-not-function-handler.md) | disallow use of not function in event handler | :star: | | [svelte/no-object-in-text-mustaches](./rules/no-object-in-text-mustaches.md) | disallow objects in text mustache interpolation | :star: | | [svelte/no-shorthand-style-property-overrides](./rules/no-shorthand-style-property-overrides.md) | disallow shorthand style properties that override related longhand properties | :star: | +| [svelte/no-store-async](./rules/no-store-async.md) | disallow using async/await inside svelte stores | :star: | | [svelte/no-unknown-style-directive-property](./rules/no-unknown-style-directive-property.md) | disallow unknown `style:property` | :star: | | [svelte/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | :star: | diff --git a/docs/rules/no-store-async.md b/docs/rules/no-store-async.md new file mode 100644 index 000000000..a8d22c4cc --- /dev/null +++ b/docs/rules/no-store-async.md @@ -0,0 +1,57 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/no-store-async" +description: "disallow using async/await inside svelte stores" +since: "v3.1.0" +--- + +# svelte/no-store-async + +> disallow using async/await inside svelte stores + +- :gear: This rule is included in `"plugin:svelte/recommended"`. + +## :book: Rule Details + +This rule reports all uses of async/await inside svelte stores. +Because it causes issues with the auto-unsubscribing features. + + + + + +```js +/* eslint svelte/no-store-async: "error" */ + +import { writable, readable, derived } from "svelte/store" + +/* ✓ GOOD */ +const w1 = writable(false, () => {}) +const r1 = readable(false, () => {}) +const d1 = derived(a1, ($a1) => {}) + +/* ✗ BAD */ +const w2 = writable(false, async () => {}) +const r2 = readable(false, async () => {}) +const d2 = derived(a1, async ($a1) => {}) +``` + + + +## :wrench: Options + +Nothing. + +## :books: Further Reading + +- [Svelte - Docs > 4. Prefix stores with $ to access their values / Store contract](https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values-store-contract) + +## :rocket: Version + +This rule was introduced in eslint-plugin-svelte v3.1.0 + +## :mag: Implementation + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-store-async.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/no-store-async.ts) From 9904b9fb3d2b4a79a6c4bd111975e2046b865ca8 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 14 Aug 2022 23:00:24 +0900 Subject: [PATCH 4/9] chore: add changeset --- .changeset/curvy-ants-admire.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/curvy-ants-admire.md diff --git a/.changeset/curvy-ants-admire.md b/.changeset/curvy-ants-admire.md new file mode 100644 index 000000000..8ff6e37eb --- /dev/null +++ b/.changeset/curvy-ants-admire.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": minor +--- + +Add svelte/stores-no-async rule by @baseballyama in #225 From c05ef7cc01b234f3627bd4f2209214169884d474 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 20 Aug 2022 18:05:59 +0900 Subject: [PATCH 5/9] fix: remove since --- docs/rules/no-store-async.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/rules/no-store-async.md b/docs/rules/no-store-async.md index a8d22c4cc..29dd29c64 100644 --- a/docs/rules/no-store-async.md +++ b/docs/rules/no-store-async.md @@ -3,7 +3,6 @@ pageClass: "rule-details" sidebarDepth: 0 title: "svelte/no-store-async" description: "disallow using async/await inside svelte stores" -since: "v3.1.0" --- # svelte/no-store-async From 715d04a1b1cc2cf013ae8462afa4f1f2a392c92b Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 20 Aug 2022 18:07:11 +0900 Subject: [PATCH 6/9] fix: changeset --- .changeset/curvy-ants-admire.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/curvy-ants-admire.md b/.changeset/curvy-ants-admire.md index 8ff6e37eb..db5aec1ec 100644 --- a/.changeset/curvy-ants-admire.md +++ b/.changeset/curvy-ants-admire.md @@ -2,4 +2,4 @@ "eslint-plugin-svelte": minor --- -Add svelte/stores-no-async rule by @baseballyama in #225 +Add svelte/stores-no-async rule From 37d1de783bd7a6897a5f1b7377897576129ad485 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 20 Aug 2022 18:50:54 +0900 Subject: [PATCH 7/9] fix: use ReferenceTracker --- src/rules/no-store-async.ts | 36 +++++++++---------- src/rules/reference-helpers/svelte-store.ts | 29 +++++++++++++++ src/rules/require-stores-init.ts | 29 ++------------- .../no-store-async/invalid/test02-errors.yaml | 12 +++++++ .../no-store-async/invalid/test02-input.js | 11 ++++++ .../no-store-async/invalid/test03-errors.yaml | 12 +++++++ .../no-store-async/invalid/test03-input.js | 11 ++++++ 7 files changed, 94 insertions(+), 46 deletions(-) create mode 100644 src/rules/reference-helpers/svelte-store.ts create mode 100644 tests/fixtures/rules/no-store-async/invalid/test02-errors.yaml create mode 100644 tests/fixtures/rules/no-store-async/invalid/test02-input.js create mode 100644 tests/fixtures/rules/no-store-async/invalid/test03-errors.yaml create mode 100644 tests/fixtures/rules/no-store-async/invalid/test03-input.js diff --git a/src/rules/no-store-async.ts b/src/rules/no-store-async.ts index 1073ac0bb..24a3c8c6c 100644 --- a/src/rules/no-store-async.ts +++ b/src/rules/no-store-async.ts @@ -1,5 +1,5 @@ import { createRule } from "../utils" -import type * as ESTree from "estree" +import { extractStoreReferences } from "./reference-helpers/svelte-store" export default createRule("no-store-async", { meta: { @@ -18,26 +18,24 @@ export default createRule("no-store-async", { }, create(context) { return { - CallExpression(node: ESTree.CallExpression) { - if (node.callee.type !== "Identifier") return - const { name } = node.callee - if (name !== "writable" && name !== "readable" && name !== "derived") - return - const [, fn] = node.arguments - if (fn.type !== "ArrowFunctionExpression" || !fn.async) return + Program() { + for (const { node } of extractStoreReferences(context)) { + const [, fn] = node.arguments + if (fn.type !== "ArrowFunctionExpression" || !fn.async) continue - const start = fn.loc?.start ?? { line: 1, column: 0 } - context.report({ - node: fn, - loc: { - start, - end: { - line: start.line, - column: start.column + 5, + const start = fn.loc?.start ?? { line: 1, column: 0 } + context.report({ + node: fn, + loc: { + start, + end: { + line: start.line, + column: start.column + 5, + }, }, - }, - messageId: "unexpected", - }) + messageId: "unexpected", + }) + } }, } }, diff --git a/src/rules/reference-helpers/svelte-store.ts b/src/rules/reference-helpers/svelte-store.ts new file mode 100644 index 000000000..3e9edc975 --- /dev/null +++ b/src/rules/reference-helpers/svelte-store.ts @@ -0,0 +1,29 @@ +import type * as ESTree from "estree" +import { ReferenceTracker } from "eslint-utils" +import type { RuleContext } from "../../types" + +/** Extract 'svelte/store' references */ +export function* extractStoreReferences( + context: RuleContext, +): Generator<{ node: ESTree.CallExpression; name: string }, void> { + const referenceTracker = new ReferenceTracker(context.getScope()) + for (const { node, path } of referenceTracker.iterateEsmReferences({ + "svelte/store": { + [ReferenceTracker.ESM]: true, + writable: { + [ReferenceTracker.CALL]: true, + }, + readable: { + [ReferenceTracker.CALL]: true, + }, + derived: { + [ReferenceTracker.CALL]: true, + }, + }, + })) { + yield { + node: node as ESTree.CallExpression, + name: path[path.length - 1], + } + } +} diff --git a/src/rules/require-stores-init.ts b/src/rules/require-stores-init.ts index 5b2ac846c..8245ef5ca 100644 --- a/src/rules/require-stores-init.ts +++ b/src/rules/require-stores-init.ts @@ -1,6 +1,5 @@ import { createRule } from "../utils" -import type * as ESTree from "estree" -import { ReferenceTracker } from "eslint-utils" +import { extractStoreReferences } from "./reference-helpers/svelte-store" export default createRule("require-stores-init", { meta: { @@ -16,33 +15,9 @@ export default createRule("require-stores-init", { type: "suggestion", }, create(context) { - /** Extract 'svelte/store' references */ - function* extractStoreReferences() { - const referenceTracker = new ReferenceTracker(context.getScope()) - for (const { node, path } of referenceTracker.iterateEsmReferences({ - "svelte/store": { - [ReferenceTracker.ESM]: true, - writable: { - [ReferenceTracker.CALL]: true, - }, - readable: { - [ReferenceTracker.CALL]: true, - }, - derived: { - [ReferenceTracker.CALL]: true, - }, - }, - })) { - yield { - node: node as ESTree.CallExpression, - name: path[path.length - 1], - } - } - } - return { Program() { - for (const { node, name } of extractStoreReferences()) { + for (const { node, name } of extractStoreReferences(context)) { const minArgs = name === "writable" || name === "readable" ? 1 diff --git a/tests/fixtures/rules/no-store-async/invalid/test02-errors.yaml b/tests/fixtures/rules/no-store-async/invalid/test02-errors.yaml new file mode 100644 index 000000000..b65aa407e --- /dev/null +++ b/tests/fixtures/rules/no-store-async/invalid/test02-errors.yaml @@ -0,0 +1,12 @@ +- message: Do not pass async functions to svelte stores. + line: 3 + column: 35 + suggestions: null +- message: Do not pass async functions to svelte stores. + line: 6 + column: 35 + suggestions: null +- message: Do not pass async functions to svelte stores. + line: 9 + column: 31 + suggestions: null diff --git a/tests/fixtures/rules/no-store-async/invalid/test02-input.js b/tests/fixtures/rules/no-store-async/invalid/test02-input.js new file mode 100644 index 000000000..a327fa985 --- /dev/null +++ b/tests/fixtures/rules/no-store-async/invalid/test02-input.js @@ -0,0 +1,11 @@ +import * as stores from "svelte/store" + +const w2 = stores.writable(false, async () => { + /** do nothing */ +}) +const r2 = stores.readable(false, async () => { + /** do nothing */ +}) +const d2 = stores.derived(a1, async ($a1) => { + /** do nothing */ +}) diff --git a/tests/fixtures/rules/no-store-async/invalid/test03-errors.yaml b/tests/fixtures/rules/no-store-async/invalid/test03-errors.yaml new file mode 100644 index 000000000..3c3ed27e9 --- /dev/null +++ b/tests/fixtures/rules/no-store-async/invalid/test03-errors.yaml @@ -0,0 +1,12 @@ +- message: Do not pass async functions to svelte stores. + line: 3 + column: 21 + suggestions: null +- message: Do not pass async functions to svelte stores. + line: 6 + column: 21 + suggestions: null +- message: Do not pass async functions to svelte stores. + line: 9 + column: 18 + suggestions: null diff --git a/tests/fixtures/rules/no-store-async/invalid/test03-input.js b/tests/fixtures/rules/no-store-async/invalid/test03-input.js new file mode 100644 index 000000000..394382d9c --- /dev/null +++ b/tests/fixtures/rules/no-store-async/invalid/test03-input.js @@ -0,0 +1,11 @@ +import { writable as A, readable as B, derived as C } from "svelte/store" + +const w2 = A(false, async () => { + /** do nothing */ +}) +const r2 = B(false, async () => { + /** do nothing */ +}) +const d2 = C(a1, async ($a1) => { + /** do nothing */ +}) From 99e0996c1866e285e6d3d4d92172f037415e3500 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 20 Aug 2022 18:55:53 +0900 Subject: [PATCH 8/9] fix: handled if fn is undefined --- src/rules/no-store-async.ts | 4 +++- tests/fixtures/rules/no-store-async/valid/test01-input.js | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rules/no-store-async.ts b/src/rules/no-store-async.ts index 24a3c8c6c..8d66e610a 100644 --- a/src/rules/no-store-async.ts +++ b/src/rules/no-store-async.ts @@ -21,7 +21,9 @@ export default createRule("no-store-async", { Program() { for (const { node } of extractStoreReferences(context)) { const [, fn] = node.arguments - if (fn.type !== "ArrowFunctionExpression" || !fn.async) continue + if (!fn || fn.type !== "ArrowFunctionExpression" || !fn.async) { + continue + } const start = fn.loc?.start ?? { line: 1, column: 0 } context.report({ diff --git a/tests/fixtures/rules/no-store-async/valid/test01-input.js b/tests/fixtures/rules/no-store-async/valid/test01-input.js index 5ff7dc4e7..f6e2c1aa6 100644 --- a/tests/fixtures/rules/no-store-async/valid/test01-input.js +++ b/tests/fixtures/rules/no-store-async/valid/test01-input.js @@ -3,9 +3,12 @@ import { writable, readable, derived } from "svelte/store" const w1 = writable(false, () => { /** do nothing */ }) +const w2 = writable(false) const r1 = readable(false, () => { /** do nothing */ }) +const r2 = readable(false) const d1 = derived(a1, ($a1) => { /** do nothing */ }) +const d2 = derived(a1) From 0b8b4ce6273f987ba0746df5c852cdf0a9b6fd5b Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 20 Aug 2022 19:10:45 +0900 Subject: [PATCH 9/9] fix: handle FunctionExpression also --- src/rules/no-store-async.ts | 7 ++++++- .../rules/no-store-async/invalid/test04-errors.yaml | 12 ++++++++++++ .../rules/no-store-async/invalid/test04-input.js | 11 +++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/rules/no-store-async/invalid/test04-errors.yaml create mode 100644 tests/fixtures/rules/no-store-async/invalid/test04-input.js diff --git a/src/rules/no-store-async.ts b/src/rules/no-store-async.ts index 8d66e610a..d805f1854 100644 --- a/src/rules/no-store-async.ts +++ b/src/rules/no-store-async.ts @@ -21,7 +21,12 @@ export default createRule("no-store-async", { Program() { for (const { node } of extractStoreReferences(context)) { const [, fn] = node.arguments - if (!fn || fn.type !== "ArrowFunctionExpression" || !fn.async) { + if ( + !fn || + (fn.type !== "ArrowFunctionExpression" && + fn.type !== "FunctionExpression") || + !fn.async + ) { continue } diff --git a/tests/fixtures/rules/no-store-async/invalid/test04-errors.yaml b/tests/fixtures/rules/no-store-async/invalid/test04-errors.yaml new file mode 100644 index 000000000..932632015 --- /dev/null +++ b/tests/fixtures/rules/no-store-async/invalid/test04-errors.yaml @@ -0,0 +1,12 @@ +- message: Do not pass async functions to svelte stores. + line: 3 + column: 28 + suggestions: null +- message: Do not pass async functions to svelte stores. + line: 6 + column: 28 + suggestions: null +- message: Do not pass async functions to svelte stores. + line: 9 + column: 24 + suggestions: null diff --git a/tests/fixtures/rules/no-store-async/invalid/test04-input.js b/tests/fixtures/rules/no-store-async/invalid/test04-input.js new file mode 100644 index 000000000..f6ca81718 --- /dev/null +++ b/tests/fixtures/rules/no-store-async/invalid/test04-input.js @@ -0,0 +1,11 @@ +import { writable, readable, derived } from "svelte/store" + +const w2 = writable(false, async function () { + /** do nothing */ +}) +const r2 = readable(false, async function () { + /** do nothing */ +}) +const d2 = derived(a1, async function ($a1) { + /** do nothing */ +})