diff --git a/index.js b/index.js index b976859..d163ff3 100644 --- a/index.js +++ b/index.js @@ -52,9 +52,12 @@ export function deepMerge (target, obj) { // This is also used in `create-vue` export default function createConfig ({ vueVersion = '3.x', // '2.x' | '3.x' (TODO: 2.7 / vue-demi) + configFormat = 'eslintrc', // eslintrc | flat - styleGuide = 'default', // default | airbnb | typescript - hasTypeScript = false, // js | ts + filePatterns = [], // flat format only - e.g. '**/*.vue', '**/*.js', etc. + + styleGuide = 'default', // default | airbnb | standard + hasTypeScript = false, // true | false needsPrettier = false, // true | false additionalConfig = {}, // e.g. Cypress, createAliasSetting for Airbnb, etc. @@ -69,13 +72,18 @@ export default function createConfig ({ addDependency('eslint') addDependency('eslint-plugin-vue') - if (styleGuide !== 'default' || hasTypeScript || needsPrettier) { + if (configFormat === 'flat') { + addDependency('@eslint/eslintrc') + addDependency('@eslint/js') + } else if (styleGuide !== 'default' || hasTypeScript || needsPrettier) { addDependency('@rushstack/eslint-patch') } const language = hasTypeScript ? 'typescript' : 'javascript' - const eslintConfig = { + const flatConfigExtends = [] + const flatConfigImports = [] + const eslintrcConfig = { root: true, extends: [ vueVersion.startsWith('2') @@ -85,49 +93,75 @@ export default function createConfig ({ } const addDependencyAndExtend = (name) => { addDependency(name) - eslintConfig.extends.push(name) + eslintrcConfig.extends.push(name) } switch (`${styleGuide}-${language}`) { case 'default-javascript': - eslintConfig.extends.push('eslint:recommended') + eslintrcConfig.extends.push('eslint:recommended') + flatConfigImports.push(`import js from '@eslint/js'`) + flatConfigExtends.push('js.configs.recommended') break case 'default-typescript': - eslintConfig.extends.push('eslint:recommended') + eslintrcConfig.extends.push('eslint:recommended') + flatConfigImports.push(`import js from '@eslint/js'`) + flatConfigExtends.push('js.configs.recommended') addDependencyAndExtend('@vue/eslint-config-typescript') + flatConfigExtends.push(`...compat.extends('@vue/eslint-config-typescript')`) break case 'airbnb-javascript': case 'standard-javascript': addDependencyAndExtend(`@vue/eslint-config-${styleGuide}`) + flatConfigExtends.push(`...compat.extends('@vue/eslint-config-${styleGuide}')`) break case 'airbnb-typescript': case 'standard-typescript': addDependencyAndExtend(`@vue/eslint-config-${styleGuide}-with-typescript`) + flatConfigExtends.push(`...compat.extends('@vue/eslint-config-${styleGuide}-with-typescript')`) break default: throw new Error(`unexpected combination of styleGuide and language: ${styleGuide}-${language}`) } + flatConfigImports.push(`import pluginVue from 'eslint-plugin-vue'`) + flatConfigExtends.push( + vueVersion.startsWith('2') + ? `...pluginVue.configs['flat/vue2-essential']` + : `...pluginVue.configs['flat/essential']` + ) + deepMerge(pkg.devDependencies, additionalDependencies) - deepMerge(eslintConfig, additionalConfig) + deepMerge(eslintrcConfig, additionalConfig) + + const flatConfigEntry = { + files: filePatterns + } + deepMerge(flatConfigEntry, additionalConfig) if (needsPrettier) { addDependency('prettier') addDependency('@vue/eslint-config-prettier') - eslintConfig.extends.push('@vue/eslint-config-prettier/skip-formatting') + eslintrcConfig.extends.push('@vue/eslint-config-prettier/skip-formatting') + flatConfigExtends.push(`...compat.extends('@vue/eslint-config-prettier/skip-formatting')`) } + const configFilename = configFormat === 'flat' + ? 'eslint.config.js' + : '.eslintrc.cjs' const files = { - '.eslintrc.cjs': '' + [configFilename]: '' } if (styleGuide === 'default') { // Both Airbnb & Standard have already set `env: node` - files['.eslintrc.cjs'] += '/* eslint-env node */\n' + files[configFilename] += '/* eslint-env node */\n' // Both Airbnb & Standard have already set `ecmaVersion` // The default in eslint-plugin-vue is 2020, which doesn't support top-level await - eslintConfig.parserOptions = { + eslintrcConfig.parserOptions = { + ecmaVersion: 'latest' + } + flatConfigEntry.languageOptions = { ecmaVersion: 'latest' } } @@ -136,7 +170,33 @@ export default function createConfig ({ files['.eslintrc.cjs'] += "require('@rushstack/eslint-patch/modern-module-resolution')\n\n" } - files['.eslintrc.cjs'] += `module.exports = ${stringifyJS(eslintConfig, styleGuide)}\n` + // eslint.config.js | .eslintrc.cjs + if (configFormat === 'flat') { + files['eslint.config.js'] += "import path from 'node:path'\n" + files['eslint.config.js'] += "import { fileURLToPath } from 'node:url'\n\n" + + flatConfigImports.forEach((pkgImport) => { + files['eslint.config.js'] += `${pkgImport}\n` + }) + files['eslint.config.js'] += '\n' + + // neccesary for compatibility until all packages support flat config + files['eslint.config.js'] += 'const __filename = fileURLToPath(import.meta.url)\n' + files['eslint.config.js'] += 'const __dirname = path.dirname(__filename)\n' + files['eslint.config.js'] += 'const compat = new FlatCompat({\n' + files['eslint.config.js'] += ' baseDirectory: __dirname\n' + files['eslint.config.js'] += '})\n\n' + + files['eslint.config.js'] += 'export default [\n' + flatConfigExtends.forEach((usage) => { + files['eslint.config.js'] += ` ${usage},\n` + }) + + const [, ...keep] = stringifyJS([flatConfigEntry], styleGuide).split('{') + files['eslint.config.js'] += ` {${keep.join('{')}\n` + } else { + files['.eslintrc.cjs'] += `module.exports = ${stringifyJS(eslintrcConfig, styleGuide)}\n` + } // .editorconfig & .prettierrc.json if (editorconfigs[styleGuide]) { diff --git a/package.json b/package.json index d80996b..8d0a1fc 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "kolorist": "^1.8.0" }, "devDependencies": { + "@eslint/eslintrc": "^3.0.2", + "@eslint/js": "^9.0.0", "@rushstack/eslint-patch": "^1.10.1", "@vue/eslint-config-airbnb": "^8.0.0", "@vue/eslint-config-airbnb-with-typescript": "^8.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3cf1a5..bff37e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,12 @@ dependencies: version: 1.8.0 devDependencies: + '@eslint/eslintrc': + specifier: ^3.0.2 + version: 3.0.2 + '@eslint/js': + specifier: ^9.0.0 + version: 9.0.0 '@rushstack/eslint-patch': specifier: ^1.10.1 version: 1.10.1 @@ -112,11 +118,33 @@ packages: - supports-color dev: true + /@eslint/eslintrc@3.0.2: + resolution: {integrity: sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 10.0.1 + globals: 14.0.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + /@eslint/js@8.57.0: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@eslint/js@9.0.0: + resolution: {integrity: sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true + /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -745,12 +773,26 @@ packages: acorn: 8.10.0 dev: true + /acorn-jsx@5.3.2(acorn@8.11.3): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.11.3 + dev: true + /acorn@8.10.0: resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} engines: {node: '>=0.4.0'} hasBin: true dev: true + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -1931,6 +1973,11 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true + /eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1978,6 +2025,15 @@ packages: - supports-color dev: true + /espree@10.0.1: + resolution: {integrity: sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 4.0.0 + dev: true + /espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2216,6 +2272,11 @@ packages: type-fest: 0.20.2 dev: true + /globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + dev: true + /globalthis@1.0.3: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} engines: {node: '>= 0.4'}