diff --git a/README.md b/README.md
index dabe16baf..2927f6793 100644
--- a/README.md
+++ b/README.md
@@ -287,6 +287,7 @@ These rules relate to better ways of doing things to help you avoid problems:
| [svelte/no-unused-svelte-ignore](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/) | disallow unused svelte-ignore comments | :star: |
| [svelte/no-useless-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: |
| [svelte/require-optimized-style-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/) | require style attributes that can be optimized | |
+| [svelte/require-stores-init](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-stores-init/) | require initial value in store | |
## Stylistic Issues
diff --git a/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte b/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte
index 4f100e414..1ce361ae8 100644
--- a/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte
+++ b/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte
@@ -12,9 +12,10 @@
let code = ""
export let rules = {}
export let fix = false
+ export let language = "html"
let time = ""
- let options = {
- filename: "example.svelte",
+ $: options = {
+ filename: language === "html" ? "example.svelte" : "example.js",
preprocess,
postprocess,
}
@@ -43,7 +44,7 @@
{linter}
bind:code
config={{
- parser: "svelte-eslint-parser",
+ parser: language === "html" ? "svelte-eslint-parser" : undefined,
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
@@ -54,6 +55,7 @@
es2021: true,
},
}}
+ {language}
{options}
on:result={onLintedResult}
showDiff={showDiff && fix}
diff --git a/docs-svelte-kit/src/lib/eslint/ESLintEditor.svelte b/docs-svelte-kit/src/lib/eslint/ESLintEditor.svelte
index 02644ef2c..cb047c701 100644
--- a/docs-svelte-kit/src/lib/eslint/ESLintEditor.svelte
+++ b/docs-svelte-kit/src/lib/eslint/ESLintEditor.svelte
@@ -11,6 +11,7 @@
export let options = {}
export let fix = true
export let showDiff = true
+ export let language = "html"
let fixedValue = code
let leftMarkers = []
@@ -221,7 +222,7 @@
bind:this={editor}
bind:code
bind:rightCode={fixedValue}
- language="html"
+ {language}
diffEditor={fix && showDiff}
markers={leftMarkers}
{rightMarkers}
diff --git a/docs/rules.md b/docs/rules.md
index 01451b026..040d07895 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -47,6 +47,7 @@ These rules relate to better ways of doing things to help you avoid problems:
| [svelte/no-unused-svelte-ignore](./rules/no-unused-svelte-ignore.md) | disallow unused svelte-ignore comments | :star: |
| [svelte/no-useless-mustaches](./rules/no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: |
| [svelte/require-optimized-style-attribute](./rules/require-optimized-style-attribute.md) | require style attributes that can be optimized | |
+| [svelte/require-stores-init](./rules/require-stores-init.md) | require initial value in store | |
## Stylistic Issues
diff --git a/docs/rules/require-stores-init.md b/docs/rules/require-stores-init.md
new file mode 100644
index 000000000..074c79af5
--- /dev/null
+++ b/docs/rules/require-stores-init.md
@@ -0,0 +1,54 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "svelte/require-stores-init"
+description: "require initial value in store"
+---
+
+# svelte/require-stores-init
+
+> require initial value in store
+
+- :exclamation: **_This rule has not been released yet._**
+
+## :book: Rule Details
+
+This rule is aimed to enforce initial values when initializing the Svelte stores.
+
+
+
+
+
+```js
+/* eslint svelte/require-stores-init: "error" */
+
+import { writable, readable, derived } from "svelte/store"
+
+/* ✓ GOOD */
+export const w1 = writable(false)
+export const r1 = readable({})
+export const d1 = derived([a, b], () => {}, false)
+
+/* ✗ BAD */
+export const w2 = writable()
+export const r2 = readable()
+export const d2 = derived([a, b], () => {})
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :heart: Compatibility
+
+This rule was taken from [@tivac/eslint-plugin-svelte].
+This rule is compatible with `@tivac/svelte/stores-initial-value` rule.
+
+[@tivac/eslint-plugin-svelte]: https://github.com/tivac/eslint-plugin-svelte/
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/require-stores-init.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/require-stores-init.ts)
diff --git a/package.json b/package.json
index 43387bf67..da52fa187 100644
--- a/package.json
+++ b/package.json
@@ -83,6 +83,7 @@
"@types/escape-html": "^1.0.2",
"@types/eslint": "^8.0.0",
"@types/eslint-scope": "^3.7.0",
+ "@types/eslint-utils": "^3.0.1",
"@types/eslint-visitor-keys": "^1.0.0",
"@types/estree": "^1.0.0",
"@types/less": "^3.0.3",
diff --git a/src/rules/require-stores-init.ts b/src/rules/require-stores-init.ts
new file mode 100644
index 000000000..5b2ac846c
--- /dev/null
+++ b/src/rules/require-stores-init.ts
@@ -0,0 +1,67 @@
+import { createRule } from "../utils"
+import type * as ESTree from "estree"
+import { ReferenceTracker } from "eslint-utils"
+
+export default createRule("require-stores-init", {
+ meta: {
+ docs: {
+ description: "require initial value in store",
+ category: "Best Practices",
+ recommended: false,
+ },
+ schema: [],
+ messages: {
+ storeDefaultValue: `Always set a default value for svelte stores.`,
+ },
+ 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()) {
+ const minArgs =
+ name === "writable" || name === "readable"
+ ? 1
+ : name === "derived"
+ ? 3
+ : 0
+
+ if (
+ node.arguments.length >= minArgs ||
+ node.arguments.some((arg) => arg.type === "SpreadElement")
+ ) {
+ continue
+ }
+ context.report({
+ node,
+ messageId: "storeDefaultValue",
+ })
+ }
+ },
+ }
+ },
+})
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index f4e629813..e5776af24 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -28,6 +28,7 @@ import noUselessMustaches from "../rules/no-useless-mustaches"
import preferClassDirective from "../rules/prefer-class-directive"
import preferStyleDirective from "../rules/prefer-style-directive"
import requireOptimizedStyleAttribute from "../rules/require-optimized-style-attribute"
+import requireStoresInit from "../rules/require-stores-init"
import shorthandAttribute from "../rules/shorthand-attribute"
import shorthandDirective from "../rules/shorthand-directive"
import sortAttributes from "../rules/sort-attributes"
@@ -65,6 +66,7 @@ export const rules = [
preferClassDirective,
preferStyleDirective,
requireOptimizedStyleAttribute,
+ requireStoresInit,
shorthandAttribute,
shorthandDirective,
sortAttributes,
diff --git a/tests/fixtures/rules/.eslintrc.js b/tests/fixtures/rules/.eslintrc.js
index c810e19f6..d4fc563ce 100644
--- a/tests/fixtures/rules/.eslintrc.js
+++ b/tests/fixtures/rules/.eslintrc.js
@@ -1,6 +1,7 @@
-"use strict"
-
module.exports = {
+ parserOptions: {
+ sourceType: "module",
+ },
overrides: [
{
files: ["*output.svelte"],
@@ -17,5 +18,6 @@ module.exports = {
"no-empty-function": "off",
"one-var": "off",
"func-style": "off",
+ "node/no-unsupported-features/es-syntax": "off",
},
}
diff --git a/tests/fixtures/rules/indent/invalid/.eslintrc.js b/tests/fixtures/rules/indent/invalid/.eslintrc.js
index 496509f71..788e1c38d 100644
--- a/tests/fixtures/rules/indent/invalid/.eslintrc.js
+++ b/tests/fixtures/rules/indent/invalid/.eslintrc.js
@@ -1,5 +1,3 @@
-"use strict"
-
module.exports = {
rules: {
"no-sparse-arrays": "off",
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/valid/.eslintrc.js b/tests/fixtures/rules/no-unused-svelte-ignore/valid/.eslintrc.js
index ac738ac81..bb79284b9 100644
--- a/tests/fixtures/rules/no-unused-svelte-ignore/valid/.eslintrc.js
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/valid/.eslintrc.js
@@ -1,5 +1,3 @@
-"use strict"
-
module.exports = {
overrides: [
{
diff --git a/tests/fixtures/rules/require-stores-init/invalid/no-init-in-js01-errors.yaml b/tests/fixtures/rules/require-stores-init/invalid/no-init-in-js01-errors.yaml
new file mode 100644
index 000000000..c0a036d13
--- /dev/null
+++ b/tests/fixtures/rules/require-stores-init/invalid/no-init-in-js01-errors.yaml
@@ -0,0 +1,12 @@
+- message: Always set a default value for svelte stores.
+ line: 2
+ column: 18
+ suggestions: null
+- message: Always set a default value for svelte stores.
+ line: 3
+ column: 18
+ suggestions: null
+- message: Always set a default value for svelte stores.
+ line: 4
+ column: 18
+ suggestions: null
diff --git a/tests/fixtures/rules/require-stores-init/invalid/no-init-in-js01-input.js b/tests/fixtures/rules/require-stores-init/invalid/no-init-in-js01-input.js
new file mode 100644
index 000000000..797262939
--- /dev/null
+++ b/tests/fixtures/rules/require-stores-init/invalid/no-init-in-js01-input.js
@@ -0,0 +1,4 @@
+import { writable, readable, derived } from "svelte/store"
+export const w = writable()
+export const r = readable()
+export const d = derived([a, b], () => {})
diff --git a/tests/fixtures/rules/require-stores-init/invalid/no-init01-errors.yaml b/tests/fixtures/rules/require-stores-init/invalid/no-init01-errors.yaml
new file mode 100644
index 000000000..e6f7ae5bc
--- /dev/null
+++ b/tests/fixtures/rules/require-stores-init/invalid/no-init01-errors.yaml
@@ -0,0 +1,12 @@
+- message: Always set a default value for svelte stores.
+ line: 3
+ column: 13
+ suggestions: null
+- message: Always set a default value for svelte stores.
+ line: 4
+ column: 13
+ suggestions: null
+- message: Always set a default value for svelte stores.
+ line: 5
+ column: 13
+ suggestions: null
diff --git a/tests/fixtures/rules/require-stores-init/invalid/no-init01-input.svelte b/tests/fixtures/rules/require-stores-init/invalid/no-init01-input.svelte
new file mode 100644
index 000000000..3a6a2be45
--- /dev/null
+++ b/tests/fixtures/rules/require-stores-init/invalid/no-init01-input.svelte
@@ -0,0 +1,6 @@
+
diff --git a/tests/fixtures/rules/require-stores-init/valid/has-init-in-js01-input.js b/tests/fixtures/rules/require-stores-init/valid/has-init-in-js01-input.js
new file mode 100644
index 000000000..11a5a93c9
--- /dev/null
+++ b/tests/fixtures/rules/require-stores-init/valid/has-init-in-js01-input.js
@@ -0,0 +1,4 @@
+import { writable, readable, derived } from "svelte/store"
+export const w = writable(false)
+export const r = readable({})
+export const d = derived([a, b], () => {}, false)
diff --git a/tests/fixtures/rules/require-stores-init/valid/has-init01-input.svelte b/tests/fixtures/rules/require-stores-init/valid/has-init01-input.svelte
new file mode 100644
index 000000000..8b0770958
--- /dev/null
+++ b/tests/fixtures/rules/require-stores-init/valid/has-init01-input.svelte
@@ -0,0 +1,6 @@
+
diff --git a/tests/fixtures/rules/require-stores-init/valid/no-svelte-store01-input.svelte b/tests/fixtures/rules/require-stores-init/valid/no-svelte-store01-input.svelte
new file mode 100644
index 000000000..79db1fde0
--- /dev/null
+++ b/tests/fixtures/rules/require-stores-init/valid/no-svelte-store01-input.svelte
@@ -0,0 +1,6 @@
+
diff --git a/tests/fixtures/rules/require-stores-init/valid/spread01-input.svelte b/tests/fixtures/rules/require-stores-init/valid/spread01-input.svelte
new file mode 100644
index 000000000..f51b331c6
--- /dev/null
+++ b/tests/fixtures/rules/require-stores-init/valid/spread01-input.svelte
@@ -0,0 +1,5 @@
+
diff --git a/tests/src/rules/require-stores-init.ts b/tests/src/rules/require-stores-init.ts
new file mode 100644
index 000000000..6dcbde52f
--- /dev/null
+++ b/tests/src/rules/require-stores-init.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/require-stores-init"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: "module",
+ },
+})
+
+tester.run(
+ "require-stores-init",
+ rule as any,
+ loadTestCases("require-stores-init"),
+)
diff --git a/tests/utils/utils.ts b/tests/utils/utils.ts
index 848d80580..ca5807a88 100644
--- a/tests/utils/utils.ts
+++ b/tests/utils/utils.ts
@@ -184,13 +184,17 @@ function writeFixtures(
const config = getConfig(ruleName, inputFile)
+ const parser =
+ path.extname(inputFile) === ".svelte"
+ ? require.resolve("svelte-eslint-parser")
+ : undefined
const result = linter.verify(
config.code,
{
rules: {
[ruleName]: ["error", ...(config.options || [])],
},
- parser: "svelte-eslint-parser",
+ parser,
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
diff --git a/typings/eslint-utils/index.d.ts b/typings/eslint-utils/index.d.ts
index 5a4112cdc..fc14c75cc 100644
--- a/typings/eslint-utils/index.d.ts
+++ b/typings/eslint-utils/index.d.ts
@@ -1,6 +1,10 @@
import type { AST } from "svelte-eslint-parser"
import type { Scope } from "eslint"
import type * as ESTree from "estree"
+export {
+ ReferenceTracker,
+ TrackedReferences,
+} from "../../node_modules/@types/eslint-utils"
type Token = { type: string; value: string }
export function isArrowToken(token: Token): boolean
export function isCommaToken(token: Token): boolean