Skip to content

Add svelte/require-stores-init rule #211

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 7, 2022
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 @@ -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

Expand Down
8 changes: 5 additions & 3 deletions docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -43,7 +44,7 @@
{linter}
bind:code
config={{
parser: "svelte-eslint-parser",
parser: language === "html" ? "svelte-eslint-parser" : undefined,
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
Expand All @@ -54,6 +55,7 @@
es2021: true,
},
}}
{language}
{options}
on:result={onLintedResult}
showDiff={showDiff && fix}
Expand Down
3 changes: 2 additions & 1 deletion docs-svelte-kit/src/lib/eslint/ESLintEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
export let options = {}
export let fix = true
export let showDiff = true
export let language = "html"

let fixedValue = code
let leftMarkers = []
Expand Down Expand Up @@ -221,7 +222,7 @@
bind:this={editor}
bind:code
bind:rightCode={fixedValue}
language="html"
{language}
diffEditor={fix && showDiff}
markers={leftMarkers}
{rightMarkers}
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
54 changes: 54 additions & 0 deletions docs/rules/require-stores-init.md
Original file line number Diff line number Diff line change
@@ -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: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>

## :book: Rule Details

This rule is aimed to enforce initial values when initializing the Svelte stores.

<ESLintCodeBlock language="javascript">

<!--eslint-skip-->

```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], () => {})
```

</ESLintCodeBlock>

## :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)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
67 changes: 67 additions & 0 deletions src/rules/require-stores-init.ts
Original file line number Diff line number Diff line change
@@ -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",
})
}
},
}
},
})
2 changes: 2 additions & 0 deletions src/utils/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -65,6 +66,7 @@ export const rules = [
preferClassDirective,
preferStyleDirective,
requireOptimizedStyleAttribute,
requireStoresInit,
shorthandAttribute,
shorthandDirective,
sortAttributes,
Expand Down
6 changes: 4 additions & 2 deletions tests/fixtures/rules/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict"

module.exports = {
parserOptions: {
sourceType: "module",
},
overrides: [
{
files: ["*output.svelte"],
Expand All @@ -17,5 +18,6 @@ module.exports = {
"no-empty-function": "off",
"one-var": "off",
"func-style": "off",
"node/no-unsupported-features/es-syntax": "off",
},
}
2 changes: 0 additions & 2 deletions tests/fixtures/rules/indent/invalid/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
"use strict"

module.exports = {
rules: {
"no-sparse-arrays": "off",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
"use strict"

module.exports = {
overrides: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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], () => {})
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
import { writable, readable, derived } from "svelte/store"
const w = writable()
const r = readable()
const d = derived([a, b], () => {})
</script>
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
import { writable, readable, derived } from "svelte/store"
const w = writable(false)
const r = readable({})
const d = derived([a, b], () => {}, false)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
import { writable, readable, derived } from "./unknown"
const w = writable()
const r = readable()
const d = derived([a, b], () => {})
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
import { derived } from "svelte/store"
const args = [[a, b], () => {}, false]
const d = derived(...args)
</script>
16 changes: 16 additions & 0 deletions tests/src/rules/require-stores-init.ts
Original file line number Diff line number Diff line change
@@ -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"),
)
6 changes: 5 additions & 1 deletion tests/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions typings/eslint-utils/index.d.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down