Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
566 changes: 362 additions & 204 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export class TsEslintConfigForVue {
toConfigArray(): FlatConfig.ConfigArray {
return toArray(tseslint.configs[this.configName])
.flat()
.map(config => ({
.map((config: FlatConfig.Config) => ({
...config,
...(config.files && config.files.includes('**/*.ts')
? { files: [...config.files, '**/*.vue'] }
Expand Down
4 changes: 3 additions & 1 deletion src/createConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export default function createConfig({
rootDir,
})
return defineConfigWithVueTs(
...configNamesToExtend.map(name => vueTsConfigs[name as ExtendableConfigName]),
...configNamesToExtend.map(
name => vueTsConfigs[name as ExtendableConfigName],
),
)
}
41 changes: 26 additions & 15 deletions src/fpHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,55 @@
// This file is for some generic helper functions that aren't tied to the main functionality of the project.

export function omit<T extends Record<string, any>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
export function omit<T extends Record<string, any>, K extends keyof T>(
obj: T,
keys: K[],
): Omit<T, K> {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => !keys.includes(key as K))
) as Omit<T, K>;
Object.entries(obj).filter(([key]) => !keys.includes(key as K)),
) as Omit<T, K>
}

// https://dev.to/nexxeln/implementing-the-pipe-operator-in-typescript-30ip
interface Pipe {
<A>(value: A): A;
<A, B>(value: A, fn1: (input: A) => B): B;
<A, B, C>(value: A, fn1: (input: A) => B, fn2: (input: B) => C): C;
<A>(value: A): A
<A, B>(value: A, fn1: (input: A) => B): B
<A, B, C>(value: A, fn1: (input: A) => B, fn2: (input: B) => C): C
<A, B, C, D>(
value: A,
fn1: (input: A) => B,
fn2: (input: B) => C,
fn3: (input: C) => D
): D;
fn3: (input: C) => D,
): D
<A, B, C, D, E>(
value: A,
fn1: (input: A) => B,
fn2: (input: B) => C,
fn3: (input: C) => D,
fn4: (input: D) => E
): E;
fn4: (input: D) => E,
): E
<A, B, C, D, E, F>(
value: A,
fn1: (input: A) => B,
fn2: (input: B) => C,
fn3: (input: C) => D,
fn4: (input: D) => E,
fn5: (input: E) => F
): F;
fn5: (input: E) => F,
): F
<A, B, C, D, E, F, G>(
value: A,
fn1: (input: A) => B,
fn2: (input: B) => C,
fn3: (input: C) => D,
fn4: (input: D) => E,
fn5: (input: E) => F,
fn6: (input: F) => G,
): G
// ... and so on
}

export const pipe: Pipe = (value: any, ...fns: Function[]): unknown => {
return fns.reduce((acc, fn) => fn(acc), value);
};

return fns.reduce((acc, fn) => fn(acc), value)
}

export function partition<T>(
array: T[],
Expand Down
26 changes: 24 additions & 2 deletions src/groupVueFiles.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,39 @@
import fs from 'node:fs'
import fg from 'fast-glob'
import path from 'node:path'
import { debuglog } from 'node:util'

const debug = debuglog('@vue/eslint-config-typescript:groupVueFiles')

type VueFilesByGroup = {
typeCheckable: string[]
nonTypeCheckable: string[]
}

export default function groupVueFiles(rootDir: string): VueFilesByGroup {
export default function groupVueFiles(
rootDir: string,
globalIgnores: string[],
): VueFilesByGroup {
debug(`Grouping .vue files in ${rootDir}`)

const ignore = [
'**/node_modules/**',
'**/.git/**',

// Global ignore patterns from ESLint config are relative to the ESLint base path,
// which is usually the cwd, but could be different if `--config` is provided via CLI.
// This is way too complicated, so we only use process.cwd() as a best-effort guess here.
// Could be improved in the future if needed.
...globalIgnores.map(pattern =>
fg.convertPathToPattern(path.resolve(process.cwd(), pattern)),
),
]
debug(`Ignoring patterns: ${ignore.join(', ')}`)

const { vueFilesWithScriptTs, otherVueFiles } = fg
.sync(['**/*.vue'], {
cwd: rootDir,
ignore: ['**/node_modules/**'],
ignore,
})
.reduce(
(acc, file) => {
Expand Down
5 changes: 1 addition & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
export {
defineConfigWithVueTs,
configureVueProject,
} from './utilities'
export { defineConfigWithVueTs, configureVueProject } from './utilities'
export { vueTsConfigs } from './configs'

// Compatibility layer for the `createConfig` function in <= 14.2.0
Expand Down
43 changes: 36 additions & 7 deletions src/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import process from 'node:process'
import tseslint from 'typescript-eslint'
import type { TSESLint } from '@typescript-eslint/utils'
import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint'
import pluginVue from 'eslint-plugin-vue'

import { TsEslintConfigForVue } from './configs'
Expand Down Expand Up @@ -43,16 +44,16 @@ export type ProjectOptions = {
/**
* Whether to override some `no-unsafe-*` rules to avoid false positives on Vue component operations.
* Defaults to `true`.
*
*
* Due to limitations in the integration between Vue and TypeScript-ESLint,
* TypeScript-ESLint cannot get the full type information for `.vue` files
* and will use fallback types that contain some `any`s.
* Therefore, some `no-unsafe-*` rules will error on functions that operate on Vue components,
* such as `createApp`, `createRouter`, `useTemplateRef`, etc.
*
*
* Setting this option to `true` will override those `no-unsafe-*` rules
* to allow these patterns in the project.
*
*
* If you're using a metaframework such as Nuxt or Quasar
* that handles app creation & router configuration for you,
* you might not need to interact with component types directly.
Expand Down Expand Up @@ -82,7 +83,8 @@ export function configureVueProject(userOptions: ProjectOptions): void {
projectOptions.tsSyntaxInTemplates = userOptions.tsSyntaxInTemplates
}
if (userOptions.allowComponentTypeUnsafety !== undefined) {
projectOptions.allowComponentTypeUnsafety = userOptions.allowComponentTypeUnsafety
projectOptions.allowComponentTypeUnsafety =
userOptions.allowComponentTypeUnsafety
}
if (userOptions.scriptLangs) {
projectOptions.scriptLangs = userOptions.scriptLangs
Expand All @@ -104,6 +106,7 @@ export function defineConfigWithVueTs(
return pipe(
configs,
flattenConfigs,
collectGlobalIgnores,
deduplicateVuePlugin,
insertAndReorderConfigs,
resolveVueTsConfigs,
Expand Down Expand Up @@ -165,6 +168,29 @@ function flattenConfigs(
)
}

let globalIgnores: string[] = []

/**
* Fields that are considered metadata and not part of the config object.
*/
const META_FIELDS = new Set(['name', 'basePath'])

function collectGlobalIgnores(configs: RawConfigItem[]): RawConfigItem[] {
configs.forEach(config => {
if (config instanceof TsEslintConfigForVue) return

if (!config.ignores) return

if (Object.keys(config).filter(key => !META_FIELDS.has(key)).length !== 1)
return

// Configs that only contain `ignores` (and possibly `name`/`basePath`) are treated as global ignores
globalIgnores.push(...config.ignores)
})

return configs
}

function resolveVueTsConfigs(configs: RawConfigItem[]): ConfigItem[] {
return configs.flatMap(config =>
config instanceof TsEslintConfigForVue ? config.toConfigArray() : config,
Expand Down Expand Up @@ -206,7 +232,7 @@ function insertAndReorderConfigs(configs: RawConfigItem[]): RawConfigItem[] {
return configs
}

const vueFiles = groupVueFiles(projectOptions.rootDir)
const vueFiles = groupVueFiles(projectOptions.rootDir, globalIgnores)
const configsWithoutTypeAwareRules = configs.map(extractTypeAwareRules)

const hasTypeAwareConfigs = configs.some(
Expand All @@ -233,7 +259,10 @@ function insertAndReorderConfigs(configs: RawConfigItem[]): RawConfigItem[] {
...(needsTypeAwareLinting
? [
...createSkipTypeCheckingConfigs(vueFiles.nonTypeCheckable),
...createTypeCheckingConfigs(vueFiles.typeCheckable, projectOptions.allowComponentTypeUnsafety),
...createTypeCheckingConfigs(
vueFiles.typeCheckable,
projectOptions.allowComponentTypeUnsafety,
),
]
: []),

Expand Down Expand Up @@ -269,7 +298,7 @@ function extractTypeAwareRules(config: RawConfigItem): RawConfigItem {
}

const rulesRequiringTypeInformation = new Set(
Object.entries(tseslint.plugin.rules!)
Object.entries((tseslint.plugin as FlatConfig.Plugin).rules!)
// @ts-expect-error
.filter(([_name, def]) => def?.meta?.docs?.requiresTypeChecking)
.map(([name, _def]) => `@typescript-eslint/${name}`)
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/custom-ignored-directory/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100
1 change: 1 addition & 0 deletions test/fixtures/custom-ignored-directory/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
36 changes: 36 additions & 0 deletions test/fixtures/custom-ignored-directory/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.DS_Store
dist
dist-ssr
coverage
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

*.tsbuildinfo

.eslintcache

# Cypress
/cypress/videos/
/cypress/screenshots/

# Vitest
__screenshots__/
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig"
]
}
48 changes: 48 additions & 0 deletions test/fixtures/custom-ignored-directory/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# custom-ignored-directory

This template should help get you started developing with Vue 3 in Vite.

## Recommended IDE Setup

[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).

## Recommended Browser Setup

- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)

## Type Support for `.vue` Imports in TS

TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.

## Customize configuration

See [Vite Configuration Reference](https://vite.dev/config/).

## Project Setup

```sh
pnpm install
```

### Compile and Hot-Reload for Development

```sh
pnpm dev
```

### Type-Check, Compile and Minify for Production

```sh
pnpm build
```

### Lint with [ESLint](https://eslint.org/)

```sh
pnpm lint
```
1 change: 1 addition & 0 deletions test/fixtures/custom-ignored-directory/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
20 changes: 20 additions & 0 deletions test/fixtures/custom-ignored-directory/eslint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { globalIgnores } from 'eslint/config'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'

// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup

export default defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
},

globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**', '**/ignored-directory/**']),

pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,
)
Loading