Skip to content
Open
208 changes: 208 additions & 0 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# @pinia/eslint-plugin

ESLint plugin for Pinia best practices and common patterns.

## Installation

```bash
npm install --save-dev @pinia/eslint-plugin
```

## Usage

Add `@pinia/eslint-plugin` to your ESLint configuration:

### Flat Config (ESLint 9+)

```js
// eslint.config.js
import piniaPlugin from '@pinia/eslint-plugin'

export default [
{
plugins: {
'@pinia': piniaPlugin,
},
rules: {
'@pinia/require-setup-store-properties-export': 'error',
'@pinia/no-circular-store-dependencies': 'warn',
'@pinia/prefer-use-store-naming': 'warn',
'@pinia/no-store-in-computed': 'error',
},
},
]
```

### Legacy Config

```js
// .eslintrc.js
module.exports = {
plugins: ['@pinia'],
rules: {
'@pinia/require-setup-store-properties-export': 'error',
'@pinia/no-circular-store-dependencies': 'warn',
'@pinia/prefer-use-store-naming': 'warn',
'@pinia/no-store-in-computed': 'error',
},
}
```

### Recommended Configuration

You can use the recommended configuration which includes sensible defaults:

```js
// eslint.config.js
import piniaPlugin from '@pinia/eslint-plugin'

export default [
piniaPlugin.configs.recommended,
]
```

## Rules

### `@pinia/require-setup-store-properties-export`

**Type:** Problem
**Fixable:** Yes
**Recommended:** Error

Ensures that all variables and functions defined in setup stores are properly exported.

According to Pinia documentation, all variables and functions defined in a setup store should be returned from the setup function to be accessible on the store instance.

**❌ Incorrect:**
```js
export const useStore = defineStore('store', () => {
const count = ref(0)
const name = ref('test')

function increment() {
count.value++
}

// Missing exports for name and increment
return { count }
})
```

**✅ Correct:**
```js
export const useStore = defineStore('store', () => {
const count = ref(0)
const name = ref('test')

function increment() {
count.value++
}

return { count, name, increment }
})
```

### `@pinia/no-circular-store-dependencies`

**Type:** Problem
**Recommended:** Warning

Warns about potential circular dependencies between stores and prevents store instantiation in setup function bodies.

Circular dependencies can cause issues in Pinia stores, especially when stores try to access each other's state during initialization.

**❌ Incorrect:**
```js
export const useUserStore = defineStore('user', () => {
// Don't instantiate stores in setup function body
const cartStore = useCartStore()
const name = ref('John')

return { name }
})
```

**✅ Correct:**
```js
export const useUserStore = defineStore('user', () => {
const name = ref('John')

function updateProfile() {
// Use stores in actions
const cartStore = useCartStore()
cartStore.clear()
}

return { name, updateProfile }
})
```

### `@pinia/prefer-use-store-naming`

**Type:** Suggestion
**Fixable:** Yes
**Recommended:** Warning

Enforces consistent naming conventions for Pinia stores using the "useXxxStore" pattern.

**Options:**
- `prefix` (string): Prefix for store function names (default: 'use')
- `suffix` (string): Suffix for store function names (default: 'Store')

**❌ Incorrect:**
```js
export const userStore = defineStore('user', () => {
const name = ref('John')
return { name }
})
```

**✅ Correct:**
```js
export const useUserStore = defineStore('user', () => {
const name = ref('John')
return { name }
})
```

### `@pinia/no-store-in-computed`

**Type:** Problem
**Recommended:** Error

Prevents store instantiation inside computed properties, which can cause reactivity issues.

**❌ Incorrect:**
```js
export default {
setup() {
const userName = computed(() => {
const userStore = useUserStore() // Don't instantiate here
return userStore.name
})

return { userName }
}
}
```

**✅ Correct:**
```js
export default {
setup() {
const userStore = useUserStore() // Instantiate at top level

const userName = computed(() => userStore.name)

return { userName }
}
}
```

## Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.

## License

MIT
69 changes: 69 additions & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"name": "@pinia/eslint-plugin",
"version": "1.0.0",
"description": "ESLint plugin for Pinia best practices",
"keywords": [
"vue",
"pinia",
"eslint",
"plugin",
"linting",
"best-practices",
"store"
],
"homepage": "https://pinia.vuejs.org",
"bugs": {
"url": "https://github.com/vuejs/pinia/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/pinia.git"
},
"funding": "https://github.com/sponsors/posva",
"license": "MIT",
"author": {
"name": "Eduardo San Martin Morote",
"email": "[email protected]"
},
"sideEffects": false,
"exports": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist/*.js",
"dist/*.mjs",
"dist/*.d.ts",
"dist/*.d.mts",
"dist/*.map"
],
"scripts": {
"build": "tsup",
"test": "vitest run",
"test:watch": "vitest",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path . -l @pinia/eslint-plugin -r 1"
},
"devDependencies": {
"@typescript-eslint/parser": "^8.35.1",
"@typescript-eslint/rule-tester": "^8.35.1",
"@typescript-eslint/utils": "^8.35.1",
"eslint": "^9.0.0",
"pinia": "workspace:*",
"tsup": "^8.5.0",
"typescript": "~5.8.3",
"vitest": "^3.2.4"
},
"engines": {
"node": ">=18.18.0"
},
"peerDependencies": {
"eslint": ">=8.0.0"
},
"publishConfig": {
"access": "public"
}
}
47 changes: 47 additions & 0 deletions packages/eslint-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @fileoverview ESLint plugin for Pinia best practices
* @author Eduardo San Martin Morote
*/

import { requireSetupStorePropertiesExport } from './rules/require-setup-store-properties-export'
import { noCircularStoreDependencies } from './rules/no-circular-store-dependencies'
import { preferUseStoreNaming } from './rules/prefer-use-store-naming'
import { noStoreInComputed } from './rules/no-store-in-computed'
import packageJson from '../package.json'

/**
* ESLint plugin for Pinia best practices and common patterns.
*
* This plugin provides rules to enforce best practices when using Pinia stores,
* including proper export patterns for setup stores, avoiding circular dependencies,
* and following naming conventions.
*/
const plugin = {
meta: {
name: '@pinia/eslint-plugin',
version: packageJson.version,
},
rules: {
'require-setup-store-properties-export': requireSetupStorePropertiesExport,
'no-circular-store-dependencies': noCircularStoreDependencies,
'prefer-use-store-naming': preferUseStoreNaming,
'no-store-in-computed': noStoreInComputed,
},
}

// Flat config export
;(plugin as any).configs = {
recommended: [
{
plugins: { '@pinia': plugin as any },
rules: {
'@pinia/require-setup-store-properties-export': 'error',
'@pinia/no-circular-store-dependencies': 'warn',
'@pinia/prefer-use-store-naming': 'warn',
'@pinia/no-store-in-computed': 'error',
},
},
],
}

export default plugin
Loading