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
67 changes: 67 additions & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"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"
],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure dist filenames exist (align with tsup output).

"module" and "exports.import" reference ./dist/index.mjs. Without the tsup outExtension change, this file won’t exist.

If you don’t change tsup, point to ./dist/index.js instead. Otherwise keep as-is after applying the tsup outExtension fix.

🤖 Prompt for AI Agents
In packages/eslint-plugin/package.json around lines 34 to 41, the "module" (and
corresponding "exports.import" elsewhere) points to ./dist/index.mjs which won't
exist unless tsup is configured to emit .mjs via outExtension; fix by either
updating your tsup config to set outExtension for .js -> .mjs so index.mjs is
generated, or change "module" to "./dist/index.js" (and update exports.import to
the same) so it points to the actual tsup output; ensure the package.json
entries exactly match the emitted filenames.

"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