Skip to content

Commit

Permalink
feat(eslint-config): support for multiple src dirs and auto infer dir…
Browse files Browse the repository at this point in the history
…ectories structure (#370)
  • Loading branch information
antfu committed Mar 29, 2024
1 parent fda2a07 commit a3625fd
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 74 deletions.
40 changes: 16 additions & 24 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,22 @@ export default createConfigForNuxt({
stylistic: true,
},
dirs: {
src: 'playground',
pages: [
'playground/pages',
'docs/pages',
],
layouts: [
'playground/layouts',
'docs/layouts',
],
components: [
'playground/components',
'docs/components',
],
},
}).append(
{
ignores: [
'packages-legacy/**',
src: [
'playground',
'docs',
],
},
{
files: ['docs/**/*.vue'],
rules: {
'vue/no-v-html': 'off',
})
.append(
{
ignores: [
'packages-legacy/**',
],
},
},
)
{
files: ['docs/**/*.vue'],
rules: {
'vue/no-v-html': 'off',
},
},
)
2 changes: 1 addition & 1 deletion packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"dependencies": {
"@eslint/js": "^8.57.0",
"@nuxt/eslint-plugin": "workspace:*",
"@rushstack/eslint-patch": "^1.9.0",
"@rushstack/eslint-patch": "^1.10.1",
"@stylistic/eslint-plugin": "^1.7.0",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
Expand Down
12 changes: 7 additions & 5 deletions packages/eslint-config/src/flat/configs/disables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@ import { join } from 'pathe'
import { GLOB_EXTS } from '../constants'
import type { NuxtESLintConfigOptions } from '../types'
import type { FlatConfigItem } from 'eslint-flat-config-utils'
import { resolveOptions } from '../utils'

export default function disables(options: NuxtESLintConfigOptions): FlatConfigItem[] {
const dirs = options.dirs ?? {}
const resolved = resolveOptions(options)
const dirs = resolved.dirs
const nestedGlobPattern = `**/*.${GLOB_EXTS}`

const fileRoutes = [
// These files must have one-word names as they have a special meaning in Nuxt.
...dirs.layers?.flatMap(layersDir => [
...dirs.src.flatMap(layersDir => [
join(layersDir, `app.${GLOB_EXTS}`),
join(layersDir, `error.${GLOB_EXTS}`),
]) || [],

// Layouts and pages are not used directly by users so they can have one-word names.
...(dirs.layouts?.map(layoutsDir => join(layoutsDir, nestedGlobPattern)) || []),
...(dirs.pages?.map(pagesDir => join(pagesDir, nestedGlobPattern)) || []),
...(dirs.layouts.map(layoutsDir => join(layoutsDir, nestedGlobPattern)) || []),
...(dirs.pages.map(pagesDir => join(pagesDir, nestedGlobPattern)) || []),

// These files should have multiple words in their names as they are within subdirectories.
...(dirs.components?.map(componentsDir => join(componentsDir, '*', nestedGlobPattern)) || []),
...(dirs.components.map(componentsDir => join(componentsDir, '*', nestedGlobPattern)) || []),
]

const configs: FlatConfigItem[] = []
Expand Down
4 changes: 3 additions & 1 deletion packages/eslint-config/src/flat/configs/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import type { NuxtESLintConfigOptions } from '../types'
import nuxtPlugin from '@nuxt/eslint-plugin'
import { GLOB_EXTS } from '../constants'
import type { FlatConfigItem } from 'eslint-flat-config-utils'
import { resolveOptions } from '../utils'

export default function nuxt(options: NuxtESLintConfigOptions): FlatConfigItem[] {
const dirs = options.dirs ?? {}
const resolved = resolveOptions(options)
const dirs = resolved.dirs

const fileSingleRoot = [
...(dirs.layouts?.map(layoutsDir => join(layoutsDir, `**/*.${GLOB_EXTS}`)) || []),
Expand Down
6 changes: 4 additions & 2 deletions packages/eslint-config/src/flat/configs/vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import * as parserTs from '@typescript-eslint/parser'
// @ts-expect-error missing types
import pluginVue from 'eslint-plugin-vue'
import type { NuxtESLintConfigOptions } from '../types'
import { removeUndefined } from '../utils'
import { removeUndefined, resolveOptions } from '../utils'
import type { FlatConfigItem } from 'eslint-flat-config-utils'

export default function vue(options: NuxtESLintConfigOptions): FlatConfigItem[] {
const resolved = resolveOptions(options)

return [
{
name: 'nuxt:setup-vue',
Expand Down Expand Up @@ -92,7 +94,7 @@ export default function vue(options: NuxtESLintConfigOptions): FlatConfigItem[]
'prefer-spread': 'error', // ts transpiles spread to apply, so no need for manual apply
'valid-typeof': 'off', // ts(2367)

...(options.features?.stylistic
...(resolved.features.stylistic
? {}
: {
// Disable Vue's default stylistic rules when stylistic is not enabled
Expand Down
15 changes: 9 additions & 6 deletions packages/eslint-config/src/flat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import stylistic from './configs/stylistic'
import type { FlatConfigItem, ResolvableFlatConfig } from 'eslint-flat-config-utils'
import { FlatConfigPipeline } from 'eslint-flat-config-utils'
import { pipe } from 'eslint-flat-config-utils'
import { resolveOptions } from './utils'

export * from './types'

Expand All @@ -34,24 +35,26 @@ export function defineFlatConfigs(
export function createConfigForNuxt(options: NuxtESLintConfigOptions = {}): FlatConfigPipeline<FlatConfigItem> {
const pipeline = pipe()

if (options.features?.standalone !== false) {
const resolved = resolveOptions(options)

if (resolved.features.standalone !== false) {
pipeline.append(
base(),
javascript(),
typescript(),
vue(options),
vue(resolved),
)
}

if (options.features?.stylistic) {
if (resolved.features.stylistic) {
pipeline.append(
stylistic(options.features.stylistic === true ? {} : options.features.stylistic),
stylistic(resolved.features.stylistic === true ? {} : resolved.features.stylistic),
)
}

pipeline.append(
nuxt(options),
disables(options),
nuxt(resolved),
disables(resolved),
)

return pipeline
Expand Down
19 changes: 13 additions & 6 deletions packages/eslint-config/src/flat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ export interface NuxtESLintConfigOptions {
/**
* Nuxt source directory
*/
src?: string
src?: string[]

/**
* Root directory for nuxt project
*/
root?: string[]

/**
* Directory for pages
Expand Down Expand Up @@ -67,12 +72,14 @@ export interface NuxtESLintConfigOptions {
* Directory for server
*/
servers?: string[]

/**
* Directory for layers
*/
layers?: string[]
}
}

type NotNill<T> = T extends null | undefined ? never : T

export interface NuxtESLintConfigOptionsResolved {
features: Required<NotNill<NuxtESLintFeaturesOptions>>
dirs: Required<NotNill<NuxtESLintConfigOptions['dirs']>>
}

export type Awaitable<T> = T | Promise<T>
38 changes: 38 additions & 0 deletions packages/eslint-config/src/flat/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
import type { NuxtESLintConfigOptions, NuxtESLintConfigOptionsResolved } from '../flat'

export function removeUndefined<T extends object>(obj: T): T {
return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)) as T
}

export function resolveOptions(
config: NuxtESLintConfigOptions,
): NuxtESLintConfigOptionsResolved {
if ('__resolved' in config) {
return config as NuxtESLintConfigOptionsResolved
}

const dirs = {
...config.dirs,
} as NuxtESLintConfigOptionsResolved['dirs']

dirs.root ||= [process.cwd()]
dirs.src ||= dirs.root
dirs.pages ||= dirs.src.map(src => `${src}/pages`)
dirs.layouts ||= dirs.src.map(src => `${src}/layouts`)
dirs.components ||= dirs.src.map(src => `${src}/components`)
dirs.composables ||= dirs.src.map(src => `${src}/composables`)
dirs.plugins ||= dirs.src.map(src => `${src}/plugins`)
dirs.modules ||= dirs.src.map(src => `${src}/modules`)
dirs.middleware ||= dirs.src.map(src => `${src}/middleware`)
dirs.servers ||= dirs.src.map(src => `${src}/servers`)

const resolved: NuxtESLintConfigOptionsResolved = {
features: {
standalone: true,
stylistic: false,
...config.features,
},
dirs,
}

Object.defineProperty(resolved, '__resolved', { value: true, enumerable: false })

return resolved
}
4 changes: 2 additions & 2 deletions packages/module/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
"@nuxt/kit": "^3.11.1",
"chokidar": "^3.6.0",
"eslint-flat-config-utils": "^0.1.2",
"eslint-flat-config-viewer": "^0.1.14",
"eslint-typegen": "^0.1.5",
"eslint-flat-config-viewer": "^0.1.20",
"eslint-typegen": "^0.1.6",
"get-port-please": "^3.1.2",
"pathe": "^1.1.2",
"unimport": "^3.7.1"
Expand Down
6 changes: 3 additions & 3 deletions packages/module/src/modules/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,15 @@ function getDirs(nuxt: Nuxt): NuxtESLintConfigOptions['dirs'] {
plugins: [],
middleware: [],
modules: [],
layers: [],
servers: [],
src: nuxt.options.srcDir,
root: [nuxt.options.rootDir],
src: [nuxt.options.srcDir],
}

for (const layer of nuxt.options._layers) {
const r = (t: string) => relative(nuxt.options.rootDir, resolve(layer.config.srcDir, t))

dirs.layers.push(r(''))
dirs.src.push(r(''))
dirs.pages.push(r(nuxt.options.dir.pages || 'pages'))
dirs.layouts.push(r(nuxt.options.dir.layouts || 'layouts'))
dirs.plugins.push(r(nuxt.options.dir.plugins || 'plugins'))
Expand Down

0 comments on commit a3625fd

Please sign in to comment.