Skip to content

Commit

Permalink
feat!: initial flat config support
Browse files Browse the repository at this point in the history
BREAKING CHANGES:
- Dropped legacy `.eslintrc*` support
- Dropped support for Vue 2
- Airbnb and Standard style guides are not yet supported

It satisfies the use case for basic JavaScript / TypeScript projects
with Vue 3.
The formatting of `additionalConfigs` still needs to be improved to be
able to be used in `create-vue`.
The CLI functionality is too basic and needs to be improved.
  • Loading branch information
haoqunjiang committed Oct 5, 2024
1 parent 9df38b7 commit 1d657db
Show file tree
Hide file tree
Showing 13 changed files with 1,196 additions and 3,525 deletions.
6 changes: 6 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
6 changes: 6 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"arrowParens": "avoid"
}
211 changes: 58 additions & 153 deletions bin/create-eslint-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import path from 'node:path'
import process from 'node:process'
import { bold, blue, yellow, red, green, dim } from 'kolorist'

import createConfig, { deepMerge, CREATE_ALIAS_SETTING_PLACEHOLDER } from '../index.js'
import createConfig, { deepMerge } from '../index.js'

const require = createRequire(import.meta.url)
const Enquirer = require('enquirer')
Expand Down Expand Up @@ -43,6 +43,7 @@ const pkg = JSON.parse(rawPkgJson)

// 1. check for existing config files
// `.eslintrc.*`, `eslintConfig` in `package.json`
// FIXME: `eslint.config.*`
// ask if wanna overwrite?

// https://eslint.org/docs/latest/user-guide/configuring/configuration-files#configuration-file-formats
Expand Down Expand Up @@ -89,55 +90,20 @@ if (pkg.eslintConfig) {
}

// 2. Check Vue
// Not detected? Choose from Vue 2 or 3
// TODO: better support for 2.7 and vue-demi
let vueVersion
// Not detected? Abort
// Vue 2? Abort because this tool only supports Vue 3
try {
vueVersion = requireInCwd('vue/package.json').version
console.info(dim(`Detected Vue.js version: ${vueVersion}`))
} catch (e) {
const anwsers = await prompt({
type: 'select',
name: 'vueVersion',
message: 'Which Vue.js version do you use in the project?',
choices: [
'3.x',
'2.x'
]
})
vueVersion = anwsers.vueVersion
// FIXME: warning that this only support Vue 3
}

// 3. Choose a style guide
// - Error Prevention (ESLint Recommended)
// - Standard
// - Airbnb
const { styleGuide } = await prompt({
type: 'select',
name: 'styleGuide',
message: 'Which style guide do you want to follow?',
choices: [
{
name: 'default',
message: 'ESLint Recommended (Error-Prevention-Only)'
},
{
name: 'airbnb',
message: `Airbnb ${dim('(https://airbnb.io/javascript/)')}`
},
{
name: 'standard',
message: `Standard ${dim('(https://standardjs.com/)')}`
}
]
})

// 4. Check TypeScript
// 4.1 Allow JS?
// 4.2 Allow JS in Vue?
// 4.3 Allow JSX (TSX, if answered no in 4.1) in Vue?
// 4.2 Allow JS in Vue? Allow JSX (TSX, if answered no in 4.1) in Vue?
let hasTypeScript = false
const additionalConfig = {}
try {
const tsVersion = requireInCwd('typescript/package.json').version
console.info(dim(`Detected TypeScript version: ${tsVersion}`))
Expand All @@ -154,108 +120,54 @@ try {
hasTypeScript = anwsers.hasTypeScript
}

// TODO: we don't have more fine-grained sub-rulsets in `@vue/eslint-config-typescript` yet
if (hasTypeScript && styleGuide !== 'default') {
const { allowJsInVue } = await prompt({
type: 'toggle',
disabled: 'No',
enabled: 'Yes',
name: 'allowJsInVue',
message: `Do you use plain ${yellow('<script>')}s (without ${blue('lang="ts"')}) in ${green('.vue')} files?`,
initial: false
})

if (allowJsInVue) {
const { allowJsxInVue } = await prompt({
type: 'toggle',
disabled: 'No',
enabled: 'Yes',
name: 'allowJsxInVue',
message: `Do you use ${yellow('<script lang="jsx">')}s in ${green('.vue')} files (not recommended)?`,
initial: false
})

additionalConfig.extends = [
`@vue/eslint-config-${styleGuide}-with-typescript/${
allowJsxInVue
? 'allow-jsx-in-vue'
: 'allow-js-in-vue'
}`
]
} else {
const { allowTsxInVue } = await prompt({
type: 'toggle',
disabled: 'No',
enabled: 'Yes',
name: 'allowTsxInVue',
message: `Do you use ${yellow('<script lang="tsx">')}s in ${green('.vue')} files (not recommended)?`,
initial: false
})

if (allowTsxInVue) {
additionalConfig.extends = [
`@vue/eslint-config-${styleGuide}-with-typescript/allow-tsx-in-vue`
]
}
}
}

// 5. If Airbnb && !TypeScript
// Does your project use any path aliases?
// Show [snippet prompts](https://github.com/enquirer/enquirer#snippet-prompt) for the user to input aliases
if (styleGuide === 'airbnb' && !hasTypeScript) {
const { hasAlias } = await prompt({
type: 'toggle',
disabled: 'No',
enabled: 'Yes',
name: 'hasAlias',
message: 'Does your project use any path aliases?',
initial: false
})

if (hasAlias) {
console.info()
console.info(`Please input your alias configurations (press ${bold(green('<Enter>'))} to skip):`)

const aliases = {}
while (true) {
console.info()

const { prefix } = await prompt({
type: 'input',
name: 'prefix',
message: 'Alias prefix',
validate: (val) => {
if (Object.hasOwn(aliases, val)) {
return red(`${green(val)} has already been aliased to ${green(aliases[val])}`)
}

return true
}
})

if (!prefix) {
break
}

const { replacement } = await prompt({
type: 'input',
name: 'replacement',
message: `Path replacement for the prefix ${green(prefix)}`,
validate: (value) => value !== ''
})

aliases[prefix] = replacement
}
if (Object.keys(aliases).length > 0) {
additionalConfig.settings = { [CREATE_ALIAS_SETTING_PLACEHOLDER]: aliases }
}

console.info()
}
}

// 6. Do you need Prettier to format your codebase?
const supportedScriptLangs = {}
// FIXME: Use a multi-select prompt
// if (hasTypeScript) {
// const { allowJsInVue } = await prompt({
// type: 'toggle',
// disabled: 'No',
// enabled: 'Yes',
// name: 'allowJsInVue',
// message: `Do you use plain ${yellow('<script>')}s (without ${blue('lang="ts"')}) in ${green('.vue')} files?`,
// initial: false
// })

// if (allowJsInVue) {
// const { allowJsxInVue } = await prompt({
// type: 'toggle',
// disabled: 'No',
// enabled: 'Yes',
// name: 'allowJsxInVue',
// message: `Do you use ${yellow('<script lang="jsx">')}s in ${green('.vue')} files (not recommended)?`,
// initial: false
// })

// additionalConfig.extends = [
// `@vue/eslint-config-${styleGuide}-with-typescript/${
// allowJsxInVue
// ? 'allow-jsx-in-vue'
// : 'allow-js-in-vue'
// }`
// ]
// } else {
// const { allowTsxInVue } = await prompt({
// type: 'toggle',
// disabled: 'No',
// enabled: 'Yes',
// name: 'allowTsxInVue',
// message: `Do you use ${yellow('<script lang="tsx">')}s in ${green('.vue')} files (not recommended)?`,
// initial: false
// })

// if (allowTsxInVue) {
// additionalConfig.extends = [
// `@vue/eslint-config-${styleGuide}-with-typescript/allow-tsx-in-vue`
// ]
// }
// }
// }

// 5. Do you need Prettier to format your codebase?
const { needsPrettier } = await prompt({
type: 'toggle',
disabled: 'No',
Expand All @@ -265,30 +177,23 @@ const { needsPrettier } = await prompt({
})

const { pkg: pkgToExtend, files } = createConfig({
vueVersion,
styleGuide,
hasTypeScript,
supportedScriptLangs,
needsPrettier,

additionalConfig
})

// TODO:
// Add `lint` command to package.json
// - eslint ... (extensions vary based on the language)
// - note: for Vue CLI, remove the @vue/cli-plugin-eslint and override its lint command, they are too outdated.
// Add a `format` command to package.json when prettier is used
// TODO:
// Add a note about that Vue CLI projects may need a `tsconfig.eslint.json`

deepMerge(pkg, pkgToExtend)

// Write `package.json` back
writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, indent) + '\n', 'utf-8')
writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, indent) + '\n', 'utf8')
// Write files back
for (const [name, content] of Object.entries(files)) {
const fullPath = path.resolve(cwd, name)
writeFileSync(fullPath, content, 'utf-8')
writeFileSync(fullPath, content, 'utf8')
}

// Prompt: Run `npm install` or `yarn` or `pnpm install`
Expand All @@ -300,7 +205,7 @@ const lintCommand = packageManager === 'npm' ? 'npm run lint' : `${packageManage

console.info(
'\n' +
`${bold(yellow('package.json'))} and ${bold(blue('.eslintrc.cjs'))} have been updated.\n` +
`${bold(yellow('package.json'))} and ${bold(blue('eslint.config.mjs'))} have been updated.\n` +
`Now please run ${bold(green(installCommand))} to re-install the dependencies.\n` +
`Then you can run ${bold(green(lintCommand))} to lint your files.`
)
13 changes: 13 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import pluginVue from 'eslint-plugin-vue'
// Use the TS config here because the default js parser doesn't support import attributes
import vueTsEslintConfig from '@vue/eslint-config-typescript'

export default [
{
name: 'app/files-to-lint',
files: ['**/*.{js,mjs,jsx,ts,mts,tsx,vue}'],
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
},
...pluginVue.configs['flat/essential'],
...vueTsEslintConfig(),
]
Loading

0 comments on commit 1d657db

Please sign in to comment.