= {
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 |
41 |
42 |
43 | Nuxt ESLint
44 |
45 |
46 |
47 |
48 |
56 |
57 |
58 |
62 |
66 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | Published under MIT License
86 |
87 |
88 |
89 |
90 |
98 |
106 |
107 |
108 |
109 |
114 |
115 |
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 |
71 |
72 |
78 |
79 |
83 |
87 |
91 |
92 |
93 |
94 |
98 |
99 |
100 |
101 |
105 |
109 |
110 |
111 |
112 |
113 |
114 |
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 |
49 |
50 |
51 |
56 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
83 |
84 |
85 |
86 |
91 |
95 |
96 |
97 |
98 |
102 |
103 |
104 |
105 |
106 |
107 |
120 |
121 |
122 |
123 |
124 |
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 |
2 |
10 |
14 |
18 |
24 |
28 |
34 |
38 |
39 |
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 | 
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 |
--------------------------------------------------------------------------------