├── packages ├── eslint-config │ ├── src │ │ ├── flat.ts │ │ ├── constants.ts │ │ ├── configs │ │ │ ├── ignores.ts │ │ │ ├── stylistic.ts │ │ │ ├── javascript.ts │ │ │ ├── import.ts │ │ │ ├── disables.ts │ │ │ ├── nuxt.ts │ │ │ ├── typescript.ts │ │ │ ├── vue.ts │ │ │ └── formatters.ts │ │ ├── configs-tooling │ │ │ ├── regexp.ts │ │ │ ├── jsdoc.ts │ │ │ └── unicorn.ts │ │ ├── globs.ts │ │ ├── utils.ts │ │ ├── index.ts │ │ └── types.ts │ ├── build.config.ts │ ├── README.md │ ├── test │ │ ├── flat-compose.test.ts │ │ └── __snapshots__ │ │ │ └── flat-compose.test.ts.snap │ └── package.json ├── module │ ├── build.config.ts │ ├── src │ │ ├── module.ts │ │ ├── modules │ │ │ ├── config │ │ │ │ ├── addons │ │ │ │ │ └── globals.ts │ │ │ │ ├── init.ts │ │ │ │ ├── index.ts │ │ │ │ ├── utils.ts │ │ │ │ ├── devtools.ts │ │ │ │ └── generate.ts │ │ │ └── checker.ts │ │ └── types.ts │ ├── README.md │ └── package.json └── eslint-plugin │ ├── build.config.ts │ ├── src │ ├── index.ts │ └── rules │ │ ├── index.ts │ │ ├── utils.ts │ │ ├── prefer-import-meta │ │ └── index.ts │ │ ├── no-nuxt-config-test-key │ │ └── index.ts │ │ └── nuxt-config-keys-order │ │ ├── keys.ts │ │ └── index.ts │ ├── package.json │ └── README.md ├── .vscode └── settings.json ├── .npmrc ├── docs ├── .npmrc ├── tsconfig.json ├── public │ ├── favicon.ico │ ├── social-card.png │ └── images │ │ └── devtools-inspector.png ├── layouts │ ├── default.vue │ └── docs.vue ├── content │ ├── 1.packages │ │ ├── index.yml │ │ ├── 2.plugin.md │ │ ├── 1.config.md │ │ └── 0.module.md │ ├── 2.guide │ │ ├── index.yml │ │ ├── 1.migration.md │ │ └── 0.faq.md │ ├── 3.legacy │ │ ├── index.yml │ │ ├── 2.eslint-config-ts.md │ │ └── 1.eslint-config.md │ └── index.yml ├── .gitignore ├── .env.example ├── server │ └── api │ │ └── search.json.get.ts ├── package.json ├── README.md ├── style.css ├── app.config.ts ├── tailwind.config.ts ├── components │ ├── content │ │ └── ReadMore.vue │ ├── OgImage │ │ └── OgImageDocs.vue │ └── NuxtLogo.vue ├── nuxt.config.ts ├── app.vue └── pages │ ├── [...slug].vue │ └── index.vue ├── playground ├── tsconfig.json ├── server │ └── tsconfig.json ├── components │ └── TheB.vue ├── components-prefixed │ └── A.vue ├── eslint.config.mjs ├── pages │ └── index.vue ├── app.vue ├── package.json └── nuxt.config.ts ├── .github ├── assets │ └── social-card.png ├── workflows │ ├── release.yml │ ├── ci.yml │ └── autofix.yml └── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ └── bug-report.yml ├── taze.config.ts ├── .gitignore ├── renovate.json ├── .editorconfig ├── tsconfig.json ├── LICENSE ├── eslint.config.js ├── README.md ├── package.json └── pnpm-workspace.yaml /packages/eslint-config/src/flat.ts: -------------------------------------------------------------------------------- 1 | export * from './index' 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.useFlatConfig": true 3 | } 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | ignore-workspace-root-check=true 3 | -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt/eslint/HEAD/docs/public/favicon.ico -------------------------------------------------------------------------------- /packages/eslint-config/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const GLOB_EXTS = '{js,ts,jsx,tsx,vue}' 2 | -------------------------------------------------------------------------------- /playground/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /docs/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/public/social-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt/eslint/HEAD/docs/public/social-card.png -------------------------------------------------------------------------------- /.github/assets/social-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt/eslint/HEAD/.github/assets/social-card.png -------------------------------------------------------------------------------- /docs/content/1.packages/index.yml: -------------------------------------------------------------------------------- 1 | navigation.title: Packages 2 | navigation.icon: i-ph-package-duotone 3 | -------------------------------------------------------------------------------- /docs/content/2.guide/index.yml: -------------------------------------------------------------------------------- 1 | navigation.title: Guide 2 | navigation.icon: i-ph-book-bookmark-duotone 3 | -------------------------------------------------------------------------------- /taze.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'taze' 2 | 3 | export default defineConfig({ 4 | 5 | }) 6 | -------------------------------------------------------------------------------- /docs/content/3.legacy/index.yml: -------------------------------------------------------------------------------- 1 | navigation.title: Legacy Packages 2 | navigation.icon: i-ph-archive-duotone 3 | -------------------------------------------------------------------------------- /docs/public/images/devtools-inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt/eslint/HEAD/docs/public/images/devtools-inspector.png -------------------------------------------------------------------------------- /playground/components/TheB.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log 5 | .nuxt 6 | .output 7 | **/.yarn/cache 8 | **/.yarn/*state* 9 | dist 10 | .eslintcache 11 | -------------------------------------------------------------------------------- /playground/components-prefixed/A.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /playground/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import withNuxt from './.nuxt/eslint.config.mjs' 2 | 3 | export default withNuxt( 4 | // Your custom configs here 5 | ) 6 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | .env 12 | .output 13 | -------------------------------------------------------------------------------- /docs/.env.example: -------------------------------------------------------------------------------- 1 | # To use Nuxt UI Pro in production 2 | NUXT_UI_PRO_LICENSE= 3 | 4 | # Used when pre-rendering the docs for dynamic OG images 5 | NUXT_PUBLIC_SITE_URL= 6 | -------------------------------------------------------------------------------- /packages/module/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | entries: [ 5 | 'src/module.ts', 6 | ], 7 | }) 8 | -------------------------------------------------------------------------------- /playground/pages/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /packages/eslint-plugin/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | entries: [ 5 | 'src/index.ts', 6 | ], 7 | declaration: true, 8 | }) 9 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>nuxt/renovate-config-nuxt" 5 | ], 6 | "lockFileMaintenance": { 7 | "enabled": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/eslint-config/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | entries: [ 5 | 'src/flat.ts', 6 | 'src/index.ts', 7 | ], 8 | declaration: true, 9 | }) 10 | -------------------------------------------------------------------------------- /packages/eslint-plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { ESLint } from 'eslint' 2 | import rules from './rules' 3 | 4 | export default { 5 | meta: { 6 | name: '@nuxt/eslint-plugin', 7 | }, 8 | rules, 9 | } satisfies ESLint.Plugin 10 | -------------------------------------------------------------------------------- /docs/server/api/search.json.get.ts: -------------------------------------------------------------------------------- 1 | import { serverQueryContent } from '#content/server' 2 | 3 | export default eventHandler(async (event) => { 4 | return serverQueryContent(event).where({ _type: 'markdown', navigation: { $ne: false } }).find() 5 | }) 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | uses: sxzz/workflows/.github/workflows/release.yml@v1 11 | with: 12 | publish: true 13 | permissions: 14 | contents: write 15 | id-token: write 16 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-eslint-playground", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "play:dev": "nuxi dev", 7 | "play:build": "nuxi build", 8 | "lint": "nuxi prepare && eslint ." 9 | }, 10 | "devDependencies": { 11 | "nuxt": "catalog:dev" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-eslint-docs", 3 | "packageManager": "pnpm@10.25.0", 4 | "private": true, 5 | "scripts": { 6 | "docs:dev": "nuxi dev", 7 | "docs:build": "NODE_OPTIONS=--max_old_space_size=8192 nuxi build", 8 | "docs:generate": "NODE_OPTIONS=--max_old_space_size=8192 nuxi generate", 9 | "docs:preview": "nuxi preview" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/ignores.ts: -------------------------------------------------------------------------------- 1 | import type { Linter } from 'eslint' 2 | 3 | export default function ignores(): Linter.Config[] { 4 | return [ 5 | { 6 | ignores: [ 7 | '**/dist', 8 | '**/node_modules', 9 | '**/.nuxt', 10 | '**/.output', 11 | '**/.vercel', 12 | '**/.netlify', 13 | '**/public', 14 | ], 15 | }, 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: 📚 Nuxt ESLint Documentation 4 | url: https://eslint.nuxt.com/packages/module 5 | about: Check the documentation for usage of Nuxt ESLint 6 | - name: 💬 Discussions 7 | url: https://github.com/nuxt/eslint/discussions 8 | about: Use discussions if you have another issue, an idea for improvement or for asking questions. 9 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs-tooling/regexp.ts: -------------------------------------------------------------------------------- 1 | import type { Linter } from 'eslint' 2 | import { configs } from 'eslint-plugin-regexp' 3 | import { GLOB_SRC, GLOB_VUE } from '../globs' 4 | 5 | export default function regexp(): Linter.Config[] { 6 | return [ 7 | { 8 | ...configs['flat/recommended'] as Linter.Config, 9 | name: 'nuxt/tooling/regexp', 10 | files: [GLOB_SRC, GLOB_VUE], 11 | }, 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/eslint-plugin/src/rules/index.ts: -------------------------------------------------------------------------------- 1 | import { rule as preferImportMetaRule } from './prefer-import-meta' 2 | import { rule as nuxtConfigOrderKeysRule } from './nuxt-config-keys-order' 3 | import { rule as noNuxtConfigTestKeyRule } from './no-nuxt-config-test-key' 4 | 5 | export default { 6 | 'prefer-import-meta': preferImportMetaRule, 7 | 'nuxt-config-keys-order': nuxtConfigOrderKeysRule, 8 | 'no-nuxt-config-test-key': noNuxtConfigTestKeyRule, 9 | } 10 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/stylistic.ts: -------------------------------------------------------------------------------- 1 | import stylistic from '@stylistic/eslint-plugin' 2 | import type { StylisticCustomizeOptions } from '@stylistic/eslint-plugin' 3 | import type { Linter } from 'eslint' 4 | import { GLOB_SRC, GLOB_VUE } from '../globs' 5 | 6 | export default (options?: StylisticCustomizeOptions): Linter.Config => { 7 | return { 8 | name: 'nuxt/stylistic', 9 | files: [GLOB_SRC, GLOB_VUE], 10 | ...stylistic.configs.customize(options) as Linter, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/eslint-plugin/src/rules/utils.ts: -------------------------------------------------------------------------------- 1 | import { ESLintUtils } from '@typescript-eslint/utils' 2 | import type { Rule } from 'eslint' 3 | 4 | export function createRule< 5 | TMessageIds extends string, 6 | TOptions extends readonly unknown[], 7 | >( 8 | rule: Readonly>, 9 | ) { 10 | const _createRule = ESLintUtils.RuleCreator( 11 | name => `https://eslint.nuxt.com/packages/plugin#nuxt${name}`, 12 | ) 13 | return _createRule(rule) as unknown as Rule.RuleModule 14 | } 15 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | modules: [ 3 | '../packages/module/src/module', 4 | ], 5 | 6 | components: [ 7 | '~/components', 8 | { path: '~/components-prefixed', prefix: 'Prefix' }, 9 | ], 10 | 11 | devtools: { 12 | enabled: true, 13 | }, 14 | 15 | compatibilityDate: '2024-07-27', 16 | 17 | eslint: { 18 | config: { 19 | // configFile: './eslint.nuxt.config.mjs', 20 | stylistic: true, 21 | }, 22 | checker: { 23 | lintOnStart: true, 24 | fix: true, 25 | }, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /docs/layouts/docs.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 24 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Nuxt ESLint 2 | 3 | Docs template with [Nuxt UI](https://ui.nuxt.com). 4 | 5 | ## Setup 6 | 7 | Install dependencies inside `docs/`: 8 | 9 | ```bash 10 | pnpm i 11 | ``` 12 | 13 | ## Development 14 | 15 | ```bash 16 | npm run dev 17 | ``` 18 | 19 | ## Static Generation 20 | 21 | Use the `generate` command to build your application. 22 | 23 | The HTML files will be generated in the .output/public directory and ready to be deployed to any static compatible hosting. 24 | 25 | ```bash 26 | npm run generate 27 | ``` 28 | 29 | ## Preview build 30 | 31 | You might want to preview the result of your build locally, to do so, run the following command: 32 | 33 | ```bash 34 | npm run preview 35 | ``` 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "allowJs": true, 8 | "noEmit": true, 9 | "skipDefaultLibCheck": true, 10 | "skipLibCheck": true, 11 | "paths": { 12 | "@nuxt/eslint-plugin": [ 13 | "./packages/eslint-plugin/src/index.ts" 14 | ], 15 | "@nuxt/eslint-config": [ 16 | "./packages/eslint-config/src/index.ts" 17 | ], 18 | "@nuxt/eslint-config/flat": [ 19 | "./packages/eslint-config/src/flat.ts" 20 | ], 21 | } 22 | }, 23 | "exclude": [ 24 | "**/dist/**", 25 | "**/node_modules/**", 26 | "playground", 27 | "docs", 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /docs/content/3.legacy/2.eslint-config-ts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: '@nuxtjs/eslint-config-typescript' 3 | --- 4 | 5 | TypeScript version of [`@nuxtjs/eslint-config`](/legacy/eslint-config), for Nuxt 2. 6 | 7 | :::callout{icon="i-ph-lightbulb-duotone"} 8 | This package is for Nuxt 2. For Nuxt 3 projects, use [`@nuxt/eslint`](/packages/module) instead.
9 | ::: 10 | 11 | :::callout{icon="i-ph-warning-circle-duotone" color="amber"} 12 | This package is in maintainance mode and no longer have active developments. 13 | ::: 14 | 15 | ::read-more 16 | --- 17 | to: https://github.com/nuxt/eslint/tree/main/packages-legacy/nuxt2-eslint-config-typescript 18 | color: gray 19 | icon: i-simple-icons-github 20 | --- 21 | Source code on GitHub 22 | :: 23 | 24 | ### Usage 25 | 26 | Refer to [this page](/legacy/eslint-config#typescript) for the usage. 27 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | .packages-disambiguation-table .badge-nuxt3 code { 2 | color: rgb(var(--color-primary-400)); 3 | background-color: rgb(var(--color-primary-400) / 0.1); 4 | border-color: rgb(var(--color-primary-400) / 0.3); 5 | } 6 | 7 | .packages-disambiguation-table .badge-nuxt2 code { 8 | color: #89c234; 9 | background-color: #89c23420; 10 | border-color: #89c23450; 11 | } 12 | 13 | .packages-disambiguation-table .badge-flat code { 14 | color: #b87be0; 15 | background-color: #b87be020; 16 | border-color: #b87be050; 17 | } 18 | 19 | .packages-disambiguation-table .badge-recommend code { 20 | color: #46c79a; 21 | background-color: #46c79a20; 22 | border-color: #46c79a50; 23 | } 24 | 25 | .packages-disambiguation-table .badge-legacy code { 26 | color: #a7753d; 27 | background-color: #a7753d20; 28 | border-color: #a7753d50; 29 | } 30 | -------------------------------------------------------------------------------- /docs/content/1.packages/2.plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: '@nuxt/eslint-plugin' 3 | --- 4 | 5 | ESLint plugin of additional rules for Nuxt 3. 6 | 7 | :::callout{icon="i-ph-lightbulb-duotone"} 8 | Usually, you don't need to use this package directly. It's already included in the [ESLint Module](/packages/module) and the [ESLint Config](/packages/config). 9 | ::: 10 | 11 | ::read-more 12 | --- 13 | to: https://github.com/nuxt/eslint/tree/main/packages/eslint-plugin 14 | color: gray 15 | icon: i-simple-icons-github 16 | --- 17 | Source code on GitHub 18 | :: 19 | 20 | ## Rules 21 | 22 | ### `nuxt/prefer-import-meta` 23 | 24 | This rule enforces the use of `import.meta.client` / `import.meta.server` in Nuxt 3 projects instead of `process.client` / `process.server`. 25 | 26 | ```json 27 | { 28 | "rules": { 29 | "nuxt/prefer-import-meta": "error" 30 | } 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | ci: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | node-version: [20] 17 | os: [ubuntu-latest, windows-latest, macos-latest] 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: pnpm/action-setup@v4 22 | with: 23 | run_install: false 24 | 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: pnpm install 29 | - run: pnpm build 30 | - run: pnpm lint 31 | - run: pnpm lint:play 32 | - run: pnpm vitest run 33 | - uses: codecov/codecov-action@v5 34 | env: 35 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 36 | -------------------------------------------------------------------------------- /docs/content/2.guide/1.migration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Migration Guide 3 | --- 4 | 5 | ## Migrate from `@nuxtjs/eslint-module` 6 | 7 | [`@nuxtjs/eslint-module`](https://github.com/nuxt-modules/eslint) is a module for running ESLint checks along with your dev server. 8 | 9 | It will be merged into [`@nuxt/eslint`](/packages/module) as an opt-in feature under the `checker` option. 10 | 11 | ```bash 12 | npm i -D @nuxt/eslint vite-plugin-eslint2 13 | npm uninstall @nuxtjs/eslint-module 14 | ``` 15 | 16 | ```ts [nuxt.config.ts] 17 | export default defineNuxtConfig({ 18 | modules: [ 19 | // Replace '@nuxtjs/eslint-module' with: 20 | '@nuxt/eslint', 21 | ], 22 | eslint: { 23 | checker: { 24 | // the options from `@nuxtjs/eslint-module` go here 25 | } 26 | } 27 | }) 28 | ``` 29 | 30 | You can learn more about it on [this page](/packages/module#dev-server-checker). 31 | -------------------------------------------------------------------------------- /packages/module/src/module.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtModule } from '@nuxt/kit' 2 | import type { ModuleOptions } from './types' 3 | 4 | export * from './types' 5 | 6 | export default defineNuxtModule({ 7 | meta: { 8 | name: '@nuxt/eslint', 9 | configKey: 'eslint', 10 | }, 11 | defaults: { 12 | config: true, 13 | checker: false, 14 | }, 15 | async setup(options, nuxt) { 16 | if (options.config) { 17 | await import('./modules/config') 18 | .then(({ setupConfigGen }) => setupConfigGen(options, nuxt)) 19 | } 20 | if (options.checker) { 21 | // TODO: maybe support build mode later on 22 | if (nuxt.options.dev) { 23 | await import('./modules/checker') 24 | .then(({ setupESLintChecker }) => { 25 | setupESLintChecker(options, nuxt) 26 | }) 27 | } 28 | } 29 | }, 30 | }) 31 | -------------------------------------------------------------------------------- /packages/eslint-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxt/eslint-plugin", 3 | "type": "module", 4 | "version": "1.12.1", 5 | "description": "ESLint plugin for Nuxt", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/nuxt/eslint.git", 10 | "directory": "packages/eslint-plugin" 11 | }, 12 | "exports": { 13 | ".": "./dist/index.mjs" 14 | }, 15 | "main": "./dist/index.mjs", 16 | "module": "./dist/index.mjs", 17 | "types": "./dist/index.d.mts", 18 | "files": [ 19 | "dist" 20 | ], 21 | "scripts": { 22 | "build": "unbuild", 23 | "stub": "unbuild --stub", 24 | "prepack": "pnpm run build" 25 | }, 26 | "peerDependencies": { 27 | "eslint": "^9.0.0" 28 | }, 29 | "dependencies": { 30 | "@typescript-eslint/types": "catalog:prod", 31 | "@typescript-eslint/utils": "catalog:prod" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/autofix.yml: -------------------------------------------------------------------------------- 1 | name: autofix.ci # needed to securely identify the workflow 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "docs/**" 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | code: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: pnpm/action-setup@v4 19 | with: 20 | run_install: false 21 | 22 | - uses: actions/setup-node@v4 23 | with: 24 | node-version: 20 25 | cache: "pnpm" 26 | 27 | - name: Install dependencies 28 | run: pnpm install 29 | 30 | - name: Run build 31 | run: pnpm build 32 | 33 | - name: Test (unit) 34 | run: pnpm test -u 35 | 36 | - name: Lint (code) 37 | run: pnpm lint:fix 38 | 39 | - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef 40 | -------------------------------------------------------------------------------- /docs/content/index.yml: -------------------------------------------------------------------------------- 1 | navigation: false 2 | hero: 3 | title: 'All-in-one
ESLint integration for Nuxt' 4 | description: Collection of ESLint-related packages for Nuxt,
providing project-aware, easy-to-use, extensible and future-proof integration. 5 | button: Get started 6 | sections: 7 | - title: Packages 8 | slot: tools 9 | toolsCards: 10 | - title: Nuxt Module 11 | description: All-in-one Nuxt module for ESLint, generates custom ESLint configuration based on your Nuxt project. 12 | icon: i-ph-cube-transparent-light 13 | to: '/packages/module' 14 | - title: ESLint Config 15 | description: Shareable ESLint configuration for Nuxt projects. 16 | icon: i-ph-gear-six-light 17 | to: 'packages/config' 18 | - title: ESLint Plugin 19 | description: ESLint plugin for Nuxt projects, that enable Nuxt specific rules. 20 | icon: i-ph-ruler-light 21 | to: 'packages/plugin' 22 | -------------------------------------------------------------------------------- /docs/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | ui: { 3 | primary: 'green', 4 | gray: 'slate', 5 | button: { 6 | color: { 7 | white: { 8 | link: 'text-white dark:text-white hover:text-gray-300 dark:hover:text-gray-300 underline-offset-4 hover:underline focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-gray-500 dark:focus-visible:ring-gray-400 transition-all duration-200', 9 | }, 10 | transparent: { 11 | outline: 'ring-1 ring-inset ring-gray-700 text-white dark:text-white hover:bg-gray-900 disabled:bg-gray-300 dark:hover:bg-gray-900 dark:disabled:bg-gray-300 focus-visible:ring-2 focus-visible:ring-gray-400 dark:focus-visible:ring-gray-400', 12 | }, 13 | }, 14 | }, 15 | }, 16 | elements: { 17 | variables: { 18 | light: { 19 | background: '255 255 255', 20 | foreground: 'var(--color-gray-700)', 21 | }, 22 | dark: { 23 | background: 'var(--color-gray-950)', 24 | foreground: 'var(--color-gray-200)', 25 | }, 26 | }, 27 | }, 28 | }) 29 | -------------------------------------------------------------------------------- /docs/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import defaultTheme from 'tailwindcss/defaultTheme' 2 | import type { Config } from 'tailwindcss' 3 | 4 | export default >{ 5 | theme: { 6 | extend: { 7 | colors: { 8 | slate: { 9 | 50: '#f8fafc', 10 | 100: '#f1f5f9', 11 | 200: '#e2e8f0', 12 | 300: '#cbd5e1', 13 | 400: '#94a3b8', 14 | 500: '#64748b', 15 | 600: '#475569', 16 | 700: '#334155', 17 | 800: '#1e293b', 18 | 900: '#0f172a', 19 | 950: '#020420', 20 | }, 21 | green: { 22 | 50: '#EFFDF5', 23 | 100: '#D9FBE8', 24 | 200: '#B3F5D1', 25 | 300: '#75EDAE', 26 | 400: '#00DC82', 27 | 500: '#00C16A', 28 | 600: '#00A155', 29 | 700: '#007F45', 30 | 800: '#016538', 31 | 900: '#0A5331', 32 | 950: '#052e16', 33 | }, 34 | }, 35 | fontFamily: { 36 | sans: ['Inter var', 'Inter', ...defaultTheme.fontFamily.sans], 37 | }, 38 | }, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /docs/components/content/ReadMore.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2022 Nuxt Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { createConfigForNuxt } from '@nuxt/eslint-config/flat' 3 | import pnpm from 'eslint-plugin-pnpm' 4 | import jsoncParser from 'jsonc-eslint-parser' 5 | 6 | export default createConfigForNuxt({ 7 | features: { 8 | stylistic: true, 9 | tooling: true, 10 | formatters: true, 11 | // typescript: { 12 | // tsconfigPath: 'tsconfig.json', 13 | // }, 14 | }, 15 | dirs: { 16 | src: [ 17 | 'playground', 18 | 'docs', 19 | ], 20 | componentsPrefixed: [ 21 | 'playground/components-prefixed', 22 | ], 23 | }, 24 | }) 25 | .append( 26 | { 27 | files: ['docs/**/*.vue'], 28 | rules: { 29 | 'vue/no-v-html': 'off', 30 | }, 31 | }, 32 | ) 33 | .append({ 34 | name: 'pnpm/package.json', 35 | files: [ 36 | 'package.json', 37 | '**/package.json', 38 | ], 39 | languageOptions: { 40 | parser: jsoncParser, 41 | }, 42 | plugins: { 43 | pnpm: pnpm, 44 | }, 45 | rules: { 46 | 'pnpm/json-enforce-catalog': 'error', 47 | 'pnpm/json-valid-catalog': 'error', 48 | 'pnpm/json-prefer-workspace-settings': 'error', 49 | }, 50 | }) 51 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/javascript.ts: -------------------------------------------------------------------------------- 1 | import pluginESLint from '@eslint/js' 2 | import type { Linter } from 'eslint' 3 | import globals from 'globals' 4 | 5 | export default function javascript(): Linter.Config[] { 6 | return [ 7 | { 8 | ...pluginESLint.configs.recommended, 9 | name: 'nuxt/javascript', 10 | languageOptions: { 11 | ecmaVersion: 2022, 12 | parserOptions: { 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | ecmaVersion: 2022, 17 | sourceType: 'module', 18 | }, 19 | sourceType: 'module', 20 | globals: { 21 | ...globals.browser, 22 | ...globals.es2021, 23 | ...globals.node, 24 | document: 'readonly', 25 | navigator: 'readonly', 26 | window: 'readonly', 27 | 28 | // This is technically not a global function, but it's a common practice in nuxt.config.ts, 29 | // we include it here to avoid false positives. 30 | defineNuxtConfig: 'readonly', 31 | }, 32 | }, 33 | linterOptions: { 34 | reportUnusedDisableDirectives: true, 35 | }, 36 | }, 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /docs/components/OgImage/OgImageDocs.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 44 | -------------------------------------------------------------------------------- /packages/module/README.md: -------------------------------------------------------------------------------- 1 | # `@nuxt/eslint` 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | [![License][license-src]][license-href] 6 | [![Nuxt][nuxt-src]][nuxt-href] 7 | 8 | All-in-one ESLint integration for Nuxt. It generates a project-aware [ESLint flat config](https://eslint.org/docs/latest/use/configure/configuration-files-new) and provides the ability to optionally run ESLint check along side the dev server. 9 | 10 | Refer to the [documentation](https://eslint.nuxt.com/packages/module) for more details. 11 | 12 | ## License 13 | 14 | MIT 15 | 16 | 17 | [npm-version-src]: https://img.shields.io/npm/v/@nuxt/eslint/latest.svg?style=flat&colorA=18181B&colorB=28CF8D 18 | [npm-version-href]: https://npmjs.com/package/@nuxt/eslint 19 | 20 | [npm-downloads-src]: https://img.shields.io/npm/dm/@nuxt/eslint.svg?style=flat&colorA=18181B&colorB=28CF8D 21 | [npm-downloads-href]: https://npmjs.com/package/@nuxt/eslint 22 | 23 | [license-src]: https://img.shields.io/npm/l/@nuxt/eslint.svg?style=flat&colorA=18181B&colorB=28CF8D 24 | [license-href]: https://npmjs.com/package/@nuxt/eslint 25 | 26 | [nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt 27 | [nuxt-href]: https://nuxt.com 28 | -------------------------------------------------------------------------------- /docs/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | extends: '@nuxt/ui-pro', 3 | 4 | modules: [ 5 | '@nuxt/image', 6 | '@nuxt/content', 7 | '@nuxt/fonts', 8 | '@nuxt/ui', 9 | '@nuxthq/studio', 10 | '@vueuse/nuxt', 11 | '@nuxtjs/plausible', 12 | 'nuxt-og-image', 13 | ], 14 | 15 | $production: { 16 | nitro: { 17 | experimental: { 18 | wasm: true, 19 | }, 20 | }, 21 | }, 22 | 23 | site: { 24 | url: 'https://eslint.nuxt.com', 25 | }, 26 | 27 | colorMode: { 28 | preference: 'dark', 29 | }, 30 | 31 | ui: { 32 | icons: ['heroicons', 'simple-icons', 'ph'], 33 | }, 34 | 35 | routeRules: { 36 | '/guide': { redirect: '/guide/getting-started' }, 37 | }, 38 | 39 | compatibilityDate: '2024-09-01', 40 | 41 | nitro: { 42 | prerender: { 43 | routes: ['/api/search.json'], 44 | autoSubfolderIndex: false, 45 | }, 46 | }, 47 | 48 | hooks: { 49 | // Related to https://github.com/nuxt/nuxt/pull/22558 50 | // Adding all global components to the main entry 51 | // To avoid lagging during page navigation on client-side 52 | 'components:extend': function (components) { 53 | for (const comp of components) { 54 | if (comp.global) 55 | comp.global = 'sync' 56 | } 57 | }, 58 | }, 59 | }) 60 | -------------------------------------------------------------------------------- /packages/module/src/modules/config/addons/globals.ts: -------------------------------------------------------------------------------- 1 | import type { Nuxt } from '@nuxt/schema' 2 | import type { Unimport } from 'unimport' 3 | import type { ESLintConfigGenAddon } from '../../../types' 4 | 5 | export function createAddonGlobals(nuxt: Nuxt): ESLintConfigGenAddon { 6 | let unimport: Unimport | undefined 7 | let nitroUnimport: Unimport | undefined 8 | 9 | nuxt.hook('imports:context', (context) => { 10 | unimport = context 11 | }) 12 | 13 | nuxt.hook('nitro:init', (nitro) => { 14 | nitroUnimport = nitro.unimport 15 | }) 16 | 17 | return { 18 | name: 'nuxt:eslint:import-globals', 19 | async getConfigs() { 20 | const imports = [ 21 | ...await unimport?.getImports() || [], 22 | ...await nitroUnimport?.getImports() || [], 23 | ].sort((a, b) => 10 * a.from.localeCompare(b.from) + a.name.localeCompare(b.name)) 24 | 25 | return { 26 | configs: [ 27 | [ 28 | '// Set globals from imports registry', 29 | '{', 30 | ` name: 'nuxt/import-globals',`, 31 | ' languageOptions: {', 32 | ` globals: Object.fromEntries(${JSON.stringify(imports.map(i => i.as || i.name))}.map(i => [i, 'readonly'])),`, 33 | ` },`, 34 | '}', 35 | ].join('\n'), 36 | ], 37 | } 38 | }, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/eslint-plugin/README.md: -------------------------------------------------------------------------------- 1 | # `@nuxt/eslint-plugin` 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | 6 | ESLint plugin of additional rules for Nuxt 3. 7 | 8 | Refer to the [documentation](https://eslint.nuxt.com/packages/plugin) for more details. 9 | 10 | 11 | 12 | [npm-version-src]: https://img.shields.io/npm/v/@nuxt/eslint-plugin?style=flat-square 13 | [npm-version-href]: https://npmjs.com/package/@nuxt/eslint-plugin 14 | [npm-downloads-src]: https://img.shields.io/npm/dm/@nuxt/eslint-plugin?style=flat-square 15 | [npm-downloads-href]: https://npmjs.com/package/@nuxt/eslint-plugin 16 | [github-actions-src]: https://img.shields.io/github/workflow/status/nuxt/eslint-plugin/ci/main?style=flat-square 17 | [github-actions-href]: https://github.com/nuxt/eslint-plugin/actions?query=workflow%3Aci 18 | [codecov-src]: https://img.shields.io/codecov/c/gh/nuxt/eslint-plugin/main?style=flat-square 19 | [codecov-href]: https://codecov.io/gh/nuxt/eslint-plugin 20 | [lgtm-src]: https://img.shields.io/lgtm/grade/javascript/github/nuxt/eslint-plugin?style=flat-square 21 | [lgtm-href]: https://lgtm.com/projects/g/nuxt/eslint-plugin 22 | [bundlephobia-src]: https://img.shields.io/bundlephobia/minzip/@nuxt/eslint-plugin?style=flat-square 23 | [bundlephobia-href]: https://bundlephobia.com/package/@nuxt/eslint-plugin 24 | -------------------------------------------------------------------------------- /packages/eslint-config/README.md: -------------------------------------------------------------------------------- 1 | # `@nuxt/eslint-config` 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | 6 | Shared ESLint config for Nuxt 3 projects. Unopinionated by default, but customizable. 7 | 8 | Refer to the [documentation](https://eslint.nuxt.com/packages/config) for more details. 9 | 10 | 11 | 12 | [npm-version-src]: https://img.shields.io/npm/v/@nuxt/eslint-config?style=flat-square 13 | [npm-version-href]: https://npmjs.com/package/@nuxt/eslint-config 14 | [npm-downloads-src]: https://img.shields.io/npm/dm/@nuxt/eslint-config?style=flat-square 15 | [npm-downloads-href]: https://npmjs.com/package/@nuxt/eslint-config 16 | [github-actions-src]: https://img.shields.io/github/workflow/status/nuxt/eslint-config/ci/main?style=flat-square 17 | [github-actions-href]: https://github.com/nuxt/eslint-config/actions?query=workflow%3Aci 18 | [codecov-src]: https://img.shields.io/codecov/c/gh/nuxt/eslint-config/main?style=flat-square 19 | [codecov-href]: https://codecov.io/gh/nuxt/eslint-config 20 | [lgtm-src]: https://img.shields.io/lgtm/grade/javascript/github/nuxt/eslint-config?style=flat-square 21 | [lgtm-href]: https://lgtm.com/projects/g/nuxt/eslint-config 22 | [bundlephobia-src]: https://img.shields.io/bundlephobia/minzip/@nuxt/eslint-config?style=flat-square 23 | [bundlephobia-href]: https://bundlephobia.com/package/@nuxt/eslint-config 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![@nuxt/eslint](./.github/assets/social-card.png) 2 | 3 | # Nuxt ESLint 4 | 5 | [![npm version][npm-version-src]][npm-version-href] 6 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 7 | [![License][license-src]][license-href] 8 | [![Nuxt][nuxt-src]][nuxt-href] 9 | 10 | Collection of ESLint-related packages for Nuxt, 11 | provides project-aware, easy-to-use, extensible and future-proof integrations. 12 | 13 | Refer to the [documentation](https://eslint.nuxt.com) for more details. 14 | 15 | ## Packages 16 | 17 | - [@nuxt/eslint](./packages/module) - Nuxt module generating project-aware ESLint config 18 | - [@nuxt/eslint-config](./packages/eslint-config) - Static ESLint Config 19 | - [@nuxt/eslint-plugin](./packages/eslint-plugin) - ESLint Plugin 20 | 21 | ## License 22 | 23 | [MIT License](./LICENSE) 24 | 25 | 26 | 27 | [npm-version-src]: https://img.shields.io/npm/v/@nuxt/eslint.svg?style=flat&colorA=18181B&colorB=28CF8D 28 | [npm-version-href]: https://npmjs.com/package/@nuxt/eslint 29 | 30 | [npm-downloads-src]: https://img.shields.io/npm/dm/@nuxt/eslint.svg?style=flat&colorA=18181B&colorB=28CF8D 31 | [npm-downloads-href]: https://npmjs.com/package/@nuxt/eslint 32 | 33 | [license-src]: https://img.shields.io/npm/l/@nuxt/eslint.svg?style=flat&colorA=18181B&colorB=28CF8D 34 | [license-href]: https://npmjs.com/package/@nuxt/eslint 35 | 36 | [nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt 37 | [nuxt-href]: https://nuxt.com 38 | -------------------------------------------------------------------------------- /packages/eslint-plugin/src/rules/prefer-import-meta/index.ts: -------------------------------------------------------------------------------- 1 | import { AST_NODE_TYPES } from '@typescript-eslint/types' 2 | import { createRule } from '../utils' 3 | 4 | type MessageIds = 'default' 5 | 6 | type Options = [] 7 | 8 | const processSuffixes = new Set([ 9 | 'client', 10 | 'browser', 11 | 'server', 12 | 'nitro', 13 | 'dev', 14 | 'test', 15 | 'prerender', 16 | ]) 17 | 18 | export const rule = createRule({ 19 | name: 'prefer-import-meta', 20 | meta: { 21 | type: 'suggestion', 22 | docs: { 23 | description: 'Prefer using `import.meta.*` over `process.*`', 24 | }, 25 | schema: [], 26 | messages: { 27 | default: 'Replace `process.{{ suffix }}` with `import.meta.{{ suffix }}`.', 28 | }, 29 | fixable: 'code', 30 | }, 31 | defaultOptions: [], 32 | create: context => ({ 33 | MemberExpression: (node) => { 34 | if ( 35 | node.object.type === AST_NODE_TYPES.Identifier 36 | && node.object.name === 'process' 37 | && node.property.type === AST_NODE_TYPES.Identifier 38 | && processSuffixes.has(node.property.name) 39 | ) { 40 | const suffix = node.property.name 41 | 42 | context.report({ 43 | node, 44 | messageId: 'default', 45 | data: { 46 | suffix, 47 | }, 48 | fix: fixer => fixer.replaceText(node, `import.meta.${suffix}`), 49 | }) 50 | } 51 | }, 52 | }), 53 | }) 54 | -------------------------------------------------------------------------------- /packages/module/src/modules/config/init.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises' 2 | import { logger } from '@nuxt/kit' 3 | import type { Nuxt } from '@nuxt/schema' 4 | import { relative, join } from 'pathe' 5 | 6 | export async function initRootESLintConfig(nuxt: Nuxt, generateConfigPath: string) { 7 | const { findUp } = await import('find-up') 8 | 9 | const hasFlatConfig = await findUp( 10 | [ 11 | 'eslint.config.js', 12 | 'eslint.config.mjs', 13 | 'eslint.config.cjs', 14 | 'eslint.config.ts', 15 | 'eslint.config.mts', 16 | 'eslint.config.cts', 17 | ], 18 | { 19 | cwd: nuxt.options.rootDir, 20 | }, 21 | ) 22 | 23 | if (hasFlatConfig) 24 | return 25 | 26 | const targetPath = join(nuxt.options.rootDir, 'eslint.config.mjs') 27 | let relativeDistPath = relative(nuxt.options.rootDir, generateConfigPath) 28 | if (!relativeDistPath.startsWith('./') && !relativeDistPath.startsWith('../')) 29 | relativeDistPath = './' + relativeDistPath 30 | 31 | await fs.writeFile( 32 | targetPath, 33 | [ 34 | '// @ts-check', 35 | `import withNuxt from '${relativeDistPath}'`, 36 | '', 37 | 'export default withNuxt(', 38 | ' // Your custom configs here', 39 | ')', 40 | '', 41 | ].join('\n'), 42 | 'utf-8', 43 | ) 44 | 45 | logger.success(`ESLint config file created at ${targetPath}`) 46 | logger.info(`If you have .eslintrc or .eslintignore files, you might want to migrate them to the new config file`) 47 | } 48 | -------------------------------------------------------------------------------- /packages/eslint-config/test/flat-compose.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import type { Linter } from 'eslint' 3 | import { createConfigForNuxt } from '../src' 4 | 5 | const cwd = process.cwd() 6 | 7 | function getFlatConfigDigest(configs: Linter.Config[]) { 8 | return configs.map((config) => { 9 | return JSON.parse(JSON.stringify({ 10 | name: config.name, 11 | files: config.files, 12 | ignores: config.ignores, 13 | }).replaceAll(cwd, '')) 14 | }) 15 | } 16 | 17 | describe('flat config composition', () => { 18 | it('empty', async () => { 19 | const configs = await createConfigForNuxt() 20 | 21 | expect(getFlatConfigDigest(configs)) 22 | .toMatchSnapshot() 23 | }) 24 | 25 | it('non-standalone', async () => { 26 | const configs = await createConfigForNuxt({ 27 | features: { 28 | standalone: false, 29 | }, 30 | }) 31 | 32 | expect(getFlatConfigDigest(configs)) 33 | .toMatchSnapshot() 34 | }) 35 | 36 | it('custom src dirs', async () => { 37 | const configs = await createConfigForNuxt({ 38 | dirs: { 39 | src: ['src1', 'src2'], 40 | }, 41 | }) 42 | 43 | expect(getFlatConfigDigest(configs)) 44 | .toMatchSnapshot() 45 | }) 46 | 47 | it('with stylistic', async () => { 48 | const configs = await createConfigForNuxt({ 49 | features: { 50 | stylistic: true, 51 | }, 52 | }) 53 | 54 | expect(getFlatConfigDigest(configs)) 55 | .toMatchSnapshot() 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/import.ts: -------------------------------------------------------------------------------- 1 | import type { Linter } from 'eslint' 2 | import type { NuxtESLintConfigOptions } from '../types' 3 | import { resolveOptions } from '../utils' 4 | 5 | export default async function imports(options: NuxtESLintConfigOptions): Promise { 6 | const resolved = resolveOptions(options) 7 | 8 | if (resolved.features.import === false) { 9 | return [] 10 | } 11 | 12 | const importOptions = resolved.features.import === true 13 | ? {} 14 | : resolved.features.import || {} 15 | 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | const plugin: any = importOptions.package === 'eslint-plugin-import-lite' 18 | ? (await import('eslint-plugin-import-lite')).default 19 | : (await import('eslint-plugin-import-x')).default 20 | 21 | return [ 22 | { 23 | name: 'nuxt/import/rules', 24 | plugins: { 25 | import: plugin, 26 | }, 27 | rules: { 28 | ...(importOptions.package === 'eslint-plugin-import-lite' 29 | ? { 30 | 'import/consistent-type-specifier-style': ['error', 'top-level'], 31 | } 32 | : {}), 33 | 'import/first': 'error', 34 | 'import/no-duplicates': 'error', 35 | 'import/no-mutable-exports': 'error', 36 | 'import/no-named-default': 'error', 37 | 38 | ...resolved.features.stylistic 39 | ? { 40 | 'import/newline-after-import': ['error', { count: 1 }], 41 | } 42 | : {}, 43 | }, 44 | }, 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs-tooling/jsdoc.ts: -------------------------------------------------------------------------------- 1 | import type { Linter } from 'eslint' 2 | import jsdocPlugin from 'eslint-plugin-jsdoc' 3 | import { GLOB_SRC, GLOB_VUE } from '../globs' 4 | import { resolveOptions } from '../utils' 5 | import type { NuxtESLintConfigOptions } from '../types' 6 | 7 | export default function jsdoc(options: NuxtESLintConfigOptions = {}): Linter.Config[] { 8 | const resolved = resolveOptions(options) 9 | 10 | return [ 11 | { 12 | name: 'nuxt/tooling/jsdoc', 13 | files: [GLOB_SRC, GLOB_VUE], 14 | plugins: { 15 | jsdoc: jsdocPlugin, 16 | }, 17 | rules: { 18 | 'jsdoc/check-access': 'warn', 19 | 'jsdoc/check-param-names': 'warn', 20 | 'jsdoc/check-property-names': 'warn', 21 | 'jsdoc/check-types': 'warn', 22 | 'jsdoc/empty-tags': 'warn', 23 | 'jsdoc/implements-on-classes': 'warn', 24 | 'jsdoc/no-defaults': 'warn', 25 | 'jsdoc/no-multi-asterisks': 'warn', 26 | 'jsdoc/require-param-name': 'warn', 27 | 'jsdoc/require-property': 'warn', 28 | 'jsdoc/require-property-description': 'warn', 29 | 'jsdoc/require-property-name': 'warn', 30 | 'jsdoc/require-returns-check': 'warn', 31 | 'jsdoc/require-returns-description': 'warn', 32 | 'jsdoc/require-yields-check': 'warn', 33 | 34 | ...resolved.features.stylistic 35 | ? { 36 | 'jsdoc/check-alignment': 'warn', 37 | 'jsdoc/multiline-blocks': 'warn', 38 | } 39 | : {}, 40 | }, 41 | }, 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/disables.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'pathe' 2 | import type { Linter } from 'eslint' 3 | import { GLOB_EXTS } from '../constants' 4 | import type { NuxtESLintConfigOptions } from '../types' 5 | import { resolveOptions } from '../utils' 6 | 7 | export default function disables(options: NuxtESLintConfigOptions): Linter.Config[] { 8 | const resolved = resolveOptions(options) 9 | const dirs = resolved.dirs 10 | const nestedGlobPattern = `**/*.${GLOB_EXTS}` 11 | 12 | const fileRoutes = [...new Set([ 13 | // These files must have one-word names as they have a special meaning in Nuxt. 14 | ...dirs.src.flatMap(layersDir => [ 15 | join(layersDir, `app.${GLOB_EXTS}`), 16 | join(layersDir, `error.${GLOB_EXTS}`), 17 | ]) || [], 18 | 19 | // Layouts and pages are not used directly by users so they can have one-word names. 20 | ...(dirs.layouts.map(layoutsDir => join(layoutsDir, nestedGlobPattern)) || []), 21 | ...(dirs.pages.map(pagesDir => join(pagesDir, nestedGlobPattern)) || []), 22 | 23 | // These files should have multiple words in their names as they are within subdirectories. 24 | ...(dirs.components.map(componentsDir => join(componentsDir, '*', nestedGlobPattern)) || []), 25 | // Prefixed components can have one-word names in file 26 | ...(dirs.componentsPrefixed.map(componentsDir => join(componentsDir, nestedGlobPattern)) || []), 27 | ])].sort() 28 | 29 | const configs: Linter.Config[] = [] 30 | 31 | if (fileRoutes.length) { 32 | configs.push({ 33 | name: 'nuxt/disables/routes', 34 | files: fileRoutes, 35 | rules: { 36 | 'vue/multi-word-component-names': 'off', 37 | }, 38 | }) 39 | } 40 | 41 | return configs 42 | } 43 | -------------------------------------------------------------------------------- /packages/eslint-config/src/globs.ts: -------------------------------------------------------------------------------- 1 | export const GLOB_SRC_EXT = '?([cm])[jt]s?(x)' 2 | export const GLOB_SRC = '**/*.?([cm])[jt]s?(x)' 3 | 4 | export const GLOB_JS = '**/*.?([cm])js' 5 | export const GLOB_JSX = '**/*.?([cm])jsx' 6 | 7 | export const GLOB_TS = '**/*.?([cm])ts' 8 | export const GLOB_TSX = '**/*.?([cm])tsx' 9 | 10 | export const GLOB_STYLE = '**/*.{c,le,sc}ss' 11 | export const GLOB_CSS = '**/*.css' 12 | export const GLOB_POSTCSS = '**/*.{p,post}css' 13 | export const GLOB_LESS = '**/*.less' 14 | export const GLOB_SCSS = '**/*.scss' 15 | 16 | export const GLOB_JSON = '**/*.json' 17 | export const GLOB_JSON5 = '**/*.json5' 18 | export const GLOB_JSONC = '**/*.jsonc' 19 | 20 | export const GLOB_MARKDOWN = '**/*.md' 21 | export const GLOB_MARKDOWN_IN_MARKDOWN = '**/*.md/*.md' 22 | export const GLOB_SVELTE = '**/*.svelte' 23 | export const GLOB_VUE = '**/*.vue' 24 | export const GLOB_YAML = '**/*.y?(a)ml' 25 | export const GLOB_TOML = '**/*.toml' 26 | export const GLOB_XML = '**/*.xml' 27 | export const GLOB_SVG = '**/*.svg' 28 | export const GLOB_HTML = '**/*.htm?(l)' 29 | export const GLOB_ASTRO = '**/*.astro' 30 | export const GLOB_ASTRO_TS = '**/*.astro/*.ts' 31 | export const GLOB_GRAPHQL = '**/*.{g,graph}ql' 32 | 33 | export const GLOB_MARKDOWN_CODE = `${GLOB_MARKDOWN}/${GLOB_SRC}` 34 | 35 | export const GLOB_TESTS = [ 36 | `**/__tests__/**/*.${GLOB_SRC_EXT}`, 37 | `**/*.spec.${GLOB_SRC_EXT}`, 38 | `**/*.test.${GLOB_SRC_EXT}`, 39 | `**/*.bench.${GLOB_SRC_EXT}`, 40 | `**/*.benchmark.${GLOB_SRC_EXT}`, 41 | ] 42 | 43 | export const GLOB_ALL_SRC = [ 44 | GLOB_SRC, 45 | GLOB_STYLE, 46 | GLOB_JSON, 47 | GLOB_JSON5, 48 | GLOB_MARKDOWN, 49 | GLOB_SVELTE, 50 | GLOB_VUE, 51 | GLOB_YAML, 52 | GLOB_XML, 53 | GLOB_HTML, 54 | ] 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "packageManager": "pnpm@10.25.0", 5 | "version": "1.12.1", 6 | "scripts": { 7 | "build": "pnpm run -r build", 8 | "stub": "pnpm run -r stub", 9 | "release": "bumpp \"package.json\" \"packages/**/package.json\"", 10 | "test": "vitest run", 11 | "play": "pnpm -C playground run play:dev", 12 | "lint": "eslint .", 13 | "lint:fix": "eslint . --fix", 14 | "lint:play": "pnpm -C playground run lint", 15 | "docs": "pnpm -C docs run docs:dev", 16 | "typecheck": "tsc --noEmit" 17 | }, 18 | "devDependencies": { 19 | "@iconify-json/catppuccin": "catalog:docs", 20 | "@iconify-json/ph": "catalog:docs", 21 | "@iconify-json/simple-icons": "catalog:docs", 22 | "@nuxt/content": "catalog:docs", 23 | "@nuxt/devtools": "catalog:docs", 24 | "@nuxt/eslint-config": "workspace:*", 25 | "@nuxt/eslint-plugin": "workspace:*", 26 | "@nuxt/fonts": "catalog:docs", 27 | "@nuxt/image": "catalog:docs", 28 | "@nuxt/test-utils": "catalog:docs", 29 | "@nuxt/ui-pro": "catalog:docs", 30 | "@nuxthq/studio": "catalog:docs", 31 | "@nuxtjs/plausible": "catalog:docs", 32 | "@types/node": "catalog:types", 33 | "@vueuse/core": "catalog:docs", 34 | "@vueuse/nuxt": "catalog:docs", 35 | "bumpp": "catalog:dev", 36 | "eslint": "catalog:dev", 37 | "eslint-plugin-format": "catalog:prod", 38 | "eslint-plugin-pnpm": "catalog:dev", 39 | "jsonc-eslint-parser": "catalog:dev", 40 | "nuxt": "catalog:dev", 41 | "nuxt-og-image": "catalog:docs", 42 | "taze": "catalog:dev", 43 | "typescript": "catalog:dev", 44 | "vitest": "catalog:dev", 45 | "vue": "catalog:prod" 46 | }, 47 | "resolutions": { 48 | "@nuxt/ui": "2.15.2" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: "🚀 Feature request" 2 | description: Suggest a feature that will improve Nuxt ESLint 3 | labels: ["pending triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for taking the time to fill out this feature request! 9 | 10 | Please carefully read the contribution docs before suggesting a new feature 11 | 👉 https://nuxt.com/docs/community/contribution/#creating-an-issue 12 | - type: textarea 13 | id: feature-description 14 | attributes: 15 | label: Describe the feature 16 | description: A clear and concise description of what you think would be a helpful addition to Nuxt, including the possible use cases and alternatives you have considered. If you have a working prototype or module that implements it, please include a link. 17 | placeholder: Feature description 18 | validations: 19 | required: true 20 | - type: checkboxes 21 | id: additional-info 22 | attributes: 23 | label: Additional information 24 | description: Additional information that helps us decide how to proceed. 25 | options: 26 | - label: Would you be willing to help implement this feature? 27 | - label: Could this feature be implemented as a module? 28 | - type: checkboxes 29 | id: required-info 30 | attributes: 31 | label: Final checks 32 | description: Before submitting, please make sure you do the following 33 | options: 34 | - label: Read the [contribution guide](https://nuxt.com/docs/community/contribution). 35 | required: true 36 | - label: Check existing [discussions](https://github.com/nuxt/eslint/discussions) and [issues](https://github.com/nuxt/eslint/issues). 37 | required: true 38 | -------------------------------------------------------------------------------- /packages/eslint-plugin/src/rules/no-nuxt-config-test-key/index.ts: -------------------------------------------------------------------------------- 1 | import type { TSESTree as Tree } from '@typescript-eslint/utils' 2 | import { createRule } from '../utils' 3 | 4 | type MessageIds = 'default' 5 | 6 | type Options = [] 7 | 8 | export const rule = createRule({ 9 | name: 'no-nuxt-config-test-key', 10 | meta: { 11 | type: 'problem', 12 | docs: { 13 | description: 'Disallow setting `test` key in Nuxt config', 14 | }, 15 | schema: [], 16 | messages: { 17 | default: 'Do not set `test` key in Nuxt config. The test environment is automatically detected.', 18 | }, 19 | }, 20 | defaultOptions: [], 21 | create(context) { 22 | return { 23 | ExportDefaultDeclaration(node) { 24 | let object: Tree.ObjectExpression | undefined 25 | if (node.declaration.type === 'ObjectExpression') { 26 | object = node.declaration 27 | } 28 | else if (node.declaration.type === 'CallExpression' && node.declaration.arguments[0]?.type === 'ObjectExpression') { 29 | object = node.declaration.arguments[0] 30 | } 31 | if (!object) { 32 | return 33 | } 34 | 35 | for (const prop of object.properties) { 36 | if ( 37 | prop.type === 'Property' 38 | && prop.key.type === 'Identifier' 39 | && prop.key.name === 'test' 40 | && ( 41 | (prop.value.type === 'Literal' && typeof prop.value.value === 'boolean') 42 | || (prop.value.type === 'Identifier' && (prop.value.name === 'true' || prop.value.name === 'false')) 43 | ) 44 | ) { 45 | context.report({ 46 | node: prop, 47 | messageId: 'default', 48 | }) 49 | } 50 | } 51 | }, 52 | } 53 | }, 54 | }) 55 | -------------------------------------------------------------------------------- /packages/module/src/modules/config/index.ts: -------------------------------------------------------------------------------- 1 | import { dirname, join } from 'node:path' 2 | import fs from 'node:fs/promises' 3 | import type { Nuxt } from '@nuxt/schema' 4 | import type { ESLintConfigGenAddon } from '../../types' 5 | import type { ModuleOptions } from '../../module' 6 | import { createAddonGlobals } from './addons/globals' 7 | import { setupDevToolsIntegration } from './devtools' 8 | import { initRootESLintConfig } from './init' 9 | import { generateESLintConfig } from './generate' 10 | 11 | export async function setupConfigGen(options: ModuleOptions, nuxt: Nuxt) { 12 | const { 13 | autoInit = true, 14 | } = typeof options.config !== 'boolean' ? options.config || {} : {} 15 | 16 | const defaultAddons = [ 17 | createAddonGlobals(nuxt), 18 | ] 19 | 20 | nuxt.hook('prepare:types', ({ declarations, nodeReferences }) => { 21 | declarations.push('/// ') 22 | if (nodeReferences) 23 | nodeReferences.push({ path: join(nuxt.options.buildDir, 'eslint-typegen.d.ts') }) 24 | }) 25 | 26 | let _configFile: string = undefined! 27 | 28 | async function writeConfigFile() { 29 | const addons: ESLintConfigGenAddon[] = [ 30 | ...defaultAddons, 31 | ] 32 | await nuxt.callHook('eslint:config:addons', addons) 33 | const { code, codeDts, configFile } = await generateESLintConfig(options, nuxt, addons) 34 | await fs.mkdir(dirname(configFile), { recursive: true }) 35 | await fs.writeFile(configFile, code, 'utf-8') 36 | await fs.writeFile(configFile.replace(/\.mjs$/, '.d.mts'), codeDts, 'utf-8') 37 | _configFile = configFile 38 | } 39 | 40 | setupDevToolsIntegration(options, nuxt) 41 | 42 | await writeConfigFile() 43 | nuxt.hook('builder:generateApp', () => { 44 | writeConfigFile() 45 | }) 46 | 47 | if (autoInit) { 48 | await initRootESLintConfig(nuxt, _configFile) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /docs/content/3.legacy/1.eslint-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: '@nuxtjs/eslint-config' 3 | --- 4 | 5 | Opinionated [ESlint](https://eslint.org/) configuration used internally for Nuxt 2 projects. 6 | 7 | :::callout{icon="i-ph-lightbulb-duotone"} 8 | This package is for Nuxt 2. For Nuxt 3 projects, use [`@nuxt/eslint`](/packages/module) instead.
9 | ::: 10 | 11 | :::callout{icon="i-ph-warning-circle-duotone" color="amber"} 12 | This package is in maintainance mode and no longer have active developments. 13 | ::: 14 | 15 | ::read-more 16 | --- 17 | to: https://github.com/nuxt/eslint/tree/main/packages-legacy/nuxt2-eslint-config 18 | color: gray 19 | icon: i-simple-icons-github 20 | --- 21 | Source code on GitHub 22 | :: 23 | 24 | ### Usage 25 | 26 | 1. Add this package to your `devDependencies` 27 | 28 | ```bash 29 | $ npm i -D @nuxtjs/eslint-config 30 | # or 31 | $ yarn add -D @nuxtjs/eslint-config 32 | ``` 33 | 34 | 2. Install `eslint` if not already present locally or globally 35 | 36 | ```bash 37 | $ npm i -D eslint 38 | # or 39 | $ yarn add -D eslint 40 | ``` 41 | 42 | 3. Create a `.eslintrc` file 43 | 44 | 4. Extend our config (you can use just the scope name as ESLint will assume the `eslint-config` prefix): 45 | 46 | ```json 47 | { 48 | "extends": ["@nuxtjs/eslint-config"] 49 | } 50 | ``` 51 | 52 | ### TypeScript 53 | 54 | If you're using TypeScript, follow [Usage](#usage) section by replacing `@nuxtjs/eslint-config` by `@nuxtjs/eslint-config-typescript`. 55 | 56 | And in your `.eslintrc` all you need is : 57 | 58 | ```json 59 | { 60 | "extends": ["@nuxtjs/eslint-config-typescript"] 61 | } 62 | ``` 63 | 64 | You can then edit/override same rules as you could with `@nuxtjs/eslint-config` but also TypeScript rules. 65 | You can find the list of supported TypeScript rules [here](https://typescript-eslint.io/rules/#supported-rules) and you can read more about Nuxt's TypeScript support [in the docs](https://nuxt.com/docs/guide/concepts/typescript). 66 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs-tooling/unicorn.ts: -------------------------------------------------------------------------------- 1 | import pluginUnicorn from 'eslint-plugin-unicorn' 2 | import type { Linter } from 'eslint' 3 | import { GLOB_SRC, GLOB_VUE } from '../globs' 4 | 5 | export default function unicorn(): Linter.Config[] { 6 | return [ 7 | { 8 | name: 'nuxt/tooling/unicorn', 9 | files: [GLOB_SRC, GLOB_VUE], 10 | plugins: { 11 | unicorn: pluginUnicorn, 12 | }, 13 | rules: { 14 | // Pass error message when throwing errors 15 | 'unicorn/error-message': 'error', 16 | // Uppercase regex escapes 17 | 'unicorn/escape-case': 'error', 18 | // Array.isArray instead of instanceof 19 | 'unicorn/no-instanceof-array': 'error', 20 | // Ban `new Array` as `Array` constructor's params are ambiguous 21 | 'unicorn/no-new-array': 'error', 22 | // Prevent deprecated `new Buffer()` 23 | 'unicorn/no-new-buffer': 'error', 24 | // Lowercase number formatting for octal, hex, binary (0x1'error' instead of 0X1'error') 25 | 'unicorn/number-literal-case': 'error', 26 | // textContent instead of innerText 27 | 'unicorn/prefer-dom-node-text-content': 'error', 28 | // includes over indexOf when checking for existence 29 | 'unicorn/prefer-includes': 'error', 30 | // Prefer using the node: protocol 31 | 'unicorn/prefer-node-protocol': 'error', 32 | // Prefer using number properties like `Number.isNaN` rather than `isNaN` 33 | 'unicorn/prefer-number-properties': 'error', 34 | // String methods startsWith/endsWith instead of more complicated stuff 35 | 'unicorn/prefer-string-starts-ends-with': 'error', 36 | // Enforce throwing type error when throwing error while checking typeof 37 | 'unicorn/prefer-type-error': 'error', 38 | // Use new when throwing error 39 | 'unicorn/throw-new-error': 'error', 40 | }, 41 | }, 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /packages/eslint-plugin/src/rules/nuxt-config-keys-order/keys.ts: -------------------------------------------------------------------------------- 1 | export const OFFICIAL_MODULES = { 2 | client: [ 3 | 'site', // SEO module 4 | 'colorMode', 5 | 'content', 6 | 'mdc', 7 | 'ui', 8 | ], 9 | 10 | server: [ 11 | 'hub', 12 | ], 13 | } 14 | 15 | export const ORDER_KEYS = [ 16 | // Ids 17 | 'appId', 18 | 'buildId', 19 | 20 | // Extends 21 | 'extends', 22 | 'theme', 23 | 24 | // Extensions 25 | 'modules', 26 | 'plugins', 27 | 28 | // Env ($production, $development, $test) 29 | /^\$/, 30 | 31 | // Nuxt Core Features 32 | 'ssr', 33 | 'pages', 34 | 'components', 35 | 'imports', 36 | 'devtools', 37 | 38 | // Client-side Integrations 39 | 'app', 40 | 'css', 41 | 'vue', 42 | 'router', 43 | 'unhead', 44 | ...OFFICIAL_MODULES.client, 45 | 'spaLoadingTemplate', 46 | 47 | // Runtime Configs 48 | 'appConfig', 49 | 'runtimeConfig', 50 | 51 | // Dirs 52 | 'dir', 53 | 'rootDir', 54 | 'srcDir', 55 | 'appDir', 56 | 'workspaceDir', 57 | 'serverDir', 58 | 'buildDir', 59 | 'modulesDir', 60 | 'analyzeDir', 61 | 62 | // Resultions 63 | 'alias', 64 | 'extensions', 65 | 'ignore', 66 | 'ignoreOptions', 67 | 'ignorePrefix', 68 | 69 | // Build Pipeline Configs 70 | 'builder', 71 | 'build', 72 | 'generate', 73 | 'routeRules', 74 | 'sourcemap', 75 | 'optimization', 76 | 77 | // Development 78 | 'dev', 79 | 'devServer', 80 | 'watch', 81 | 'watchers', 82 | 83 | // Feature flags 84 | 'future', 85 | 'features', 86 | 'experimental', 87 | 'compatibilityDate', 88 | 89 | // Nitro 90 | 'nitro', 91 | ...OFFICIAL_MODULES.server, 92 | 'serverHandlers', 93 | 'devServerHandlers', 94 | 95 | // Tooling Integrations 96 | 'vite', 97 | 'webpack', 98 | 'typescript', 99 | 'postcss', 100 | 101 | // Other Integrations 102 | 'test', 103 | 'telemetry', 104 | 105 | // Logging 106 | 'debug', 107 | 'logLevel', 108 | 109 | // Hooks 110 | 'hooks', 111 | ] 112 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/nuxt.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'pathe' 2 | import nuxtPlugin from '@nuxt/eslint-plugin' 3 | import type { Linter } from 'eslint' 4 | import type { NuxtESLintConfigOptions } from '../types' 5 | import { GLOB_EXTS } from '../constants' 6 | import { resolveOptions } from '../utils' 7 | 8 | export default function nuxt(options: NuxtESLintConfigOptions): Linter.Config[] { 9 | const resolved = resolveOptions(options) 10 | const dirs = resolved.dirs 11 | 12 | const fileSingleRoot = [ 13 | ...(dirs.layouts?.map(layoutsDir => join(layoutsDir, `**/*.${GLOB_EXTS}`)) || []), 14 | ...(dirs.pages?.map(pagesDir => join(pagesDir, `**/*.${GLOB_EXTS}`)) || []), 15 | ...(dirs.components?.map(componentsDir => join(componentsDir, `**/*.server.${GLOB_EXTS}`)) || []), 16 | ].sort() 17 | 18 | const { 19 | sortConfigKeys = !!(options.features?.stylistic), 20 | } = options.features?.nuxt || {} 21 | 22 | const configs: Linter.Config[] = [] 23 | 24 | configs.push({ 25 | name: 'nuxt/setup', 26 | plugins: { 27 | nuxt: nuxtPlugin, 28 | }, 29 | languageOptions: { 30 | globals: { 31 | // Nuxt's runtime globals 32 | $fetch: 'readonly', 33 | }, 34 | }, 35 | }) 36 | 37 | if (fileSingleRoot.length) 38 | configs.push({ 39 | name: 'nuxt/vue/single-root', 40 | files: fileSingleRoot, 41 | rules: { 42 | 'vue/no-multiple-template-root': 'error', 43 | }, 44 | }) 45 | 46 | configs.push({ 47 | name: 'nuxt/rules', 48 | rules: { 49 | 'nuxt/prefer-import-meta': 'error', 50 | }, 51 | }) 52 | 53 | configs.push({ 54 | name: 'nuxt/nuxt-config', 55 | files: [ 56 | '**/.config/nuxt.?([cm])[jt]s?(x)', 57 | '**/nuxt.config.?([cm])[jt]s?(x)', 58 | ], 59 | rules: { 60 | 'nuxt/no-nuxt-config-test-key': 'error', 61 | }, 62 | }) 63 | 64 | if (sortConfigKeys) { 65 | configs.push({ 66 | name: 'nuxt/sort-config', 67 | files: [ 68 | '**/.config/nuxt.?([cm])[jt]s?(x)', 69 | '**/nuxt.config.?([cm])[jt]s?(x)', 70 | ], 71 | rules: { 72 | 'nuxt/nuxt-config-keys-order': 'error', 73 | }, 74 | }) 75 | } 76 | 77 | return configs 78 | } 79 | -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxt/eslint-config", 3 | "version": "1.12.1", 4 | "description": "ESLint config for Nuxt projects", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/nuxt/eslint.git", 8 | "directory": "packages/eslint-config" 9 | }, 10 | "license": "MIT", 11 | "main": "./dist/index.mjs", 12 | "module": "./dist/index.mjs", 13 | "types": "./dist/index.d.ts", 14 | "exports": { 15 | ".": "./dist/index.mjs", 16 | "./flat": "./dist/flat.mjs" 17 | }, 18 | "typesVersions": { 19 | "*": { 20 | "flat": [ 21 | "./dist/flat.d.ts" 22 | ], 23 | "*": [ 24 | "./dist/*", 25 | "./*" 26 | ] 27 | } 28 | }, 29 | "files": [ 30 | "dist" 31 | ], 32 | "scripts": { 33 | "build": "unbuild", 34 | "stub": "unbuild --stub", 35 | "prepack": "pnpm run build" 36 | }, 37 | "peerDependencies": { 38 | "eslint": "^9.0.0", 39 | "eslint-plugin-format": "*" 40 | }, 41 | "peerDependenciesMeta": { 42 | "eslint-plugin-format": { 43 | "optional": true 44 | } 45 | }, 46 | "dependencies": { 47 | "@antfu/install-pkg": "catalog:prod", 48 | "@clack/prompts": "catalog:prod", 49 | "@eslint/js": "catalog:prod", 50 | "@nuxt/eslint-plugin": "workspace:*", 51 | "@stylistic/eslint-plugin": "catalog:prod", 52 | "@typescript-eslint/eslint-plugin": "catalog:prod", 53 | "@typescript-eslint/parser": "catalog:prod", 54 | "eslint-config-flat-gitignore": "catalog:prod", 55 | "eslint-flat-config-utils": "catalog:prod", 56 | "eslint-merge-processors": "catalog:prod", 57 | "eslint-plugin-import-lite": "catalog:prod", 58 | "eslint-plugin-import-x": "catalog:prod", 59 | "eslint-plugin-jsdoc": "catalog:prod", 60 | "eslint-plugin-regexp": "catalog:prod", 61 | "eslint-plugin-unicorn": "catalog:prod", 62 | "eslint-plugin-vue": "catalog:prod", 63 | "eslint-processor-vue-blocks": "catalog:prod", 64 | "globals": "catalog:prod", 65 | "local-pkg": "catalog:prod", 66 | "pathe": "catalog:prod", 67 | "vue-eslint-parser": "catalog:prod" 68 | }, 69 | "devDependencies": { 70 | "eslint": "catalog:dev", 71 | "typescript": "catalog:dev" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/module/src/modules/config/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Nuxt } from '@nuxt/schema' 2 | import { relative, resolve } from 'pathe' 3 | import type { NuxtESLintConfigOptions } from '@nuxt/eslint-config/flat' 4 | import type { ModuleOptions } from '../../types' 5 | 6 | export function getDirs(nuxt: Nuxt, options: ModuleOptions): NuxtESLintConfigOptions['dirs'] { 7 | const rootDir = (typeof options.config === 'object' && options.config.rootDir) || nuxt.options.rootDir 8 | 9 | const dirs: Required = { 10 | pages: [], 11 | composables: [], 12 | components: [], 13 | componentsPrefixed: [], 14 | layouts: [], 15 | plugins: [], 16 | middleware: [], 17 | modules: [], 18 | servers: [], 19 | root: [], 20 | src: [], 21 | } 22 | 23 | for (const layer of nuxt.options._layers) { 24 | const r = (t: string) => relative(rootDir, resolve(layer.config.srcDir, t.replace(/^~[/\\]/, ''))) 25 | 26 | dirs.src.push(r('')) 27 | dirs.pages.push(r(nuxt.options.dir.pages || 'pages')) 28 | dirs.layouts.push(r(nuxt.options.dir.layouts || 'layouts')) 29 | dirs.plugins.push(r(nuxt.options.dir.plugins || 'plugins')) 30 | dirs.middleware.push(r(nuxt.options.dir.middleware || 'middleware')) 31 | dirs.modules.push(r(nuxt.options.dir.modules || 'modules')) 32 | 33 | dirs.composables.push(r('composables')) 34 | dirs.composables.push(r('utils')) 35 | for (const dir of (layer.config.imports?.dirs ?? [])) { 36 | if (dir) 37 | dirs.composables.push(r(dir)) 38 | } 39 | 40 | if (layer.config.components && layer.config.components !== true) { 41 | const options = Array.isArray(layer.config.components) 42 | ? { dirs: layer.config.components } 43 | : layer.config.components 44 | for (const dir of options.dirs || []) { 45 | if (typeof dir === 'string') 46 | dirs.components.push(r(dir)) 47 | else if (dir && 'path' in dir && typeof dir.path === 'string') { 48 | dirs.components.push(r(dir.path)) 49 | if (dir.prefix) 50 | dirs.componentsPrefixed.push(r(dir.path)) 51 | } 52 | } 53 | } 54 | else { 55 | dirs.components.push(r('components')) 56 | } 57 | } 58 | 59 | return dirs 60 | } 61 | -------------------------------------------------------------------------------- /packages/module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxt/eslint", 3 | "type": "module", 4 | "version": "1.12.1", 5 | "description": "Generate ESLint config from current Nuxt settings", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/nuxt/eslint.git", 10 | "directory": "packages/module" 11 | }, 12 | "exports": { 13 | ".": "./dist/module.mjs" 14 | }, 15 | "main": "./dist/module.mjs", 16 | "module": "./dist/module.mjs", 17 | "types": "./dist/types.d.mts", 18 | "files": [ 19 | "dist" 20 | ], 21 | "scripts": { 22 | "build": "nuxt-module-build build", 23 | "prepare": "nuxt-module-build prepare", 24 | "prepack": "pnpm run build", 25 | "dev": "nuxi dev playground", 26 | "dev:build": "nuxi build playground", 27 | "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground", 28 | "lint": "eslint .", 29 | "lint:play": "cd playground && eslint .", 30 | "test": "vitest run", 31 | "test:watch": "vitest watch" 32 | }, 33 | "peerDependencies": { 34 | "eslint": "^9.0.0", 35 | "eslint-webpack-plugin": "^4.1.0", 36 | "vite-plugin-eslint2": "^5.0.0" 37 | }, 38 | "peerDependenciesMeta": { 39 | "eslint-webpack-plugin": { 40 | "optional": true 41 | }, 42 | "vite-plugin-eslint2": { 43 | "optional": true 44 | } 45 | }, 46 | "dependencies": { 47 | "@eslint/config-inspector": "catalog:prod", 48 | "@nuxt/devtools-kit": "catalog:prod", 49 | "@nuxt/eslint-config": "workspace:*", 50 | "@nuxt/eslint-plugin": "workspace:*", 51 | "@nuxt/kit": "catalog:prod", 52 | "chokidar": "catalog:prod", 53 | "eslint-flat-config-utils": "catalog:prod", 54 | "eslint-typegen": "catalog:prod", 55 | "find-up": "catalog:prod", 56 | "get-port-please": "catalog:prod", 57 | "mlly": "catalog:prod", 58 | "pathe": "catalog:prod", 59 | "unimport": "catalog:prod" 60 | }, 61 | "devDependencies": { 62 | "@nuxt/module-builder": "catalog:bundling", 63 | "@nuxt/schema": "catalog:types", 64 | "@typescript-eslint/scope-manager": "catalog:prod", 65 | "eslint-webpack-plugin": "catalog:prod", 66 | "nuxt": "catalog:dev", 67 | "vite-plugin-eslint2": "catalog:prod" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - playground 4 | - docs 5 | 6 | catalogs: 7 | prod: 8 | '@antfu/install-pkg': ^1.1.0 9 | '@clack/prompts': ^0.11.0 10 | '@eslint/config-inspector': ^1.4.2 11 | '@eslint/js': ^9.39.1 12 | '@nuxt/devtools-kit': ^3.1.1 13 | '@nuxt/kit': ^4.2.2 14 | '@stylistic/eslint-plugin': ^5.6.1 15 | '@typescript-eslint/eslint-plugin': ^8.49.0 16 | '@typescript-eslint/parser': ^8.49.0 17 | '@typescript-eslint/scope-manager': ^8.49.0 18 | '@typescript-eslint/types': ^8.49.0 19 | '@typescript-eslint/utils': ^8.49.0 20 | chokidar: ^5.0.0 21 | eslint-config-flat-gitignore: ^2.1.0 22 | eslint-flat-config-utils: ^2.1.4 23 | eslint-merge-processors: ^2.0.0 24 | eslint-plugin-format: ^1.1.0 25 | eslint-plugin-import-lite: ^0.3.0 26 | eslint-plugin-import-x: ^4.16.1 27 | eslint-plugin-jsdoc: ^61.5.0 28 | eslint-plugin-n: ^17.23.1 29 | eslint-plugin-promise: ^7.2.1 30 | eslint-plugin-regexp: ^2.10.0 31 | eslint-plugin-unicorn: ^62.0.0 32 | eslint-plugin-vue: ^10.6.2 33 | eslint-processor-vue-blocks: ^2.0.0 34 | eslint-typegen: ^2.3.0 35 | eslint-webpack-plugin: ^5.0.2 36 | find-up: ^8.0.0 37 | get-port-please: ^3.2.0 38 | globals: ^16.5.0 39 | local-pkg: ^1.1.2 40 | mlly: ^1.8.0 41 | pathe: ^2.0.3 42 | unimport: ^5.5.0 43 | vite-plugin-eslint2: ^5.0.4 44 | vue-eslint-parser: ^10.2.0 45 | vue: ^3.5.25 46 | bundling: 47 | '@nuxt/module-builder': ^1.0.2 48 | docs: 49 | '@iconify-json/catppuccin': ^1.2.17 50 | '@iconify-json/ph': ^1.2.2 51 | '@iconify-json/simple-icons': ^1.2.62 52 | '@nuxt/content': ^3.9.0 53 | '@nuxt/devtools': ^3.1.1 54 | '@nuxt/fonts': ^0.12.1 55 | '@nuxt/image': ^2.0.0 56 | '@nuxt/test-utils': ^3.21.0 57 | '@nuxt/ui-pro': ^3.3.7 58 | '@nuxthq/studio': ^2.2.1 59 | '@nuxtjs/plausible': ^2.0.1 60 | '@vueuse/core': ^14.1.0 61 | '@vueuse/nuxt': ^14.1.0 62 | nuxt-og-image: ^5.1.12 63 | types: 64 | '@types/node': ^24.10.2 65 | '@nuxt/schema': ^4.2.2 66 | dev: 67 | bumpp: ^10.3.2 68 | eslint-plugin-pnpm: ^1.4.2 69 | eslint: ^9.39.1 70 | jsonc-eslint-parser: ^2.4.2 71 | nuxt: ^4.2.2 72 | taze: ^19.9.2 73 | typescript: ^5.9.3 74 | vitest: ^4.0.15 75 | 76 | onlyBuiltDependencies: 77 | - esbuild 78 | - sharp 79 | - unrs-resolver 80 | - vue-demi 81 | -------------------------------------------------------------------------------- /docs/content/2.guide/0.faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FAQ 3 | description: Frequently asked questions about ESLint integration in Nuxt 4 | --- 5 | 6 | This project is composed of multiple packages for different level of ESLint integration. 7 | 8 | ### What to use? 9 | 10 | For new projects, we highly recommend using the [ESLint Module](/packages/module) which provides a project-aware ESLint flat config generation and is more future-proof. 11 | 12 | If you still use the legacy `.eslintrc` format, you can use the [ESLint Config](/packages/config) package to manually configure your ESLint settings. We recommend you to migrate to the flat config format if possible. 13 | 14 | If you are maintaining your custom ESLint config and want a low-level setup, you can use the [ESLint Plugin](/packages/plugin) package directly to enable some Nuxt-specific rules in your config. 15 | 16 | ### Package Disambiguation 17 | 18 | Due to the historical reasons, we have quite a few packages for different ESLint integrations. 19 | 20 | Here's a table to help you understand the differences: 21 | 22 |
23 | 24 | | Package | Tags | 25 | | --- | --- | 26 | | [`@nuxt/eslint`](/packages/module)
All-in-one ESLint module for Nuxt 3 | [`nuxt3`]{.badge-nuxt3} [`flat-config`]{.badge-flat} [`recommended`]{.badge-recommend} | 27 | | [`@nuxt/eslint-config`](/packages/config)
Shared config for Nuxt 3, for both flat config and legacy config.
Unopinionated but customizable. | [`nuxt3`]{.badge-nuxt3} [`flat-config`]{.badge-flat} [`legacy-config`]{.badge-legacy} | 28 | | [`@nuxt/eslint-plugin`](/packages/plugin)
Low-level ESLint plugin for Nuxt 3. | [`nuxt3`]{.badge-nuxt3} | 29 | | Legacy packages: | | 30 | | [`@nuxtjs/eslint-module`](https://github.com/nuxt-modules/eslint)
Runs ESLint check along side the dev server.
Now merged into the `@nuxt/eslint` module. | [`nuxt3`]{.badge-nuxt3} [`nuxt2`]{.badge-nuxt2} [`deprecated`]{.badge-legacy} | 31 | | [`@nuxtjs/eslint-config`](/legacy/eslint-config) (note the `@nuxtjs` scope)
Shared config for Nuxt 2, opinionated with stylistic rules.
Maintainance mode, no longer under active development. | [`nuxt2`]{.badge-nuxt2} [`legacy-config`]{.badge-legacy} | 32 | | [`@nuxtjs/eslint-plugin-typescript`](/legacy/eslint-config-ts) (note the `@nuxtjs` scope)
TypeScript integrations for `@nuxtjs/eslint-config`.
Maintainance mode, no longer under active development. | [`nuxt2`]{.badge-nuxt2} [`legacy-config`]{.badge-legacy} | 33 | | [`eslint-plugin-nuxt`](https://github.com/nuxt/eslint-plugin-nuxt)
ESLint plugin for Nuxt 2.
It has been replaced by [`@nuxt/eslint-plugin`](/packages/plugin) for Nuxt 3 | [`nuxt2`]{.badge-nuxt2} [`deprecated`]{.badge-legacy} | 34 | 35 |
36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41E Bug report" 2 | description: Create a report to help us improve Nuxt ESLint 3 | labels: ["pending triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Please carefully read the contribution docs before creating a bug report 9 | 👉 https://nuxt.com/docs/community/reporting-bugs 10 | - type: textarea 11 | id: bug-env 12 | attributes: 13 | label: Environment 14 | description: You can use `npx nuxi info` to fill this section 15 | placeholder: Environment 16 | validations: 17 | required: true 18 | - type: dropdown 19 | id: package 20 | attributes: 21 | label: Package 22 | description: | 23 | Please select the package that you are experiencing the issue with. 24 | Please make sure you are using the **latest versions** before reporting. 25 | 26 | You can refer to [this page](https://eslint.nuxt.com/guide/faq#packages-disambiguation) to understand the difference between the packages. 27 | options: 28 | - '@nuxt/eslint' 29 | - '@nuxt/eslint-module' 30 | - '@nuxt/eslint-plugin' 31 | - '@nuxtjs/eslint-config (for Nuxt 2, deprecated)' 32 | - '@nuxtjs/eslint-config-typescript (for Nuxt 2, deprecated)' 33 | - '@nuxtjs/eslint-module (merged to `@nuxt/eslint` now, deprecated)' 34 | - type: textarea 35 | id: reproduction 36 | attributes: 37 | label: Reproduction 38 | description: Please provide a link to a repo that can reproduce the problem you ran into. A [**minimal reproduction**](https://nuxt.com/docs/community/reporting-bugs#create-a-minimal-reproduction) is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided we might close it. 39 | placeholder: Reproduction 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: bug-description 44 | attributes: 45 | label: Describe the bug 46 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 47 | placeholder: Bug description 48 | validations: 49 | required: true 50 | - type: textarea 51 | id: additional 52 | attributes: 53 | label: Additional context 54 | description: If applicable, add any other context about the problem here 55 | - type: textarea 56 | id: logs 57 | attributes: 58 | label: Logs 59 | description: | 60 | Optional if provided reproduction. Please try not to insert an image but copy paste the log text. 61 | render: shell-script 62 | -------------------------------------------------------------------------------- /docs/app.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 116 | -------------------------------------------------------------------------------- /packages/module/src/modules/config/devtools.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import { resolvePath } from 'mlly' 3 | import type { Nuxt } from '@nuxt/schema' 4 | import { join, dirname } from 'pathe' 5 | import { getPort } from 'get-port-please' 6 | import type { ModuleOptions } from '../../types' 7 | 8 | export async function setupDevToolsIntegration( 9 | options: ModuleOptions, 10 | nuxt: Nuxt, 11 | ) { 12 | const { 13 | enabled = 'lazy', 14 | port, 15 | } = (typeof options.config !== 'boolean' ? options.config || {} : {}).devtools || {} 16 | 17 | if (enabled === false) 18 | return 19 | 20 | let viewerProcess: ReturnType | undefined 21 | let viewerPort: number | undefined 22 | let viewerUrl: string | undefined 23 | let started = false 24 | 25 | async function start() { 26 | if (started) 27 | return 28 | started = true 29 | const { startSubprocess } = await import('@nuxt/devtools-kit') 30 | const inspectorBinPath = join( 31 | dirname(await resolvePath( 32 | '@eslint/config-inspector/package.json', 33 | { url: dirname(fileURLToPath(import.meta.url)) }, 34 | )), 35 | 'bin.mjs', 36 | ) 37 | 38 | viewerPort = port || await getPort({ 39 | portRange: [8123, 10000], 40 | random: true, 41 | }) 42 | viewerProcess = startSubprocess( 43 | { 44 | command: 'node', 45 | args: [inspectorBinPath, '--no-open'], 46 | cwd: nuxt.options.rootDir, 47 | env: { 48 | PORT: viewerPort.toString(), 49 | }, 50 | }, 51 | { 52 | id: 'eslint-config-inspector', 53 | name: 'ESLint Config Viewer', 54 | }, 55 | nuxt, 56 | ) 57 | nuxt.callHook('devtools:customTabs:refresh') 58 | 59 | // Wait for viewer to be ready 60 | const url = `http://localhost:${viewerPort}` 61 | for (let i = 0; i < 100; i++) { 62 | if (await fetch(url).then(r => r.ok).catch(() => false)) 63 | break 64 | await new Promise(resolve => setTimeout(resolve, 500)) 65 | } 66 | await new Promise(resolve => setTimeout(resolve, 2000)) 67 | viewerUrl = url 68 | } 69 | 70 | nuxt.hook('devtools:customTabs', (tabs) => { 71 | tabs.push({ 72 | name: 'eslint-config', 73 | title: 'ESLint Config', 74 | icon: 'https://raw.githubusercontent.com/eslint/config-inspector/refs/heads/main/public/favicon.svg', 75 | view: viewerUrl 76 | ? { 77 | type: 'iframe', 78 | src: viewerUrl, 79 | } 80 | : { 81 | type: 'launch', 82 | description: 'Start ESLint config inspector to analyze the local ESLint configs', 83 | actions: [ 84 | { 85 | label: 'Launch', 86 | pending: !!viewerProcess, 87 | handle: start, 88 | }, 89 | ], 90 | }, 91 | }) 92 | }) 93 | 94 | if (enabled === true) 95 | start() 96 | } 97 | -------------------------------------------------------------------------------- /packages/eslint-config/src/utils.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | import { isPackageExists } from 'local-pkg' 3 | import type { NuxtESLintConfigOptions, NuxtESLintConfigOptionsResolved } from './flat' 4 | import type { Awaitable } from './types' 5 | 6 | export const parserPlain = { 7 | meta: { 8 | name: 'parser-plain', 9 | }, 10 | parseForESLint: (code: string) => ({ 11 | ast: { 12 | body: [], 13 | comments: [], 14 | loc: { end: code.length, start: 0 }, 15 | range: [0, code.length], 16 | tokens: [], 17 | type: 'Program', 18 | }, 19 | scopeManager: null, 20 | services: { isPlain: true }, 21 | visitorKeys: { 22 | Program: [], 23 | }, 24 | }), 25 | } 26 | 27 | export async function ensurePackages(packages: (string | undefined)[]): Promise { 28 | if (process.env.CI || !process.stdout.isTTY) 29 | return 30 | 31 | const nonExistingPackages = packages.filter(i => i && !isPackageExists(i)) as string[] 32 | if (nonExistingPackages.length === 0) 33 | return 34 | 35 | const p = await import('@clack/prompts') 36 | const result = await p.confirm({ 37 | message: `${nonExistingPackages.length === 1 ? 'Package is' : 'Packages are'} required for this config: ${nonExistingPackages.join(', ')}. Do you want to install them?`, 38 | }) 39 | if (result) 40 | await import('@antfu/install-pkg').then(i => i.installPackage(nonExistingPackages, { dev: true })) 41 | } 42 | 43 | export async function interopDefault(m: Awaitable): Promise { 44 | const resolved = await m 45 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 46 | return (resolved as any).default || resolved 47 | } 48 | 49 | export function removeUndefined(obj: T): T { 50 | return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)) as T 51 | } 52 | 53 | export function resolveOptions( 54 | config: NuxtESLintConfigOptions, 55 | ): NuxtESLintConfigOptionsResolved { 56 | if ('__resolved' in config) { 57 | return config as NuxtESLintConfigOptionsResolved 58 | } 59 | 60 | const dirs = { 61 | ...config.dirs, 62 | } as NuxtESLintConfigOptionsResolved['dirs'] 63 | 64 | dirs.root ||= ['.', './app'] // Support both Nuxt 3 and 4 conventions by default 65 | dirs.src ||= dirs.root 66 | dirs.pages ||= dirs.src.map(src => `${src}/pages`) 67 | dirs.layouts ||= dirs.src.map(src => `${src}/layouts`) 68 | dirs.components ||= dirs.src.map(src => `${src}/components`) 69 | dirs.composables ||= dirs.src.map(src => `${src}/composables`) 70 | dirs.plugins ||= dirs.src.map(src => `${src}/plugins`) 71 | dirs.modules ||= dirs.src.map(src => `${src}/modules`) 72 | dirs.middleware ||= dirs.src.map(src => `${src}/middleware`) 73 | dirs.servers ||= dirs.src.map(src => `${src}/servers`) 74 | dirs.componentsPrefixed ||= [] 75 | 76 | const resolved: NuxtESLintConfigOptionsResolved = { 77 | features: { 78 | standalone: true, 79 | stylistic: false, 80 | typescript: isPackageExists('typescript'), 81 | tooling: false, 82 | formatters: false, 83 | nuxt: {}, 84 | import: {}, 85 | ...config.features, 86 | }, 87 | dirs, 88 | } 89 | 90 | Object.defineProperty(resolved, '__resolved', { value: true, enumerable: false }) 91 | 92 | return resolved 93 | } 94 | -------------------------------------------------------------------------------- /docs/pages/[...slug].vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 115 | -------------------------------------------------------------------------------- /packages/module/src/modules/checker.ts: -------------------------------------------------------------------------------- 1 | import { addVitePlugin, addWebpackPlugin, useLogger } from '@nuxt/kit' 2 | import { relative } from 'pathe' 3 | import { watch } from 'chokidar' 4 | import type { Nuxt } from '@nuxt/schema' 5 | import type { ESLintPluginOptions as ViteCheckerOptions } from 'vite-plugin-eslint2' 6 | import type { Options as WebpackCheckerOptions } from 'eslint-webpack-plugin' 7 | import type { CheckerOptions, ModuleOptions } from '../module' 8 | 9 | const logger = useLogger('nuxt:eslint:checker') 10 | 11 | const flatConfigFiles = [ 12 | 'eslint.config.js', 13 | 'eslint.config.mjs', 14 | 'eslint.config.cjs', 15 | ] 16 | 17 | export async function setupESLintChecker(moduleOptions: ModuleOptions, nuxt: Nuxt) { 18 | const options: CheckerOptions = { 19 | cache: true, 20 | include: [`${nuxt.options.srcDir}/**/*.{js,jsx,ts,tsx,vue}`], 21 | exclude: ['**/node_modules/**', nuxt.options.buildDir], 22 | lintOnStart: true, 23 | formatter: 'stylish', 24 | emitWarning: true, 25 | emitError: true, 26 | ...(typeof moduleOptions.checker === 'boolean' ? {} : moduleOptions.checker || {}), 27 | } 28 | 29 | // When not specified, we try to detect the configType 30 | options.configType ||= process.env.ESLINT_USE_FLAT_CONFIG !== 'false' 31 | ? 'flat' 32 | : 'eslintrc' 33 | 34 | // Vite: https://github.com/ModyQyW/vite-plugin-eslint2#eslintpath 35 | // Webpack: https://github.com/webpack-contrib/eslint-webpack-plugin#configtype 36 | options.eslintPath ||= options.configType === 'flat' 37 | ? 'eslint/use-at-your-own-risk' 38 | : 'eslint' 39 | 40 | const configPaths = [ 41 | '.eslintignore', 42 | '.eslintrc', 43 | '.eslintrc.js', 44 | '.eslintrc.yaml', 45 | '.eslintrc.yml', 46 | '.eslintrc.json', 47 | ...flatConfigFiles, 48 | ].map(path => relative(nuxt.options.rootDir, path)) 49 | 50 | if (nuxt.options.watch) { 51 | nuxt.options.watch.push(...configPaths) 52 | } 53 | else { 54 | const watcher = watch(configPaths, { depth: 0 }).on('change', (path: string) => { 55 | logger.info(`Eslint config changed: ${path}`) 56 | logger.warn('Please restart the Nuxt server to apply changes or upgrade to latest Nuxt for automatic restart.') 57 | }) 58 | nuxt.hook('close', () => watcher.close()) 59 | } 60 | 61 | if (nuxt.options.builder === '@nuxt/vite-builder') { 62 | const vitePluginEslint = await import('vite-plugin-eslint2').then(m => 'default' in m ? m.default : m) 63 | addVitePlugin(() => { 64 | const viteOptions: Partial = { 65 | lintInWorker: true, 66 | ...options, 67 | ...options.vite, 68 | } 69 | // @ts-expect-error extra vite options 70 | delete viteOptions.configType 71 | return vitePluginEslint(viteOptions) 72 | }, { server: false }) 73 | } 74 | else if (nuxt.options.builder === '@nuxt/webpack-builder') { 75 | const EslintWebpackPlugin = await import('eslint-webpack-plugin').then(m => 'default' in m ? m.default : m) 76 | 77 | addWebpackPlugin(() => { 78 | const webpackOptions: WebpackCheckerOptions = { 79 | ...options, 80 | context: nuxt.options.srcDir, 81 | files: options.include, 82 | lintDirtyModulesOnly: !options.lintOnStart, 83 | } 84 | 85 | return new EslintWebpackPlugin(webpackOptions) 86 | }, { server: false }) 87 | } 88 | else { 89 | logger.warn('Unsupported builder ' + nuxt.options.builder + ', ESLint checker is not enabled.') 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/eslint-config/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { ResolvableFlatConfig, FlatConfigComposer } from 'eslint-flat-config-utils' 2 | import { composer } from 'eslint-flat-config-utils' 3 | import gitignore from 'eslint-config-flat-gitignore' 4 | import type { NuxtESLintConfigOptions } from './types' 5 | import disables from './configs/disables' 6 | import nuxt from './configs/nuxt' 7 | import ignores from './configs/ignores' 8 | import javascript from './configs/javascript' 9 | import { resolveOptions } from './utils' 10 | 11 | export * from './types' 12 | 13 | export { resolveOptions } 14 | 15 | /** 16 | * Provide type definitions for constructing ESLint flat config items. 17 | * 18 | * This function takes flat config item, or an array of them as rest arguments. 19 | * It also automatically resolves the promise if the config item is a promise. 20 | */ 21 | export function defineFlatConfigs( 22 | ...configs: ResolvableFlatConfig[] 23 | ): FlatConfigComposer { 24 | return composer(...configs) 25 | } 26 | 27 | /** 28 | * Create an array of ESLint flat configs for Nuxt 3, based on the given options. 29 | * Accepts appending user configs as rest arguments from the second argument. 30 | * 31 | * For Nuxt apps, it's recommended to use `@nuxt/eslint` module instead, which will generate the necessary configuration based on your project. 32 | * @see https://eslint.nuxt.com/packages/module 33 | */ 34 | export function createConfigForNuxt( 35 | options: NuxtESLintConfigOptions = {}, 36 | ...userConfigs: ResolvableFlatConfig[] 37 | ): FlatConfigComposer { 38 | const c = composer() 39 | 40 | const resolved = resolveOptions(options) 41 | 42 | if (resolved.features.standalone !== false) { 43 | c.append( 44 | gitignore({ strict: false }), 45 | ignores(), 46 | javascript(), 47 | // Make these imports async, as they are optional and imports plugins 48 | resolved.features.typescript !== false 49 | ? import('./configs/typescript').then(m => m.default(resolved)) 50 | : undefined, 51 | import('./configs/vue').then(m => m.default(resolved)), 52 | import('./configs/import').then(m => m.default(resolved)), 53 | ) 54 | } 55 | 56 | c.append( 57 | nuxt(resolved), 58 | ) 59 | 60 | if (resolved.features.tooling) { 61 | const toolingOptions = typeof resolved.features.tooling === 'boolean' ? {} : resolved.features.tooling 62 | c.append( 63 | toolingOptions.jsdoc !== false && import('./configs-tooling/jsdoc').then(m => m.default(resolved)), 64 | toolingOptions.unicorn !== false && import('./configs-tooling/unicorn').then(m => m.default()), 65 | toolingOptions.regexp !== false && import('./configs-tooling/regexp').then(m => m.default()), 66 | ) 67 | } 68 | 69 | const stylisticOptions = typeof resolved.features.stylistic === 'boolean' 70 | ? {} 71 | : resolved.features.stylistic 72 | 73 | if (resolved.features.stylistic) { 74 | c.append( 75 | import('./configs/stylistic').then(m => m.default(stylisticOptions)), 76 | ) 77 | } 78 | 79 | if (resolved.features.formatters) { 80 | c.append( 81 | import('./configs/formatters').then(m => m.formatters(resolved.features.formatters, stylisticOptions)), 82 | ) 83 | } 84 | 85 | c.append( 86 | disables(resolved), 87 | ) 88 | 89 | if (userConfigs.length > 0) { 90 | c.append(...userConfigs) 91 | } 92 | 93 | c 94 | .setPluginConflictsError() 95 | .setPluginConflictsError('import', [ 96 | 'Different instances of plugin "{{pluginName}}" found in multiple configs:', 97 | '{{configNames}}.', 98 | 'You might forget to set `standalone: false`.', 99 | 'Please refer to https://eslint.nuxt.com/packages/module#custom-config-presets.', 100 | '', 101 | ].join('\n')) 102 | 103 | return c 104 | } 105 | 106 | export default createConfigForNuxt 107 | -------------------------------------------------------------------------------- /packages/module/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Import } from 'unimport' 2 | import type { ESLintPluginOptions as ViteCheckerOptions } from 'vite-plugin-eslint2' 3 | import type { Options as WebpackCheckerOptions } from 'eslint-webpack-plugin' 4 | import type { NuxtESLintFeaturesOptions } from '@nuxt/eslint-config/flat' 5 | 6 | declare module '@nuxt/schema' { 7 | interface NuxtHooks { 8 | /** 9 | * Called before generating ESLint config, can be used to custom ESLint config integrations 10 | */ 11 | 'eslint:config:addons': (addons: ESLintConfigGenAddon[]) => void 12 | } 13 | } 14 | 15 | export interface ConfigGenOptions extends NuxtESLintFeaturesOptions { 16 | /** 17 | * File path to the generated ESLint config 18 | * 19 | * @default '.nuxt/eslint.config.mjs' 20 | */ 21 | configFile?: string 22 | 23 | /** 24 | * Create `eslint.config.mjs` file automatically if not exists 25 | * 26 | * @default true 27 | */ 28 | autoInit?: boolean 29 | 30 | /** 31 | * Override rootDir for the generated ESLint config 32 | * If you generate ESLint config from a different directory, you can set this option 33 | * 34 | * @default nuxt.options.rootDir 35 | */ 36 | rootDir?: string 37 | 38 | /** 39 | * Options for DevTools integration 40 | */ 41 | devtools?: { 42 | /** 43 | * Enable ESLint config inspector in DevTools 44 | * 45 | * @default 'lazy' 46 | */ 47 | enabled?: boolean | 'lazy' 48 | /** 49 | * Port for the ESLint config inspector 50 | */ 51 | port?: number 52 | } 53 | } 54 | 55 | export interface CheckerOptions { 56 | /** 57 | * Use ESLint cache to improve performance 58 | * 59 | * @default true 60 | */ 61 | cache?: boolean 62 | 63 | /** 64 | * ESLint config type 65 | * 66 | * Default to `flat` unless env `ESLINT_USE_FLAT_CONFIG` is set to `false` 67 | * 68 | * @default 'flat' 69 | */ 70 | configType?: 'flat' | 'eslintrc' 71 | 72 | /** 73 | * Files to include for linting 74 | */ 75 | include?: string[] 76 | 77 | /** 78 | * Files to exclude from linting 79 | */ 80 | exclude?: string[] 81 | 82 | /** 83 | * ESLint formatter for the output 84 | * 85 | * @see https://eslint.org/docs/user-guide/formatters/ 86 | */ 87 | formatter?: string 88 | 89 | /** 90 | * Path to the ESLint module 91 | * 92 | * @default 'eslint' or 'eslint/use-at-your-own-risk' based on configType 93 | */ 94 | eslintPath?: string 95 | 96 | /** 97 | * Lint on start 98 | * 99 | * @default true 100 | */ 101 | lintOnStart?: boolean 102 | 103 | /** 104 | * The warnings found will printed 105 | * 106 | * @default true 107 | */ 108 | emitWarning?: boolean 109 | 110 | /** 111 | * The errors found will printed 112 | * 113 | * @default true 114 | */ 115 | emitError?: boolean 116 | 117 | /** 118 | * Run ESLint fix 119 | * @default false 120 | */ 121 | fix?: boolean 122 | 123 | /** 124 | * Vite specific options 125 | */ 126 | vite?: ViteCheckerOptions 127 | 128 | /** 129 | * Webpack specific options 130 | * 131 | * @see https://www.npmjs.com/package/eslint-webpack-plugin 132 | */ 133 | webpack?: WebpackCheckerOptions 134 | } 135 | 136 | export interface ModuleOptions { 137 | /** 138 | * Options for ESLint flat config generation (.nuxt/eslint.config.mjs) 139 | */ 140 | config?: ConfigGenOptions | boolean 141 | 142 | /** 143 | * Enable ESLint checker align with dev server or build process 144 | * Not enabled by default 145 | * 146 | * @default false 147 | */ 148 | checker?: CheckerOptions | boolean 149 | } 150 | 151 | export interface ESLintConfigGenAddonResult { 152 | /** 153 | * Imports statements to add to the generated ESLint config 154 | */ 155 | imports?: Import[] 156 | /** 157 | * Flat config items, should be stringified lines 158 | */ 159 | configs?: string[] 160 | } 161 | 162 | export type Awaitable = T | Promise 163 | 164 | export type ESLintConfigGenAddon = { 165 | name: string 166 | getConfigs: () => Awaitable 167 | } 168 | -------------------------------------------------------------------------------- /docs/pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 47 | 48 | 125 | 126 | 146 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/typescript.ts: -------------------------------------------------------------------------------- 1 | import parserTs from '@typescript-eslint/parser' 2 | import pluginTs from '@typescript-eslint/eslint-plugin' 3 | import type { Linter } from 'eslint' 4 | import type { NuxtESLintConfigOptions } from '@nuxt/eslint-config/flat' 5 | import { resolveOptions } from '../utils' 6 | 7 | export { parserTs, pluginTs } 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | const getRulesFromConfigs = (config: any) => { 11 | const array = Array.isArray(config) ? config : [config] 12 | const object = array.reduce((acc, item) => { 13 | return { ...acc, ...item.rules } 14 | }, {}) 15 | return object 16 | } 17 | 18 | export default function typescript(options: NuxtESLintConfigOptions): Linter.Config[] { 19 | const resolved = resolveOptions(options) 20 | 21 | if (resolved.features.typescript === false) { 22 | return [] 23 | } 24 | 25 | const tsOptions = resolved.features.typescript === true ? {} : resolved.features.typescript 26 | const strict = tsOptions.strict === false ? false : true 27 | const tsconfigPath = tsOptions.tsconfigPath || undefined 28 | 29 | return [ 30 | { 31 | name: 'nuxt/typescript/setup', 32 | plugins: { 33 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 34 | '@typescript-eslint': pluginTs as any, 35 | }, 36 | }, 37 | { 38 | name: 'nuxt/typescript/rules', 39 | files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts', '**/*.vue'], 40 | languageOptions: { 41 | parser: parserTs, 42 | parserOptions: { 43 | extraFileExtensions: ['.vue'], 44 | sourceType: 'module', 45 | ...tsconfigPath 46 | ? { 47 | projectService: { 48 | allowDefaultProject: ['./*.js'], 49 | defaultProject: tsconfigPath, 50 | }, 51 | tsconfigRootDir: process.cwd(), 52 | } 53 | : {}, 54 | }, 55 | }, 56 | rules: { 57 | ...getRulesFromConfigs(pluginTs.configs['flat/recommended']), 58 | 59 | // Type-aware rules 60 | ...(tsconfigPath 61 | ? getRulesFromConfigs(pluginTs.configs['flat/recommended-type-checked-only']) 62 | : {}), 63 | 64 | // Strict rules 65 | ...(strict 66 | ? tsconfigPath 67 | ? getRulesFromConfigs(pluginTs.configs['flat/strict-type-checked-only']) 68 | : getRulesFromConfigs(pluginTs.configs['flat/strict']) 69 | : {}), 70 | 71 | // Include typescript eslint rules in *.vue files 72 | // https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/eslint-recommended.ts 73 | 'constructor-super': 'off', // ts(2335) & ts(2377) 74 | 'getter-return': 'off', // ts(2378) 75 | 'no-const-assign': 'off', // ts(2588) 76 | 'no-dupe-args': 'off', // ts(2300) 77 | 'no-dupe-class-members': 'off', // ts(2393) & ts(2300) 78 | 'no-dupe-keys': 'off', // ts(1117) 79 | 'no-func-assign': 'off', // ts(2539) 80 | 'no-import-assign': 'off', // ts(2539) & ts(2540) 81 | 'no-new-symbol': 'off', // ts(7009) 82 | 'no-obj-calls': 'off', // ts(2349) 83 | 'no-redeclare': 'off', // ts(2451) 84 | 'no-setter-return': 'off', // ts(2408) 85 | 'no-this-before-super': 'off', // ts(2376) 86 | 'no-undef': 'off', // ts(2304) 87 | 'no-unreachable': 'off', // ts(7027) 88 | 'no-unsafe-negation': 'off', // ts(2365) & ts(2360) & ts(2358) 89 | 'no-var': 'error', // ts transpiles let/const to var, so no need for vars any more 90 | 'prefer-const': 'error', // ts provides better types with const 91 | 'prefer-rest-params': 'error', // ts provides better types with rest args over arguments 92 | 'prefer-spread': 'error', // ts transpiles spread to apply, so no need for manual apply 93 | 'valid-typeof': 'off', // ts(2367) 94 | 'no-unused-vars': 'off', // ts takes care of this 95 | 96 | '@typescript-eslint/no-non-null-assertion': 'off', 97 | '@typescript-eslint/consistent-type-imports': ['error', { 98 | disallowTypeAnnotations: false, 99 | prefer: 'type-imports', 100 | }], 101 | '@typescript-eslint/no-unused-vars': ['error', { 102 | args: 'after-used', 103 | argsIgnorePattern: '^_', 104 | ignoreRestSiblings: true, 105 | vars: 'all', 106 | varsIgnorePattern: '^_', 107 | }], 108 | '@typescript-eslint/no-import-type-side-effects': 'error', 109 | }, 110 | }, 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /docs/content/1.packages/1.config.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: '@nuxt/eslint-config' 3 | --- 4 | 5 | Shared ESLint config for Nuxt 3 projects. Unopinionated by default, but customizable. 6 | 7 | :::callout{icon="i-ph-lightbulb-duotone"} 8 | We recommand to use directly the [ESLint Module](/packages/module) that will provide project-aware ESLint config and Nuxt DevTools integration on top of this config. 9 | ::: 10 | 11 | ::read-more 12 | --- 13 | to: https://github.com/nuxt/eslint/tree/main/packages/eslint-config 14 | color: gray 15 | icon: i-simple-icons-github 16 | --- 17 | Source code on GitHub 18 | :: 19 | 20 | 21 | ## Configurations 22 | 23 | Since version v1.0, we provide in the flat config format. The entry `@nuxt/eslint-config` provides a factory function to create a project-aware ESLint config for Nuxt 3 projects. It is unopinionated by default but customizable by passing options to the factory function. Used by [`@nuxt/eslint`](/packages/module) module to generate project-aware ESLint config. 24 | 25 | 1. Install this package and `eslint` in your `devDependencies`. 26 | 27 | ::code-group 28 | ```bash [yarn] 29 | yarn add --dev @nuxt/eslint-config eslint 30 | ``` 31 | ```bash [npm] 32 | npm install --save-dev @nuxt/eslint-config eslint 33 | ``` 34 | ```bash [pnpm] 35 | pnpm add -D @nuxt/eslint-config eslint 36 | ``` 37 | ```bash [bun] 38 | bun add -D @nuxt/eslint-config eslint 39 | ``` 40 | :: 41 | 42 | 2. Import the config factory function from `@nuxt/eslint-config` entry in your `eslint.config.mjs`: 43 | 44 | ```js [eslint.config.mjs] 45 | import { createConfigForNuxt } from '@nuxt/eslint-config' 46 | 47 | export default createConfigForNuxt({ 48 | // options here 49 | }) 50 | ``` 51 | 52 | You might also want to add a script entry to your `package.json`: 53 | 54 | ```json [package.json] 55 | { 56 | "scripts": { 57 | "lint": "eslint ." 58 | } 59 | } 60 | ``` 61 | 62 | ### Customizing the Config 63 | 64 | Note that `createConfigForNuxt()` returns a chainable [`FlatConfigComposer` instance](https://github.com/antfu/eslint-flat-config-utils#composer) from [`eslint-flat-config-utils`](https://github.com/antfu/eslint-flat-config-utils) which allows you to manipulate the ESLint flat config with ease. If you want to combine with other configs, you can use the `.append()` method: 65 | 66 | ```js [eslint.config.mjs] 67 | import { createConfigForNuxt } from '@nuxt/eslint-config' 68 | 69 | export default createConfigForNuxt({ 70 | // options here 71 | }) 72 | .prepend( 73 | // ...Prepend some flat configs in front 74 | ) 75 | // Override some rules in a specific config, based on their name 76 | .override('nuxt/typescript/rules', { 77 | rules: { 78 | // ...Override rules, for example: 79 | '@typescript-eslint/ban-types': 'off' 80 | } 81 | }) 82 | // ...you can chain more operations as needed 83 | ``` 84 | 85 | `FlatConfigComposer` is also a Promise object, that you can use `await` to get the final config object. 86 | 87 | ### Module Authors 88 | 89 | This config also provides rules for module/library authors. You can enable them by setting the `features.tooling` option to `true`: 90 | 91 | :::callout{icon="i-ph-crane-tower-duotone" color="amber"} 92 | This feature is experimental and may change in the future. 93 | ::: 94 | 95 | ```js [eslint.config.mjs] 96 | import { createConfigForNuxt } from '@nuxt/eslint-config' 97 | 98 | export default createConfigForNuxt({ 99 | features: { 100 | tooling: true 101 | } 102 | }) 103 | ``` 104 | 105 | This will enable rules with `unicorn`, `regexp` and `jsdoc` plugins, to ensure your module is following best practices. 106 | 107 | You can also turn them off individually by providing an object with the rule names set to `false`: 108 | 109 | ```js [eslint.config.mjs] 110 | import { createConfigForNuxt } from '@nuxt/eslint-config' 111 | 112 | export default createConfigForNuxt({ 113 | features: { 114 | tooling: { 115 | regexp: false, 116 | } 117 | } 118 | }) 119 | ``` 120 | 121 | ## ESLint Stylistic 122 | 123 | Similar to the [ESLint Module](https://eslint.nuxt.com/packages/module#eslint-stylistic), you can opt-in by setting `stylistic` to `true` in the `features` module options. 124 | 125 | ```js [eslint.config.mjs] 126 | import { createConfigForNuxt } from '@nuxt/eslint-config' 127 | 128 | export default createConfigForNuxt({ 129 | features: { 130 | stylistic: true // <--- 131 | } 132 | }) 133 | ``` 134 | 135 | Learn more about all the available options in the [ESLint Stylistic documentation](https://eslint.style/guide/config-presets#configuration-factory). 136 | 137 | ## Type Aware Rules 138 | 139 | This config also provides type-aware rules for TypeScript and Vue. You can enable them by setting `features.typescript` to an object with `tsconfigPath` the path to your `tsconfig.json` file. 140 | ```js [eslint.config.mjs] 141 | import { createConfigForNuxt } from '@nuxt/eslint-config' 142 | 143 | export default createConfigForNuxt({ 144 | features: { 145 | typescript: { 146 | tsconfigPath: './tsconfig.json' 147 | } 148 | } 149 | }) 150 | ``` 151 | -------------------------------------------------------------------------------- /packages/module/src/modules/config/generate.ts: -------------------------------------------------------------------------------- 1 | import { builtinModules } from 'node:module' 2 | import { dirname, join, resolve } from 'node:path' 3 | import { stringifyImports } from 'unimport' 4 | import type { Import } from 'unimport' 5 | import type { Nuxt } from '@nuxt/schema' 6 | import { relative } from 'pathe' 7 | import { createResolver } from '@nuxt/kit' 8 | import type { ESLintConfigGenAddon } from '../../types' 9 | import type { ConfigGenOptions, ModuleOptions } from '../../module' 10 | import { getDirs } from './utils' 11 | 12 | const r = createResolver(import.meta.url) 13 | 14 | export async function generateESLintConfig( 15 | options: ModuleOptions, 16 | nuxt: Nuxt, 17 | addons: ESLintConfigGenAddon[], 18 | ) { 19 | const importLines: Import[] = [] 20 | const configItems: string[] = [] 21 | 22 | const config: ConfigGenOptions = { 23 | standalone: true, 24 | ...typeof options.config !== 'boolean' ? options.config || {} : {}, 25 | } 26 | 27 | let { 28 | configFile = join(nuxt.options.buildDir, 'eslint.config.mjs'), 29 | } = config 30 | 31 | configFile = resolve(nuxt.options.rootDir, configFile) 32 | const configDir = dirname(configFile) 33 | 34 | importLines.push( 35 | { 36 | from: 'eslint-typegen', 37 | name: 'default', 38 | as: 'typegen', 39 | }, 40 | { 41 | from: '@nuxt/eslint-config/flat', 42 | name: 'createConfigForNuxt', 43 | }, 44 | { 45 | from: '@nuxt/eslint-config/flat', 46 | name: 'defineFlatConfigs', 47 | }, 48 | { 49 | from: '@nuxt/eslint-config/flat', 50 | name: 'resolveOptions', 51 | }, 52 | { 53 | from: 'url', 54 | name: 'fileURLToPath', 55 | }, 56 | ) 57 | 58 | const dirs = getDirs(nuxt, options) || {} 59 | 60 | for (const addon of addons) { 61 | const resolved = await addon.getConfigs() 62 | if (resolved?.imports) 63 | importLines.push(...resolved.imports) 64 | if (resolved?.configs) 65 | configItems.push(...resolved.configs) 66 | } 67 | 68 | function relativeWithDot(path: string) { 69 | const r = relative(configDir, path) 70 | return r.startsWith('.') ? r : './' + r 71 | } 72 | 73 | const imports = await Promise.all(importLines.map(async (line): Promise => { 74 | return { 75 | ...line, 76 | from: builtinModules.includes(line.from) 77 | ? line.from.replace(/^(node:)?/, 'node:') 78 | : line.from.match(/^\w+:/) 79 | ? line.from 80 | : relativeWithDot(await r.resolvePath(line.from)), 81 | } 82 | })) 83 | 84 | const code = [ 85 | '// ESLint config generated by Nuxt', 86 | '/// ', 87 | '/* eslint-disable */', 88 | '// @ts-nocheck', 89 | '', 90 | stringifyImports(imports, false), 91 | '', 92 | 'const r = (...args) => fileURLToPath(new URL(...args, import.meta.url))', 93 | '', 94 | 'export { defineFlatConfigs }', 95 | '', 96 | `export const options = resolveOptions({`, 97 | ` features: ${JSON.stringify(config, null, 2)},`, 98 | ` dirs: {`, 99 | ...Object 100 | .entries(dirs) 101 | .map(([key, value]) => { 102 | return ` ${key}: [${value.map(v => 103 | key === 'root' 104 | ? `r(${JSON.stringify(relativeWithDot(v))})` 105 | : JSON.stringify(v), 106 | ).join(', ')}],` 107 | }), 108 | `}`, 109 | `})`, 110 | '', 111 | `export const configs = createConfigForNuxt(options)`, 112 | 113 | ...(configItems.length 114 | ? [ 115 | '', 116 | `configs.append(`, 117 | configItems.join(',\n\n'), 118 | `)`, 119 | '', 120 | ] 121 | : []), 122 | 123 | 'export function withNuxt(...customs) {', 124 | ' return configs', 125 | ' .clone()', 126 | ' .append(...customs)', 127 | ' .onResolved(configs => typegen(configs, { dtsPath: r("./eslint-typegen.d.ts"), augmentFlatConfigUtils: true }))', 128 | '}', 129 | '', 130 | 'export default withNuxt', 131 | ].join('\n') 132 | 133 | const [ 134 | pathToFlatConfigUtils, 135 | pathToESLintConfigFlat, 136 | ] = await Promise.all([ 137 | r.resolvePath('eslint-flat-config-utils').then(r => relativeWithDot(r)), 138 | r.resolvePath('@nuxt/eslint-config/flat').then(r => relativeWithDot(r)), 139 | ]) 140 | 141 | const codeDts = [ 142 | 'import type { FlatConfigComposer } from ' + JSON.stringify(pathToFlatConfigUtils), 143 | 'import { defineFlatConfigs } from ' + JSON.stringify(pathToESLintConfigFlat), 144 | 'import type { NuxtESLintConfigOptionsResolved } from ' + JSON.stringify(pathToESLintConfigFlat), 145 | '', 146 | 'declare const configs: FlatConfigComposer', 147 | 'declare const options: NuxtESLintConfigOptionsResolved', 148 | 'declare const withNuxt: typeof defineFlatConfigs', 149 | 'export default withNuxt', 150 | 'export { withNuxt, defineFlatConfigs, configs, options }', 151 | ].join('\n') 152 | 153 | return { 154 | code, 155 | codeDts, 156 | configFile, 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /docs/components/NuxtLogo.vue: -------------------------------------------------------------------------------- 1 | 40 | -------------------------------------------------------------------------------- /packages/eslint-config/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { StylisticCustomizeOptions } from '@stylistic/eslint-plugin' 2 | 3 | export interface ToolingOptions { 4 | /** 5 | * Enable RegExp rules 6 | * 7 | * @see https://github.com/ota-meshi/eslint-plugin-regexp 8 | * @default true 9 | */ 10 | regexp?: boolean 11 | /** 12 | * Enable Unicorn rules 13 | * 14 | * @see https://github.com/sindresorhus/eslint-plugin-unicorn 15 | * @default true 16 | */ 17 | unicorn?: boolean 18 | /** 19 | * Enable jsdoc rules 20 | * 21 | * @default true 22 | */ 23 | jsdoc?: boolean 24 | } 25 | 26 | export interface NuxtSpecificOptions { 27 | /** 28 | * Sort keys in nuxt.config to maintain a consistent order 29 | * 30 | * @default true when `features.stylistic` is enabled 31 | */ 32 | sortConfigKeys?: boolean 33 | } 34 | 35 | export interface NuxtESLintFeaturesOptions { 36 | /** 37 | * Setup basic JavaScript, TypeScript and Vue plugins and rules. 38 | * 39 | * You might want to disable it when you are using other ESLint config that handles the basic setup. 40 | * 41 | * @default true 42 | */ 43 | standalone?: boolean 44 | 45 | /** 46 | * Enable rules for Nuxt module authors or library authors 47 | * 48 | * @experimental Changes might not follow semver 49 | * @default false 50 | */ 51 | tooling?: boolean | ToolingOptions 52 | 53 | /** 54 | * Enable the import plugin 55 | * 56 | * @default true 57 | */ 58 | import?: boolean | ImportPluginOptions 59 | 60 | /** 61 | * Enable stylistic ESLint rules for formatting and code style check 62 | * 63 | * @see https://eslint.style/guide/config-presets 64 | * @default false 65 | */ 66 | stylistic?: boolean | StylisticCustomizeOptions 67 | 68 | /** 69 | * Enable formatters to handling formatting for different file types 70 | * 71 | * Requires `eslint-plugin-format` to be installed 72 | * 73 | * @default false 74 | */ 75 | formatters?: boolean | OptionsFormatters 76 | 77 | /** 78 | * Options for Nuxt specific rules 79 | */ 80 | nuxt?: NuxtSpecificOptions 81 | 82 | /** 83 | * Enable TypeScript support. Can also be an object to config the options. 84 | * 85 | * By default it enables automatic when `typescript` is installed in the project. 86 | */ 87 | typescript?: boolean | { 88 | /** 89 | * Enable strict rules 90 | * @see https://typescript-eslint.io/users/configs#strict 91 | * @default true 92 | */ 93 | strict?: boolean 94 | /** 95 | * Path to the tsconfig file, when this is provide, type-aware rules will be enabled. 96 | */ 97 | tsconfigPath?: string 98 | } 99 | } 100 | 101 | export interface ImportPluginOptions { 102 | /** 103 | * The import plugin to use 104 | * 105 | * @default 'eslint-plugin-import-x' 106 | */ 107 | package?: 'eslint-plugin-import-lite' | 'eslint-plugin-import-x' 108 | } 109 | 110 | export interface NuxtESLintConfigOptions { 111 | features?: NuxtESLintFeaturesOptions 112 | 113 | dirs?: { 114 | /** 115 | * Nuxt source directory 116 | */ 117 | src?: string[] 118 | 119 | /** 120 | * Root directory for nuxt project 121 | */ 122 | root?: string[] 123 | 124 | /** 125 | * Directory for pages 126 | */ 127 | pages?: string[] 128 | 129 | /** 130 | * Directory for layouts 131 | */ 132 | layouts?: string[] 133 | 134 | /** 135 | * Directory for components 136 | */ 137 | components?: string[] 138 | 139 | /** 140 | * Directory for components with prefix 141 | * Ignore `vue/multi-word-component-names` 142 | */ 143 | componentsPrefixed?: string[] 144 | 145 | /** 146 | * Directory for composobles 147 | */ 148 | composables?: string[] 149 | 150 | /** 151 | * Directory for plugins 152 | */ 153 | plugins?: string[] 154 | 155 | /** 156 | * Directory for modules 157 | */ 158 | modules?: string[] 159 | 160 | /** 161 | * Directory for middleware 162 | */ 163 | middleware?: string[] 164 | 165 | /** 166 | * Directory for server 167 | */ 168 | servers?: string[] 169 | } 170 | } 171 | 172 | export interface OptionsFormatters { 173 | /** 174 | * Enable formatting support for CSS, Less, Sass, and SCSS. 175 | * 176 | * Currently only support Prettier. 177 | */ 178 | css?: 'prettier' | boolean 179 | 180 | /** 181 | * Enable formatting support for HTML. 182 | * 183 | * Currently only support Prettier. 184 | */ 185 | html?: 'prettier' | boolean 186 | 187 | /** 188 | * Enable formatting support for XML. 189 | * 190 | * Currently only support Prettier. 191 | */ 192 | xml?: 'prettier' | boolean 193 | 194 | /** 195 | * Enable formatting support for SVG. 196 | * 197 | * Currently only support Prettier. 198 | */ 199 | svg?: 'prettier' | boolean 200 | 201 | /** 202 | * Enable formatting support for Markdown. 203 | * 204 | * Support both Prettier and dprint. 205 | * 206 | * When set to `true`, it will use Prettier. 207 | */ 208 | markdown?: 'prettier' | 'dprint' | boolean 209 | 210 | /** 211 | * Enable formatting support for GraphQL. 212 | */ 213 | graphql?: 'prettier' | boolean 214 | 215 | /** 216 | * Custom options for Prettier. 217 | * 218 | * By default it's controlled by our own config. 219 | */ 220 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 221 | prettierOptions?: any 222 | 223 | /** 224 | * Custom options for dprint. 225 | * 226 | * By default it's controlled by our own config. 227 | */ 228 | dprintOptions?: boolean 229 | } 230 | 231 | type NotNill = T extends null | undefined ? never : T 232 | 233 | export interface NuxtESLintConfigOptionsResolved { 234 | features: Required> 235 | dirs: Required> 236 | } 237 | 238 | export type Awaitable = T | Promise 239 | -------------------------------------------------------------------------------- /packages/eslint-plugin/src/rules/nuxt-config-keys-order/index.ts: -------------------------------------------------------------------------------- 1 | import type { TSESTree as Tree, TSESLint } from '@typescript-eslint/utils' 2 | import { createRule } from '../utils' 3 | import { ORDER_KEYS } from './keys' 4 | 5 | type MessageIds = 'default' 6 | 7 | type Options = [] 8 | 9 | export const rule = createRule({ 10 | name: 'nuxt-config-keys-order', 11 | meta: { 12 | type: 'suggestion', 13 | docs: { 14 | description: 'Prefer recommended order of Nuxt config properties', 15 | }, 16 | schema: [], 17 | messages: { 18 | default: 'Expected config key "{{a}}" to come before "{{b}}"', 19 | }, 20 | fixable: 'code', 21 | }, 22 | defaultOptions: [], 23 | create(context) { 24 | return { 25 | ExportDefaultDeclaration(node) { 26 | let object: Tree.ObjectExpression | undefined 27 | if (node.declaration.type === 'ObjectExpression') { 28 | object = node.declaration 29 | } 30 | else if (node.declaration.type === 'CallExpression' && node.declaration.arguments[0].type === 'ObjectExpression') { 31 | object = node.declaration.arguments[0] 32 | } 33 | if (!object) { 34 | return 35 | } 36 | 37 | const hasFixes = sort(context, object) 38 | if (!hasFixes) { 39 | const envProps = object.properties.filter(i => i.type === 'Property' && i.key.type === 'Identifier' && i.key.name.startsWith('$')) as Tree.Property[] 40 | for (const prop of envProps) { 41 | if (prop.value.type === 'ObjectExpression') 42 | sort(context, prop.value) 43 | } 44 | } 45 | }, 46 | } 47 | }, 48 | }) 49 | 50 | function sort(context: TSESLint.RuleContext, node: Tree.ObjectExpression) { 51 | return sortAst( 52 | context, 53 | node, 54 | node.properties as Tree.Property[], 55 | (prop) => { 56 | if (prop.type === 'Property') 57 | return getString(prop.key) 58 | return null 59 | }, 60 | sortKeys, 61 | ) 62 | } 63 | 64 | function sortKeys(a: string, b: string) { 65 | const indexA = ORDER_KEYS.findIndex(k => typeof k === 'string' ? k === a : k.test(a)) 66 | const indexB = ORDER_KEYS.findIndex(k => typeof k === 'string' ? k === b : k.test(b)) 67 | if (indexA === -1 && indexB !== -1) 68 | return 1 69 | if (indexA !== -1 && indexB === -1) 70 | return -1 71 | if (indexA < indexB) 72 | return -1 73 | if (indexA > indexB) 74 | return 1 75 | return a.localeCompare(b) 76 | } 77 | 78 | // Ported from https://github.com/gauben/eslint-plugin-command/blob/04efa47a2319a5f9afb395cf0efccc9cb111058d/src/commands/keep-sorted.ts#L138-L144 79 | function sortAst( 80 | context: TSESLint.RuleContext, 81 | node: Tree.Node, 82 | list: T[], 83 | getName: (node: T) => string | (string | null)[] | null, 84 | sort: (a: string, b: string) => number = (a, b) => a.localeCompare(b), 85 | insertComma = true, 86 | ) { 87 | const firstToken = context.sourceCode.getFirstToken(node)! 88 | const lastToken = context.sourceCode.getLastToken(node)! 89 | if (!firstToken || !lastToken) 90 | return false 91 | 92 | if (list.length < 2) 93 | return false 94 | 95 | const reordered = list.slice() 96 | const ranges = new Map() 97 | const names = new Map() 98 | 99 | const rangeStart = Math.max( 100 | firstToken.range[1], 101 | context.sourceCode.getIndexFromLoc({ 102 | line: list[0].loc.start.line, 103 | column: 0, 104 | }), 105 | ) 106 | 107 | let rangeEnd = rangeStart 108 | for (let i = 0; i < list.length; i++) { 109 | const item = list[i] 110 | let name = getName(item) 111 | if (typeof name === 'string') 112 | name = [name] 113 | names.set(item, name) 114 | 115 | let lastRange = item.range[1] 116 | const nextToken = context.sourceCode.getTokenAfter(item) 117 | if (nextToken?.type === 'Punctuator' && nextToken.value === ',') 118 | lastRange = nextToken.range[1] 119 | const nextChar = context.sourceCode.getText()[lastRange] 120 | 121 | // Insert comma if it's the last item without a comma 122 | let text = getTextOf(context.sourceCode, [rangeEnd, lastRange]) 123 | if (nextToken === lastToken && insertComma) 124 | text += ',' 125 | 126 | // Include subsequent newlines 127 | if (nextChar === '\n') { 128 | lastRange++ 129 | text += '\n' 130 | } 131 | 132 | ranges.set(item, [rangeEnd, lastRange, text]) 133 | rangeEnd = lastRange 134 | } 135 | 136 | const segments: [number, number][] = [] 137 | let segmentStart: number = -1 138 | for (let i = 0; i < list.length; i++) { 139 | if (names.get(list[i]) == null) { 140 | if (segmentStart > -1) 141 | segments.push([segmentStart, i]) 142 | segmentStart = -1 143 | } 144 | else { 145 | if (segmentStart === -1) 146 | segmentStart = i 147 | } 148 | } 149 | if (segmentStart > -1 && segmentStart !== list.length - 1) 150 | segments.push([segmentStart, list.length]) 151 | 152 | for (const [start, end] of segments) { 153 | reordered.splice( 154 | start, 155 | end - start, 156 | ...reordered 157 | .slice(start, end) 158 | .sort((a, b) => { 159 | const nameA: (string | null)[] = names.get(a)! 160 | const nameB: (string | null)[] = names.get(b)! 161 | 162 | const length = Math.max(nameA.length, nameB.length) 163 | for (let i = 0; i < length; i++) { 164 | const a = nameA[i] 165 | const b = nameB[i] 166 | if (a == null || b == null || a === b) 167 | continue 168 | return sort(a, b) 169 | } 170 | return 0 171 | }), 172 | ) 173 | } 174 | 175 | const changed = reordered.some((prop, i) => prop !== list[i]) 176 | if (!changed) 177 | return false 178 | 179 | const newContent = reordered 180 | .map(i => ranges.get(i)![2]) 181 | .join('') 182 | 183 | // console.log({ 184 | // reordered, 185 | // newContent, 186 | // oldContent: ctx.context.sourceCode.text.slice(rangeStart, rangeEnd), 187 | // }) 188 | 189 | context.report({ 190 | node, 191 | messageId: 'default', 192 | data: { 193 | a: names.get(reordered[0])![0]!, 194 | b: names.get(reordered[1])![0]!, 195 | }, 196 | fix(fixer) { 197 | return fixer.replaceTextRange([rangeStart, rangeEnd], newContent) 198 | }, 199 | }) 200 | } 201 | 202 | function getTextOf(sourceCode: TSESLint.SourceCode, node?: Tree.Node | Tree.Token | Tree.Range | null) { 203 | if (!node) 204 | return '' 205 | if (Array.isArray(node)) 206 | return sourceCode.text.slice(node[0], node[1]) 207 | return sourceCode.getText(node) 208 | } 209 | 210 | function getString(node: Tree.Node): string | null { 211 | if (node.type === 'Identifier') 212 | return node.name 213 | if (node.type === 'Literal') 214 | return String(node.raw) 215 | return null 216 | } 217 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/vue.ts: -------------------------------------------------------------------------------- 1 | import * as parserVue from 'vue-eslint-parser' 2 | import pluginVue from 'eslint-plugin-vue' 3 | import processorVueBlocks from 'eslint-processor-vue-blocks' 4 | import type { Linter } from 'eslint' 5 | import { mergeProcessors } from 'eslint-merge-processors' 6 | import type { NuxtESLintConfigOptions } from '../types' 7 | import { removeUndefined, resolveOptions } from '../utils' 8 | 9 | // imported from 'eslint-plugin-vue/lib/utils/inline-non-void-elements.json' 10 | const INLINE_ELEMENTS = ['a', 'abbr', 'audio', 'b', 'bdi', 'bdo', 'canvas', 'cite', 'code', 'data', 'del', 'dfn', 'em', 'i', 'iframe', 'ins', 'kbd', 'label', 'map', 'mark', 'noscript', 'object', 'output', 'picture', 'q', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'svg', 'time', 'u', 'var', 'video'] 11 | 12 | export default async function vue(options: NuxtESLintConfigOptions): Promise { 13 | const resolved = resolveOptions(options) 14 | const hasTs = resolved.features.typescript !== false 15 | 16 | const parser = hasTs 17 | ? await import('./typescript').then(mod => mod.parserTs) 18 | : undefined 19 | 20 | const { 21 | indent = 2, 22 | commaDangle = 'always-multiline', 23 | } = typeof resolved.features.stylistic === 'boolean' ? {} : resolved.features.stylistic 24 | 25 | const configs: Linter.Config[] = [ 26 | { 27 | name: 'nuxt/vue/setup', 28 | plugins: { 29 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 30 | vue: pluginVue as any, 31 | }, 32 | languageOptions: { 33 | parserOptions: { 34 | ecmaVersion: 'latest', 35 | extraFileExtensions: ['.vue'], 36 | parser, 37 | sourceType: 'module', 38 | ecmaFeatures: { 39 | jsx: true, 40 | }, 41 | }, 42 | // This allows Vue plugin to work with auto imports 43 | // https://github.com/vuejs/eslint-plugin-vue/pull/2422 44 | globals: { 45 | computed: 'readonly', 46 | defineEmits: 'readonly', 47 | defineExpose: 'readonly', 48 | defineProps: 'readonly', 49 | onMounted: 'readonly', 50 | onUnmounted: 'readonly', 51 | reactive: 'readonly', 52 | ref: 'readonly', 53 | shallowReactive: 'readonly', 54 | shallowRef: 'readonly', 55 | toRef: 'readonly', 56 | toRefs: 'readonly', 57 | watch: 'readonly', 58 | watchEffect: 'readonly', 59 | }, 60 | }, 61 | }, 62 | { 63 | name: 'nuxt/vue/rules', 64 | files: [ 65 | '**/*.vue', 66 | ], 67 | languageOptions: { 68 | parser: parserVue, 69 | }, 70 | processor: options.features?.formatters 71 | ? mergeProcessors([ 72 | pluginVue.processors['.vue'], 73 | processorVueBlocks({ 74 | blocks: { 75 | styles: true, 76 | }, 77 | }), 78 | ]) 79 | : pluginVue.processors['.vue'], 80 | rules: { 81 | ...pluginVue.configs.base.rules, 82 | 83 | ...pluginVue.configs['flat/essential'].map(c => c.rules).reduce((acc, c) => ({ ...acc, ...c }), {}), 84 | ...pluginVue.configs['flat/strongly-recommended'].map(c => c.rules).reduce((acc, c) => ({ ...acc, ...c }), {}), 85 | ...pluginVue.configs['flat/recommended'].map(c => c.rules).reduce((acc, c) => ({ ...acc, ...c }), {}), 86 | 87 | // Deprecated in favor of 'vue/block-order' 88 | 'vue/component-tags-order': undefined, 89 | 'vue/block-order': 'warn', 90 | 91 | ...(resolved.features.stylistic 92 | ? { 93 | 'vue/array-bracket-spacing': ['error', 'never'], 94 | 'vue/arrow-spacing': ['error', { after: true, before: true }], 95 | 'vue/block-spacing': ['error', 'always'], 96 | 'vue/block-tag-newline': [ 97 | 'error', 98 | { 99 | multiline: 'always', 100 | singleline: 'always', 101 | }, 102 | ], 103 | 'vue/brace-style': ['error', 'stroustrup', { allowSingleLine: true }], 104 | 'vue/html-indent': ['error', indent], 105 | 'vue/html-quotes': ['error', 'double'], 106 | 'vue/comma-dangle': ['error', commaDangle], 107 | 'vue/comma-spacing': ['error', { after: true, before: false }], 108 | 'vue/comma-style': ['error', 'last'], 109 | 'vue/html-comment-content-spacing': [ 110 | 'error', 111 | 'always', 112 | { exceptions: ['-'] }, 113 | ], 114 | 'vue/key-spacing': ['error', { afterColon: true, beforeColon: false }], 115 | 'vue/keyword-spacing': ['error', { after: true, before: true }], 116 | 'vue/object-curly-newline': 'off', 117 | 'vue/object-curly-spacing': ['error', 'always'], 118 | 'vue/object-property-newline': [ 119 | 'error', 120 | { allowAllPropertiesOnSameLine: true }, 121 | ], 122 | 'vue/one-component-per-file': 'off', 123 | 'vue/operator-linebreak': ['error', 'before'], 124 | 'vue/padding-line-between-blocks': ['error', 'always'], 125 | 'vue/quote-props': ['error', 'consistent-as-needed'], 126 | 'vue/require-default-prop': 'off', 127 | 'vue/space-in-parens': ['error', 'never'], 128 | 'vue/template-curly-spacing': 'error', 129 | 'vue/multiline-html-element-content-newline': ['error', { 130 | ignoreWhenEmpty: true, 131 | ignores: ['pre', 'textarea', 'router-link', 'RouterLink', 'nuxt-link', 'NuxtLink', 'u-link', 'ULink', ...INLINE_ELEMENTS], 132 | allowEmptyLines: false, 133 | }], 134 | 'vue/singleline-html-element-content-newline': ['error', { 135 | ignoreWhenNoAttributes: true, 136 | ignoreWhenEmpty: true, 137 | ignores: ['pre', 'textarea', 'router-link', 'RouterLink', 'nuxt-link', 'NuxtLink', 'u-link', 'ULink', ...INLINE_ELEMENTS], 138 | externalIgnores: [], 139 | }], 140 | } 141 | : { 142 | // Disable Vue's default stylistic rules when stylistic is not enabled 143 | 'vue/html-closing-bracket-newline': undefined, 144 | 'vue/html-closing-bracket-spacing': undefined, 145 | 'vue/html-indent': undefined, 146 | 'vue/html-quotes': undefined, 147 | 'vue/max-attributes-per-line': undefined, 148 | 'vue/multiline-html-element-content-newline': undefined, 149 | 'vue/mustache-interpolation-spacing': undefined, 150 | 'vue/no-multi-spaces': undefined, 151 | 'vue/no-spaces-around-equal-signs-in-attribute': undefined, 152 | 'vue/singleline-html-element-content-newline': undefined, 153 | }), 154 | }, 155 | }, 156 | ] 157 | 158 | for (const config of configs) { 159 | if (config.rules) 160 | config.rules = removeUndefined(config.rules) 161 | } 162 | 163 | return configs 164 | } 165 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/formatters.ts: -------------------------------------------------------------------------------- 1 | import type { StylisticCustomizeOptions } from '@stylistic/eslint-plugin' 2 | import type { Linter } from 'eslint' 3 | import { isPackageExists } from 'local-pkg' 4 | import type { OptionsFormatters } from '../types' 5 | import { ensurePackages, interopDefault, parserPlain } from '../utils' 6 | import { GLOB_CSS, GLOB_GRAPHQL, GLOB_HTML, GLOB_LESS, GLOB_MARKDOWN, GLOB_POSTCSS, GLOB_SCSS, GLOB_SVG, GLOB_XML } from '../globs' 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | type VendoredPrettierOptions = any 10 | 11 | function mergePrettierOptions( 12 | options: VendoredPrettierOptions, 13 | overrides: VendoredPrettierOptions = {}, 14 | ): VendoredPrettierOptions { 15 | return { 16 | ...options, 17 | ...overrides, 18 | plugins: [ 19 | ...(overrides.plugins || []), 20 | ...(options.plugins || []), 21 | ], 22 | } 23 | } 24 | 25 | export async function formatters( 26 | options: OptionsFormatters | boolean = {}, 27 | stylistic: StylisticCustomizeOptions, 28 | ): Promise { 29 | if (!options) 30 | return [] 31 | 32 | if (options === true) { 33 | const isPrettierPluginXmlInScope = isPackageExists('@prettier/plugin-xml') 34 | options = { 35 | css: true, 36 | graphql: true, 37 | html: true, 38 | // Markdown is disabled by default as many Nuxt projects use MDC with @nuxt/content, 39 | // where Prettier doesn't fully understand. 40 | markdown: false, 41 | svg: isPrettierPluginXmlInScope, 42 | xml: isPrettierPluginXmlInScope, 43 | } 44 | } 45 | 46 | await ensurePackages([ 47 | 'eslint-plugin-format', 48 | (options.xml || options.svg) ? '@prettier/plugin-xml' : undefined, 49 | ]) 50 | 51 | const { 52 | indent, 53 | quotes, 54 | semi, 55 | } = { 56 | indent: 2, 57 | quotes: 'single', 58 | semi: false, 59 | ...stylistic, 60 | } 61 | 62 | const prettierOptions: VendoredPrettierOptions = Object.assign( 63 | { 64 | endOfLine: 'auto', 65 | printWidth: 120, 66 | semi, 67 | singleQuote: quotes === 'single', 68 | tabWidth: typeof indent === 'number' ? indent : 2, 69 | trailingComma: 'all', 70 | useTabs: indent === 'tab', 71 | } satisfies VendoredPrettierOptions, 72 | options.prettierOptions || {}, 73 | ) 74 | 75 | const prettierXmlOptions: VendoredPrettierOptions = { 76 | xmlQuoteAttributes: 'double', 77 | xmlSelfClosingSpace: true, 78 | xmlSortAttributesByKey: false, 79 | xmlWhitespaceSensitivity: 'ignore', 80 | } 81 | 82 | const dprintOptions = Object.assign( 83 | { 84 | indentWidth: typeof indent === 'number' ? indent : 2, 85 | quoteStyle: quotes === 'single' ? 'preferSingle' : 'preferDouble', 86 | useTabs: indent === 'tab', 87 | }, 88 | options.dprintOptions || {}, 89 | ) 90 | 91 | const pluginFormat = await interopDefault(import('eslint-plugin-format')) 92 | 93 | const configs: Linter.Config[] = [ 94 | { 95 | name: 'nuxt/formatter/setup', 96 | plugins: { 97 | format: pluginFormat, 98 | }, 99 | }, 100 | ] 101 | 102 | if (options.css) { 103 | configs.push( 104 | { 105 | files: [GLOB_CSS, GLOB_POSTCSS], 106 | languageOptions: { 107 | parser: parserPlain, 108 | }, 109 | name: 'nuxt/formatter/css', 110 | rules: { 111 | 'format/prettier': [ 112 | 'error', 113 | mergePrettierOptions(prettierOptions, { 114 | parser: 'css', 115 | }), 116 | ], 117 | }, 118 | }, 119 | { 120 | files: [GLOB_SCSS], 121 | languageOptions: { 122 | parser: parserPlain, 123 | }, 124 | name: 'nuxt/formatter/scss', 125 | rules: { 126 | 'format/prettier': [ 127 | 'error', 128 | mergePrettierOptions(prettierOptions, { 129 | parser: 'scss', 130 | }), 131 | ], 132 | }, 133 | }, 134 | { 135 | files: [GLOB_LESS], 136 | languageOptions: { 137 | parser: parserPlain, 138 | }, 139 | name: 'nuxt/formatter/less', 140 | rules: { 141 | 'format/prettier': [ 142 | 'error', 143 | mergePrettierOptions(prettierOptions, { 144 | parser: 'less', 145 | }), 146 | ], 147 | }, 148 | }, 149 | ) 150 | } 151 | 152 | if (options.html) { 153 | configs.push({ 154 | files: [GLOB_HTML], 155 | languageOptions: { 156 | parser: parserPlain, 157 | }, 158 | name: 'nuxt/formatter/html', 159 | rules: { 160 | 'format/prettier': [ 161 | 'error', 162 | mergePrettierOptions(prettierOptions, { 163 | parser: 'html', 164 | }), 165 | ], 166 | }, 167 | }) 168 | } 169 | 170 | if (options.xml) { 171 | configs.push({ 172 | files: [GLOB_XML], 173 | languageOptions: { 174 | parser: parserPlain, 175 | }, 176 | name: 'nuxt/formatter/xml', 177 | rules: { 178 | 'format/prettier': [ 179 | 'error', 180 | mergePrettierOptions({ ...prettierXmlOptions, ...prettierOptions }, { 181 | parser: 'xml', 182 | plugins: [ 183 | '@prettier/plugin-xml', 184 | ], 185 | }), 186 | ], 187 | }, 188 | }) 189 | } 190 | if (options.svg) { 191 | configs.push({ 192 | files: [GLOB_SVG], 193 | languageOptions: { 194 | parser: parserPlain, 195 | }, 196 | name: 'nuxt/formatter/svg', 197 | rules: { 198 | 'format/prettier': [ 199 | 'error', 200 | mergePrettierOptions({ ...prettierXmlOptions, ...prettierOptions }, { 201 | parser: 'xml', 202 | plugins: [ 203 | '@prettier/plugin-xml', 204 | ], 205 | }), 206 | ], 207 | }, 208 | }) 209 | } 210 | 211 | if (options.markdown) { 212 | const formater = options.markdown === true 213 | ? 'prettier' 214 | : options.markdown 215 | 216 | configs.push({ 217 | files: [GLOB_MARKDOWN], 218 | languageOptions: { 219 | parser: parserPlain, 220 | }, 221 | name: 'nuxt/formatter/markdown', 222 | rules: { 223 | [`format/${formater}`]: [ 224 | 'error', 225 | formater === 'prettier' 226 | ? mergePrettierOptions(prettierOptions, { 227 | embeddedLanguageFormatting: 'off', 228 | parser: 'markdown', 229 | }) 230 | : { 231 | ...dprintOptions, 232 | language: 'markdown', 233 | }, 234 | ], 235 | }, 236 | }) 237 | } 238 | 239 | if (options.graphql) { 240 | configs.push({ 241 | files: [GLOB_GRAPHQL], 242 | languageOptions: { 243 | parser: parserPlain, 244 | }, 245 | name: 'nuxt/formatter/graphql', 246 | rules: { 247 | 'format/prettier': [ 248 | 'error', 249 | mergePrettierOptions(prettierOptions, { 250 | parser: 'graphql', 251 | }), 252 | ], 253 | }, 254 | }) 255 | } 256 | 257 | return configs 258 | } 259 | -------------------------------------------------------------------------------- /packages/eslint-config/test/__snapshots__/flat-compose.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`flat config composition > custom src dirs 1`] = ` 4 | [ 5 | { 6 | "ignores": [ 7 | "**/node_modules", 8 | "**/*.iml", 9 | "**/.idea", 10 | "**/*.log", 11 | "**/.nuxt", 12 | "**/.output", 13 | "**/.yarn/cache", 14 | "**/.yarn/*state*", 15 | "**/dist", 16 | "**/.eslintcache", 17 | ], 18 | "name": "gitignore", 19 | }, 20 | { 21 | "ignores": [ 22 | "**/dist", 23 | "**/node_modules", 24 | "**/.nuxt", 25 | "**/.output", 26 | "**/.vercel", 27 | "**/.netlify", 28 | "**/public", 29 | ], 30 | }, 31 | { 32 | "name": "nuxt/javascript", 33 | }, 34 | { 35 | "name": "nuxt/typescript/setup", 36 | }, 37 | { 38 | "files": [ 39 | "**/*.ts", 40 | "**/*.tsx", 41 | "**/*.mts", 42 | "**/*.cts", 43 | "**/*.vue", 44 | ], 45 | "name": "nuxt/typescript/rules", 46 | }, 47 | { 48 | "name": "nuxt/vue/setup", 49 | }, 50 | { 51 | "files": [ 52 | "**/*.vue", 53 | ], 54 | "name": "nuxt/vue/rules", 55 | }, 56 | { 57 | "name": "nuxt/import/rules", 58 | }, 59 | { 60 | "name": "nuxt/setup", 61 | }, 62 | { 63 | "files": [ 64 | "src1/components/**/*.server.{js,ts,jsx,tsx,vue}", 65 | "src1/layouts/**/*.{js,ts,jsx,tsx,vue}", 66 | "src1/pages/**/*.{js,ts,jsx,tsx,vue}", 67 | "src2/components/**/*.server.{js,ts,jsx,tsx,vue}", 68 | "src2/layouts/**/*.{js,ts,jsx,tsx,vue}", 69 | "src2/pages/**/*.{js,ts,jsx,tsx,vue}", 70 | ], 71 | "name": "nuxt/vue/single-root", 72 | }, 73 | { 74 | "name": "nuxt/rules", 75 | }, 76 | { 77 | "files": [ 78 | "**/.config/nuxt.?([cm])[jt]s?(x)", 79 | "**/nuxt.config.?([cm])[jt]s?(x)", 80 | ], 81 | "name": "nuxt/nuxt-config", 82 | }, 83 | { 84 | "files": [ 85 | "src1/app.{js,ts,jsx,tsx,vue}", 86 | "src1/components/*/**/*.{js,ts,jsx,tsx,vue}", 87 | "src1/error.{js,ts,jsx,tsx,vue}", 88 | "src1/layouts/**/*.{js,ts,jsx,tsx,vue}", 89 | "src1/pages/**/*.{js,ts,jsx,tsx,vue}", 90 | "src2/app.{js,ts,jsx,tsx,vue}", 91 | "src2/components/*/**/*.{js,ts,jsx,tsx,vue}", 92 | "src2/error.{js,ts,jsx,tsx,vue}", 93 | "src2/layouts/**/*.{js,ts,jsx,tsx,vue}", 94 | "src2/pages/**/*.{js,ts,jsx,tsx,vue}", 95 | ], 96 | "name": "nuxt/disables/routes", 97 | }, 98 | ] 99 | `; 100 | 101 | exports[`flat config composition > empty 1`] = ` 102 | [ 103 | { 104 | "ignores": [ 105 | "**/node_modules", 106 | "**/*.iml", 107 | "**/.idea", 108 | "**/*.log", 109 | "**/.nuxt", 110 | "**/.output", 111 | "**/.yarn/cache", 112 | "**/.yarn/*state*", 113 | "**/dist", 114 | "**/.eslintcache", 115 | ], 116 | "name": "gitignore", 117 | }, 118 | { 119 | "ignores": [ 120 | "**/dist", 121 | "**/node_modules", 122 | "**/.nuxt", 123 | "**/.output", 124 | "**/.vercel", 125 | "**/.netlify", 126 | "**/public", 127 | ], 128 | }, 129 | { 130 | "name": "nuxt/javascript", 131 | }, 132 | { 133 | "name": "nuxt/typescript/setup", 134 | }, 135 | { 136 | "files": [ 137 | "**/*.ts", 138 | "**/*.tsx", 139 | "**/*.mts", 140 | "**/*.cts", 141 | "**/*.vue", 142 | ], 143 | "name": "nuxt/typescript/rules", 144 | }, 145 | { 146 | "name": "nuxt/vue/setup", 147 | }, 148 | { 149 | "files": [ 150 | "**/*.vue", 151 | ], 152 | "name": "nuxt/vue/rules", 153 | }, 154 | { 155 | "name": "nuxt/import/rules", 156 | }, 157 | { 158 | "name": "nuxt/setup", 159 | }, 160 | { 161 | "files": [ 162 | "app/components/**/*.server.{js,ts,jsx,tsx,vue}", 163 | "app/layouts/**/*.{js,ts,jsx,tsx,vue}", 164 | "app/pages/**/*.{js,ts,jsx,tsx,vue}", 165 | "components/**/*.server.{js,ts,jsx,tsx,vue}", 166 | "layouts/**/*.{js,ts,jsx,tsx,vue}", 167 | "pages/**/*.{js,ts,jsx,tsx,vue}", 168 | ], 169 | "name": "nuxt/vue/single-root", 170 | }, 171 | { 172 | "name": "nuxt/rules", 173 | }, 174 | { 175 | "files": [ 176 | "**/.config/nuxt.?([cm])[jt]s?(x)", 177 | "**/nuxt.config.?([cm])[jt]s?(x)", 178 | ], 179 | "name": "nuxt/nuxt-config", 180 | }, 181 | { 182 | "files": [ 183 | "app.{js,ts,jsx,tsx,vue}", 184 | "app/app.{js,ts,jsx,tsx,vue}", 185 | "app/components/*/**/*.{js,ts,jsx,tsx,vue}", 186 | "app/error.{js,ts,jsx,tsx,vue}", 187 | "app/layouts/**/*.{js,ts,jsx,tsx,vue}", 188 | "app/pages/**/*.{js,ts,jsx,tsx,vue}", 189 | "components/*/**/*.{js,ts,jsx,tsx,vue}", 190 | "error.{js,ts,jsx,tsx,vue}", 191 | "layouts/**/*.{js,ts,jsx,tsx,vue}", 192 | "pages/**/*.{js,ts,jsx,tsx,vue}", 193 | ], 194 | "name": "nuxt/disables/routes", 195 | }, 196 | ] 197 | `; 198 | 199 | exports[`flat config composition > non-standalone 1`] = ` 200 | [ 201 | { 202 | "name": "nuxt/setup", 203 | }, 204 | { 205 | "files": [ 206 | "app/components/**/*.server.{js,ts,jsx,tsx,vue}", 207 | "app/layouts/**/*.{js,ts,jsx,tsx,vue}", 208 | "app/pages/**/*.{js,ts,jsx,tsx,vue}", 209 | "components/**/*.server.{js,ts,jsx,tsx,vue}", 210 | "layouts/**/*.{js,ts,jsx,tsx,vue}", 211 | "pages/**/*.{js,ts,jsx,tsx,vue}", 212 | ], 213 | "name": "nuxt/vue/single-root", 214 | }, 215 | { 216 | "name": "nuxt/rules", 217 | }, 218 | { 219 | "files": [ 220 | "**/.config/nuxt.?([cm])[jt]s?(x)", 221 | "**/nuxt.config.?([cm])[jt]s?(x)", 222 | ], 223 | "name": "nuxt/nuxt-config", 224 | }, 225 | { 226 | "files": [ 227 | "app.{js,ts,jsx,tsx,vue}", 228 | "app/app.{js,ts,jsx,tsx,vue}", 229 | "app/components/*/**/*.{js,ts,jsx,tsx,vue}", 230 | "app/error.{js,ts,jsx,tsx,vue}", 231 | "app/layouts/**/*.{js,ts,jsx,tsx,vue}", 232 | "app/pages/**/*.{js,ts,jsx,tsx,vue}", 233 | "components/*/**/*.{js,ts,jsx,tsx,vue}", 234 | "error.{js,ts,jsx,tsx,vue}", 235 | "layouts/**/*.{js,ts,jsx,tsx,vue}", 236 | "pages/**/*.{js,ts,jsx,tsx,vue}", 237 | ], 238 | "name": "nuxt/disables/routes", 239 | }, 240 | ] 241 | `; 242 | 243 | exports[`flat config composition > with stylistic 1`] = ` 244 | [ 245 | { 246 | "ignores": [ 247 | "**/node_modules", 248 | "**/*.iml", 249 | "**/.idea", 250 | "**/*.log", 251 | "**/.nuxt", 252 | "**/.output", 253 | "**/.yarn/cache", 254 | "**/.yarn/*state*", 255 | "**/dist", 256 | "**/.eslintcache", 257 | ], 258 | "name": "gitignore", 259 | }, 260 | { 261 | "ignores": [ 262 | "**/dist", 263 | "**/node_modules", 264 | "**/.nuxt", 265 | "**/.output", 266 | "**/.vercel", 267 | "**/.netlify", 268 | "**/public", 269 | ], 270 | }, 271 | { 272 | "name": "nuxt/javascript", 273 | }, 274 | { 275 | "name": "nuxt/typescript/setup", 276 | }, 277 | { 278 | "files": [ 279 | "**/*.ts", 280 | "**/*.tsx", 281 | "**/*.mts", 282 | "**/*.cts", 283 | "**/*.vue", 284 | ], 285 | "name": "nuxt/typescript/rules", 286 | }, 287 | { 288 | "name": "nuxt/vue/setup", 289 | }, 290 | { 291 | "files": [ 292 | "**/*.vue", 293 | ], 294 | "name": "nuxt/vue/rules", 295 | }, 296 | { 297 | "name": "nuxt/import/rules", 298 | }, 299 | { 300 | "name": "nuxt/setup", 301 | }, 302 | { 303 | "files": [ 304 | "app/components/**/*.server.{js,ts,jsx,tsx,vue}", 305 | "app/layouts/**/*.{js,ts,jsx,tsx,vue}", 306 | "app/pages/**/*.{js,ts,jsx,tsx,vue}", 307 | "components/**/*.server.{js,ts,jsx,tsx,vue}", 308 | "layouts/**/*.{js,ts,jsx,tsx,vue}", 309 | "pages/**/*.{js,ts,jsx,tsx,vue}", 310 | ], 311 | "name": "nuxt/vue/single-root", 312 | }, 313 | { 314 | "name": "nuxt/rules", 315 | }, 316 | { 317 | "files": [ 318 | "**/.config/nuxt.?([cm])[jt]s?(x)", 319 | "**/nuxt.config.?([cm])[jt]s?(x)", 320 | ], 321 | "name": "nuxt/nuxt-config", 322 | }, 323 | { 324 | "files": [ 325 | "**/.config/nuxt.?([cm])[jt]s?(x)", 326 | "**/nuxt.config.?([cm])[jt]s?(x)", 327 | ], 328 | "name": "nuxt/sort-config", 329 | }, 330 | { 331 | "files": [ 332 | "**/*.?([cm])[jt]s?(x)", 333 | "**/*.vue", 334 | ], 335 | "name": "nuxt/stylistic", 336 | }, 337 | { 338 | "files": [ 339 | "app.{js,ts,jsx,tsx,vue}", 340 | "app/app.{js,ts,jsx,tsx,vue}", 341 | "app/components/*/**/*.{js,ts,jsx,tsx,vue}", 342 | "app/error.{js,ts,jsx,tsx,vue}", 343 | "app/layouts/**/*.{js,ts,jsx,tsx,vue}", 344 | "app/pages/**/*.{js,ts,jsx,tsx,vue}", 345 | "components/*/**/*.{js,ts,jsx,tsx,vue}", 346 | "error.{js,ts,jsx,tsx,vue}", 347 | "layouts/**/*.{js,ts,jsx,tsx,vue}", 348 | "pages/**/*.{js,ts,jsx,tsx,vue}", 349 | ], 350 | "name": "nuxt/disables/routes", 351 | }, 352 | ] 353 | `; 354 | -------------------------------------------------------------------------------- /docs/content/1.packages/0.module.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ESLint Module 3 | --- 4 | 5 | All-in-one ESLint integration for Nuxt. It generates a project-aware [ESLint flat config](https://eslint.org/docs/latest/use/configure/configuration-files-new) and provides the ability to optionally run ESLint check along side the dev server. 6 | 7 | :::callout{icon="i-ph-lightbulb-duotone"} 8 | This module is designed for the [new ESLint flat config format](https://eslint.org/docs/latest/use/configure/configuration-files-new) which is the [default format since ESLint v9](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/). Flat config is supported since ESLint v8.45.0 so you can use any version of ESLint later than that. We recommend you to use the latest version of ESLint to get the best experience. 9 |

10 | The legacy `.eslintrc` config is **not supported** by this module. We highly recommend you to migrate over the flat config to be future-proof. 11 | ::: 12 | 13 | ::read-more 14 | --- 15 | to: https://github.com/nuxt/eslint/tree/main/packages/module 16 | color: gray 17 | icon: i-simple-icons-github 18 | --- 19 | Source code on GitHub 20 | :: 21 | 22 | ## Features 23 | 24 | - [ESLint flat config](https://eslint.org/docs/latest/use/configure/configuration-files-new), composable, customizable and future-proof. 25 | - Project-aware Nuxt-specific settings, also supports [layers](https://nuxt.com/docs/getting-started/layers). 26 | - [Nuxt DevTools](https://github.com/nuxt/devtools) integration powered by [`@eslint/config-inspector`](https://github.com/eslint/config-inspector). 27 | - Extensible with other modules. 28 | - Optional [dev server checker](#dev-server-checker) integration. 29 | 30 | ## Quick Setup 31 | 32 | Run the following command to add the `@nuxt/eslint` module to your project: 33 | 34 | ```bash [Terminal] 35 | npx nuxi module add eslint 36 | ``` 37 | 38 | Once you start your Nuxt app, a `eslint.config.mjs` file will be generated under your project root. You can customize it as needed. 39 | 40 | :::callout{icon="i-catppuccin-typescript"} 41 | 42 | If you are using TypeScript, you need to install the `typescript` in your project: 43 | 44 | ::code-group 45 | ```bash [yarn] 46 | yarn add --dev typescript 47 | ``` 48 | ```bash [npm] 49 | npm install -D typescript 50 | ``` 51 | ```bash [pnpm] 52 | pnpm add -D typescript 53 | ``` 54 | ```bash [bun] 55 | bun add -D typescript 56 | ``` 57 | :: 58 | 59 | ::: 60 | 61 | ### Manual Setup 62 | 63 | ::code-group 64 | ```bash [yarn] 65 | yarn add --dev @nuxt/eslint eslint typescript 66 | ``` 67 | ```bash [npm] 68 | npm install -D @nuxt/eslint eslint typescript 69 | ``` 70 | ```bash [pnpm] 71 | pnpm add -D @nuxt/eslint eslint typescript 72 | ``` 73 | ```bash [bun] 74 | bun add -D @nuxt/eslint eslint typescript 75 | ``` 76 | :: 77 | 78 | ```ts [nuxt.config.ts] 79 | export default defineNuxtConfig({ 80 | modules: [ 81 | '@nuxt/eslint' 82 | ], 83 | eslint: { 84 | // options here 85 | } 86 | }) 87 | ``` 88 | 89 | And create an `eslint.config.mjs` file under your project root, with the following content: 90 | 91 | ```js [eslint.config.mjs] 92 | import withNuxt from './.nuxt/eslint.config.mjs' 93 | 94 | export default withNuxt( 95 | // your custom flat configs go here, for example: 96 | // { 97 | // files: ['**/*.ts', '**/*.tsx'], 98 | // rules: { 99 | // 'no-console': 'off' // allow console.log in TypeScript files 100 | // } 101 | // }, 102 | // { 103 | // ... 104 | // } 105 | ) 106 | ``` 107 | 108 | `withNuxt` will take the rest arguments of flat configs and append them after Nuxt flat config items. You can either use the [Nuxt DevTools](https://github.com/nuxt/devtools) panel to inspect the resolved ESLint flat config, or manually run [`npx @eslint/config-inspector`](https://github.com/eslint/config-inspector). 109 | 110 | ## Recipes 111 | 112 | ### VS Code 113 | 114 | ESLint v9.x support was added in the [ESLint VS Code extension (`vscode-eslint`)](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) v3.0.10. 115 | In versions of `vscode-eslint` prior to v3.0.10, the new configuration system is not enabled by default. To enable support for the new configuration files, edit your `.vscode/settings.json` file and add the following: 116 | 117 | ```json [.vscode/settings.json] 118 | { 119 | // Required in vscode-eslint < v3.0.10 only 120 | "eslint.useFlatConfig": true 121 | } 122 | ``` 123 | 124 | ### NPM Scripts 125 | 126 | Add the below to lint commands to your `package.json` script section: 127 | 128 | ```json 129 | { 130 | "scripts": { 131 | ... 132 | "lint": "eslint .", 133 | "lint:fix": "eslint . --fix", 134 | ... 135 | }, 136 | } 137 | ``` 138 | 139 | Run the `npm run lint` command to check if the code style is correct or run `npm run lint:fix` to automatically fix issues. 140 | 141 | ### Prettier 142 | 143 | This module does not enable stylistic/formatting rules by default. You can use Prettier alongside directly. 144 | 145 | ### ESLint Stylistic 146 | 147 | If you prefer to use ESLint for formatting, we also directly integrate with [ESLint Stylistic](https://eslint.style/) to make it easy. You can opt-in by setting `config.stylistic` to `true` in the `eslint` module options. 148 | 149 | ```ts [nuxt.config.ts] 150 | export default defineNuxtConfig({ 151 | modules: [ 152 | '@nuxt/eslint' 153 | ], 154 | eslint: { 155 | config: { 156 | stylistic: true // <--- 157 | } 158 | } 159 | }) 160 | ``` 161 | 162 | You can also pass an object to customize the rules: 163 | 164 | ```ts [nuxt.config.ts] 165 | export default defineNuxtConfig({ 166 | modules: [ 167 | '@nuxt/eslint' 168 | ], 169 | eslint: { 170 | config: { 171 | stylistic: { 172 | indent: 'tab', 173 | semi: true, 174 | // ... 175 | } 176 | } 177 | } 178 | }) 179 | ``` 180 | 181 | Learn more about all the available options in the [ESLint Stylistic documentation](https://eslint.style/guide/config-presets#configuration-factory). 182 | 183 | ### Config Customizations 184 | 185 | `withNuxt()` returns a chainable [`FlatConfigComposer` instance](https://github.com/antfu/eslint-flat-config-utils#composer) from [`eslint-flat-config-utils`](https://github.com/antfu/eslint-flat-config-utils) which allows you to manipulate the ESLint flat config with ease. 186 | 187 | ```ts [eslint.config.mjs] 188 | import withNuxt from './.nuxt/eslint.config.mjs' 189 | 190 | export default withNuxt( 191 | // ...Custom flat configs append after nuxt's configs 192 | ) 193 | .prepend( 194 | // ...Prepend some flat configs in front 195 | ) 196 | // Override some rules in a specific config, based on their name 197 | .override('nuxt/typescript/rules', { 198 | rules: { 199 | // ...Override rules, for example: 200 | '@typescript-eslint/ban-types': 'off' 201 | } 202 | }) 203 | // ...you can chain more operations as needed 204 | ``` 205 | 206 | You can learn more about the options available with the types can JSDocs of the instance. 207 | 208 | For all the available configure names, please use the DevTools to inspect. 209 | 210 | ### Config Inspector 211 | 212 | This module ships [ESLint Config Inspector](https://github.com/eslint/config-inspector) in your [Nuxt DevTools](https://github.com/nuxt/devtools). You can inspect the resolved ESLint flat config there: 213 | 214 | ![ESLint Config Inspector in Nuxt DevTools](/images/devtools-inspector.png) 215 | 216 | ### Dev Server Checker 217 | 218 | Usually you don't need this setting as most IDEs are capable of running ESLint directly to inform you of issues. It's also possible to set up a [pre-commit hook with `lint-staged`](https://github.com/lint-staged/lint-staged) to guard your codebase before committing. 219 | 220 | That said, if you are working on a team using a variety of IDEs you may still want to run the ESLint checker along side the dev server (which might slow down the dev server) to ensure issues are raised no matter the environment. You can enable it by setting `checker` to `true` in the `eslint` module options. 221 | 222 | ```ts [nuxt.config.ts] 223 | export default defineNuxtConfig({ 224 | modules: [ 225 | '@nuxt/eslint' 226 | ], 227 | eslint: { 228 | checker: true // <--- 229 | } 230 | }) 231 | ``` 232 | 233 | You will need to install extra dependencies `vite-plugin-eslint2` for Vite, or `eslint-webpack-plugin` if you are using the [Webpack builder](https://nuxt.com/docs/getting-started/configuration#with-webpack) for Nuxt 3. 234 | 235 | ```bash 236 | # For Vite 237 | npm i -D vite-plugin-eslint2 238 | 239 | # For Webpack 240 | npm i -D eslint-webpack-plugin 241 | ``` 242 | 243 | This enables a similar experience to using [`@nuxtjs/eslint-module`](https://github.com/nuxt-modules/eslint). 244 | 245 | The checker runs in flat config mode by default. If you want to run it in legacy mode, you will need to set `configType` to `eslintrc` 246 | 247 | ```ts [nuxt.config.ts] 248 | export default defineNuxtConfig({ 249 | modules: [ 250 | '@nuxt/eslint' 251 | ], 252 | eslint: { 253 | checker: { 254 | configType: 'eslintrc' // <--- (consider migrating to flat config if possible) 255 | } 256 | } 257 | }) 258 | ``` 259 | 260 | ### Custom Config Presets 261 | 262 | By default, this module installs the JS, TS and Vue plugins with their recommended rules. This might already be covered by your config presets, so in that case you can disable the default setup by setting the `standalone` option to `false`. 263 | 264 | ```ts [nuxt.config.ts] 265 | export default defineNuxtConfig({ 266 | modules: [ 267 | '@nuxt/eslint' 268 | ], 269 | eslint: { 270 | config: { 271 | standalone: false // <--- 272 | } 273 | } 274 | }) 275 | ``` 276 | 277 | This ensures the module only generates Nuxt-specific rules so that you can merge it with your own config presets. 278 | 279 | For example, with [`@antfu/eslint-config`](https://github.com/antfu/eslint-config): 280 | 281 | ```js [eslint.config.mjs] 282 | // @ts-check 283 | import antfu from '@antfu/eslint-config' 284 | import withNuxt from './.nuxt/eslint.config.mjs' 285 | 286 | export default withNuxt( 287 | antfu({ 288 | // ...@antfu/eslint-config options 289 | }), 290 | // ...your other rules 291 | ) 292 | ``` 293 | 294 | ### Auto-Init 295 | 296 | Upon server start, the module will look for an `eslint.config.*` file and generate one if it doesn't exist for easier usage. You can opt-out this be setting `autoInit` to `false` in the `eslint.config` module options. 297 | 298 | ```ts [nuxt.config.ts] 299 | export default defineNuxtConfig({ 300 | modules: [ 301 | '@nuxt/eslint' 302 | ], 303 | eslint: { 304 | config: { 305 | autoInit: false // <--- disable auto-init 306 | } 307 | } 308 | }) 309 | ``` 310 | --------------------------------------------------------------------------------