From 6ac159a72b0aa693062b6da57be5c5d540728e8e Mon Sep 17 00:00:00 2001 From: Konstantinos Paparas Date: Mon, 8 Jan 2024 23:10:49 +0100 Subject: [PATCH] feat: migrate to flat config --- .github/renovate.json5 | 22 +- README.md | 8 +- eslint.config.js | 6 +- package.json | 12 +- pnpm-lock.yaml | 302 +++++++++++++++++++++---- src/configs/comments.ts | 8 +- src/configs/formatters.ts | 161 +++++++++++++ src/configs/ignores.ts | 8 +- src/configs/imports.ts | 145 ++++++------ src/configs/index.ts | 8 +- src/configs/javascript.ts | 361 ++++++++++++++++-------------- src/configs/jsonc.ts | 95 ++++++-- src/configs/markdown.ts | 143 ++++++++---- src/configs/node.ts | 22 ++ src/configs/perfectionist.ts | 17 ++ src/configs/prettier.ts | 20 -- src/configs/sort-keys.ts | 12 - src/configs/sort.ts | 402 ++++++++++++++++----------------- src/configs/stylistic.ts | 56 +++++ src/configs/typescript.ts | 241 +++++++++++++------- src/configs/unicorn.ts | 8 +- src/configs/vue.ts | 423 ++++++++++++++++++++--------------- src/configs/yaml.ts | 71 +++++- src/env.ts | 14 +- src/factory.ts | 197 ++++++++++++++++ src/globs.ts | 15 ++ src/index.ts | 4 +- src/plugins.ts | 21 +- src/presets.ts | 77 ------- src/types.ts | 235 +++++++++++++++++++ src/utils.ts | 61 +++++ src/vendor/prettier.ts | 136 +++++++++++ tsup.config.ts | 8 + 33 files changed, 2333 insertions(+), 986 deletions(-) create mode 100644 src/configs/formatters.ts create mode 100644 src/configs/node.ts create mode 100644 src/configs/perfectionist.ts delete mode 100644 src/configs/prettier.ts delete mode 100644 src/configs/sort-keys.ts create mode 100644 src/configs/stylistic.ts create mode 100644 src/factory.ts delete mode 100644 src/presets.ts create mode 100644 src/types.ts create mode 100644 src/utils.ts create mode 100644 src/vendor/prettier.ts create mode 100644 tsup.config.ts diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 5e22e2b..51dcf72 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,15 +1,15 @@ { - $schema: 'https://docs.renovatebot.com/renovate-schema.json', - extends: [ - 'config:recommended', - ':dependencyDashboard', - ':dependencyDashboardApproval', + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":dependencyDashboard", + ":dependencyDashboardApproval" ], - packageRules: [ + "packageRules": [ { - allowedVersions: '/^(18)\\./', - groupName: 'Node.js', - matchPackageNames: ['@types/node', 'node'], - }, - ], + "allowedVersions": "/^(18)\\./", + "groupName": "Node.js", + "matchPackageNames": ["@types/node", "node"] + } + ] } diff --git a/README.md b/README.md index d5a3b68..cbfd4e0 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ A common configuration to be used across the different [rotki](https://github.co pnpm add -D eslint @rotki/eslint-config ``` - ### Config `.eslintrc` ```json @@ -36,19 +35,18 @@ For example: } ``` - ### TypeScript Aware Rules Type aware rules are enabled when a `tsconfig.eslint.json` is found in the project root, which will introduce some stricter rules into your project. If you want to enable it while have no `tsconfig.eslint.json` in the project root, you can change tsconfig name by modifying `ESLINT_TSCONFIG` env. ```js // .eslintrc.js -process.env.ESLINT_TSCONFIG = 'tsconfig.json' +process.env.ESLINT_TSCONFIG = 'tsconfig.json'; module.exports = { extends: '@rotki' -} +}; ``` ## License -[AGPL-3.0](./LICENSE) License © 2023- [Rotki Solutions GmbH](https://github.com/rotki) \ No newline at end of file +[AGPL-3.0](./LICENSE) License © 2023- [Rotki Solutions GmbH](https://github.com/rotki) diff --git a/eslint.config.js b/eslint.config.js index 43959be..30b95de 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,3 +1,7 @@ import { rotki } from './dist/index.js'; -export default rotki(); +export default rotki({ + vue: true, + typescript: true, + formatters: true, +}); diff --git a/package.json b/package.json index ea5152e..72e6845 100644 --- a/package.json +++ b/package.json @@ -38,26 +38,34 @@ "eslint": ">=8.0.0" }, "dependencies": { + "@antfu/install-pkg": "0.3.1", + "@stylistic/eslint-plugin": "1.5.3", "@typescript-eslint/eslint-plugin": "6.18.0", "@typescript-eslint/parser": "6.18.0", + "eslint-config-flat-gitignore": "0.1.2", "eslint-config-prettier": "9.1.0", "eslint-define-config": "2.1.0", + "eslint-merge-processors": "0.1.0", "eslint-plugin-antfu": "2.1.1", "eslint-plugin-eslint-comments": "3.2.0", + "eslint-plugin-format": "0.1.0", "eslint-plugin-html": "7.1.0", "eslint-plugin-i": "2.29.1", "eslint-plugin-jsonc": "2.11.2", "eslint-plugin-markdown": "3.0.1", + "eslint-plugin-n": "16.6.2", + "eslint-plugin-perfectionist": "2.5.0", "eslint-plugin-prettier": "5.1.2", - "eslint-plugin-sort-keys": "2.3.5", "eslint-plugin-unicorn": "50.0.1", "eslint-plugin-unused-imports": "3.0.0", "eslint-plugin-vue": "9.19.2", "eslint-plugin-yml": "1.11.0", + "eslint-processor-vue-blocks": "0.1.1", "globals": "13.24.0", "jsonc-eslint-parser": "2.4.0", "local-pkg": "0.5.0", "prettier": "3.1.1", + "prompts": "2.4.2", "vue-eslint-parser": "9.4.0", "yaml-eslint-parser": "1.2.2" }, @@ -65,7 +73,9 @@ "@commitlint/cli": "18.4.4", "@commitlint/config-conventional": "18.4.4", "@rotki/eslint-config": "*", + "@types/eslint": "8.56.1", "@types/node": "20.10.7", + "@types/prompts": "2.4.9", "bumpp": "9.2.1", "eslint": "8.56.0", "eslint-flat-config-viewer": "0.1.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4798b5d..826d84c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,24 +8,39 @@ importers: .: dependencies: + '@antfu/install-pkg': + specifier: 0.3.1 + version: 0.3.1 + '@stylistic/eslint-plugin': + specifier: 1.5.3 + version: 1.5.3(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/eslint-plugin': specifier: 6.18.0 version: 6.18.0(@typescript-eslint/parser@6.18.0)(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/parser': specifier: 6.18.0 version: 6.18.0(eslint@8.56.0)(typescript@5.3.3) + eslint-config-flat-gitignore: + specifier: 0.1.2 + version: 0.1.2 eslint-config-prettier: specifier: 9.1.0 version: 9.1.0(eslint@8.56.0) eslint-define-config: specifier: 2.1.0 version: 2.1.0 + eslint-merge-processors: + specifier: 0.1.0 + version: 0.1.0(eslint@8.56.0) eslint-plugin-antfu: specifier: 2.1.1 version: 2.1.1(eslint@8.56.0) eslint-plugin-eslint-comments: specifier: 3.2.0 version: 3.2.0(eslint@8.56.0) + eslint-plugin-format: + specifier: 0.1.0 + version: 0.1.0(eslint@8.56.0) eslint-plugin-html: specifier: 7.1.0 version: 7.1.0 @@ -38,12 +53,15 @@ importers: eslint-plugin-markdown: specifier: 3.0.1 version: 3.0.1(eslint@8.56.0) + eslint-plugin-n: + specifier: 16.6.2 + version: 16.6.2(eslint@8.56.0) + eslint-plugin-perfectionist: + specifier: 2.5.0 + version: 2.5.0(eslint@8.56.0)(typescript@5.3.3)(vue-eslint-parser@9.4.0) eslint-plugin-prettier: specifier: 5.1.2 - version: 5.1.2(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.1.1) - eslint-plugin-sort-keys: - specifier: 2.3.5 - version: 2.3.5 + version: 5.1.2(@types/eslint@8.56.1)(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.1.1) eslint-plugin-unicorn: specifier: 50.0.1 version: 50.0.1(eslint@8.56.0) @@ -56,6 +74,9 @@ importers: eslint-plugin-yml: specifier: 1.11.0 version: 1.11.0(eslint@8.56.0) + eslint-processor-vue-blocks: + specifier: 0.1.1 + version: 0.1.1(@vue/compiler-sfc@3.4.6)(eslint@8.56.0) globals: specifier: 13.24.0 version: 13.24.0 @@ -68,6 +89,9 @@ importers: prettier: specifier: 3.1.1 version: 3.1.1 + prompts: + specifier: 2.4.2 + version: 2.4.2 vue-eslint-parser: specifier: 9.4.0 version: 9.4.0(eslint@8.56.0) @@ -84,9 +108,15 @@ importers: '@rotki/eslint-config': specifier: '*' version: 'link:' + '@types/eslint': + specifier: 8.56.1 + version: 8.56.1 '@types/node': specifier: 20.10.7 version: 20.10.7 + '@types/prompts': + specifier: 2.4.9 + version: 2.4.9 bumpp: specifier: 9.2.1 version: 9.2.1 @@ -118,6 +148,12 @@ packages: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} engines: {node: '>=0.10.0'} + /@antfu/install-pkg@0.3.1: + resolution: {integrity: sha512-A3zWY9VeTPnxlMiZtsGHw2lSd3ghwvL8s9RiGOtqvDxhhFfZ781ynsGBa/iUnDJ5zBrmTFQrJDud3TGgRISaxw==} + dependencies: + execa: 8.0.1 + dev: false + /@babel/code-frame@7.23.5: resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} engines: {node: '>=6.9.0'} @@ -128,7 +164,6 @@ packages: /@babel/helper-string-parser@7.23.4: resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} @@ -148,7 +183,6 @@ packages: hasBin: true dependencies: '@babel/types': 7.23.6 - dev: true /@babel/types@7.23.6: resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==} @@ -157,7 +191,6 @@ packages: '@babel/helper-string-parser': 7.23.4 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - dev: true /@commitlint/cli@18.4.4(@types/node@20.10.7)(typescript@5.3.3): resolution: {integrity: sha512-Ro3wIo//fV3XiV1EkdpHog6huaEyNcUAVrSmtgKqYM5g982wOWmP4FXvEDFwRMVgz878CNBvvCc33dMZ5AQJ/g==} @@ -323,6 +356,18 @@ packages: chalk: 4.1.2 dev: true + /@dprint/formatter@0.2.1: + resolution: {integrity: sha512-GCzgRt2o4mhZLy8L47k2A+q9EMG/jWhzZebE29EqKsxmjDrSfv2VisEj/Q+39OOf04jTkEfB/TRO+IZSyxHdYg==} + dev: false + + /@dprint/markdown@0.16.3: + resolution: {integrity: sha512-KvwUrCdHR1spFk0EcdW33KEGFLfkcdx6hJN8mwipGBw0b40sl5oPtVUTgRiH70eV7VUhPfycDfIsDutWNHb17w==} + dev: false + + /@dprint/toml@0.5.4: + resolution: {integrity: sha512-d+5GwwzztZD0QixmOBhaO6nWVLsAeYsJ1HJYNxDoDRbASFCpza9BBVshG5ctBRXCkkIHhD9BO1SnbOoRQltUQw==} + dev: false + /@esbuild/aix-ppc64@0.19.11: resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==} engines: {node: '>=12'} @@ -613,7 +658,6 @@ packages: /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true /@jridgewell/trace-mapping@0.3.20: resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} @@ -766,13 +810,83 @@ packages: dev: true optional: true + /@stylistic/eslint-plugin-js@1.5.3(eslint@8.56.0): + resolution: {integrity: sha512-XlKnm82fD7Sw9kQ6FFigE0tobvptNBXZWsdfoKmUyK7bNxHsAHOFT8zJGY3j3MjZ0Fe7rLTu86hX/vOl0bRRdQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: '>=8.40.0' + dependencies: + acorn: 8.11.3 + escape-string-regexp: 4.0.0 + eslint: 8.56.0 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + dev: false + + /@stylistic/eslint-plugin-jsx@1.5.3(eslint@8.56.0): + resolution: {integrity: sha512-gKXWFmvg3B4e6G+bVz2p37icjj3gS5lzazZD6oLjmQ2b0Lw527VpnxGjWxQ16keKXtrVzUfebakjskOoALg3CQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: '>=8.40.0' + dependencies: + '@stylistic/eslint-plugin-js': 1.5.3(eslint@8.56.0) + eslint: 8.56.0 + estraverse: 5.3.0 + dev: false + + /@stylistic/eslint-plugin-plus@1.5.3(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-fuOBySbH4dbfY4Dwvu+zg5y+e0lALHTyQske5+a2zNC8Ejnx4rFlVjYOmaVFtxFhTD4V0vM7o21Ozci0igcxKg==} + peerDependencies: + eslint: '*' + dependencies: + '@typescript-eslint/utils': 6.18.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: false + + /@stylistic/eslint-plugin-ts@1.5.3(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-/gUEqGo0gpFeu220YmC0788VliKnmTaAz4pI82KA5cUuCp6OzEhGlrNkb1eevMwH0RRgyND20HJxOYvEGlwu+w==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: '>=8.40.0' + dependencies: + '@stylistic/eslint-plugin-js': 1.5.3(eslint@8.56.0) + '@typescript-eslint/utils': 6.18.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: false + + /@stylistic/eslint-plugin@1.5.3(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-Vee+hHKaCd8DPRoRJTCV+mOFz+zFIaA9QiNJaAvgBzmPkcDnSC7Ewh518fN6SSNe9psS8TDIpcxd1g5v4MSY5A==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: '>=8.40.0' + dependencies: + '@stylistic/eslint-plugin-js': 1.5.3(eslint@8.56.0) + '@stylistic/eslint-plugin-jsx': 1.5.3(eslint@8.56.0) + '@stylistic/eslint-plugin-plus': 1.5.3(eslint@8.56.0)(typescript@5.3.3) + '@stylistic/eslint-plugin-ts': 1.5.3(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: false + + /@types/eslint@8.56.1: + resolution: {integrity: sha512-18PLWRzhy9glDQp3+wOgfLYRWlhgX0azxgJ63rdpoUHyrC9z0f5CkFburjQx4uD7ZCruw85ZtMt6K+L+R8fLJQ==} + dependencies: + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 + /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: false /@types/mdast@3.0.15: resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} @@ -793,6 +907,13 @@ packages: /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + /@types/prompts@2.4.9: + resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} + dependencies: + '@types/node': 20.10.7 + kleur: 3.0.3 + dev: true + /@types/semver@7.5.6: resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} dev: false @@ -971,14 +1092,12 @@ packages: entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.0.2 - dev: true /@vue/compiler-dom@3.4.6: resolution: {integrity: sha512-i39ZuyHPzPb0v5yXZbvODGwLr+T7lS1rYSjMd1oCTa14aDP80kYpWXrWPF1JVD4QJJNyLgFnJ2hxvFLM7dy9NQ==} dependencies: '@vue/compiler-core': 3.4.6 '@vue/shared': 3.4.6 - dev: true /@vue/compiler-sfc@3.4.6: resolution: {integrity: sha512-kTFOiyMtuetFqi5yEPA4hR6FTD36zKKY3qaBonxGb4pgj0yK1eACqH+iycTAsEqr2u4cOhcGkx3Yjecpgh6FTQ==} @@ -992,14 +1111,12 @@ packages: magic-string: 0.30.5 postcss: 8.4.33 source-map-js: 1.0.2 - dev: true /@vue/compiler-ssr@3.4.6: resolution: {integrity: sha512-XqeojjDitjMLyOogDePNSxw9XL4FAXchO9oOfqdzLVEtYES5j+AEilPJyP0KhQPfGecY2mJ3Y7/e6kkiJQLKvg==} dependencies: '@vue/compiler-dom': 3.4.6 '@vue/shared': 3.4.6 - dev: true /@vue/reactivity@3.4.6: resolution: {integrity: sha512-/VuOxdWDyAeKFHjOuSKEtH9jEVPRgsXxu84utBP1SiXFcFRx2prwiC9cSR8hKOfj5nBwhLXYb6XEU69mLpuk0w==} @@ -1034,7 +1151,6 @@ packages: /@vue/shared@3.4.6: resolution: {integrity: sha512-O16vewA05D0IwfG2N/OFEuVeb17pieaI32mmYXp36V8lp+/pI1YV04rRL9Eyjndj3xQO5SNjAxTh6ul4IlBa3A==} - dev: true /JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} @@ -1179,6 +1295,12 @@ packages: engines: {node: '>=6'} dev: false + /builtins@5.0.1: + resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} + dependencies: + semver: 7.5.4 + dev: false + /bumpp@9.2.1: resolution: {integrity: sha512-mq6/e8+bnIsOMy1VceTLC49WucMIZqd2nYn0e7Et5LhTO3yYQ8OWJsTl/B+uJDs5eywZmJ4Yt1WTEd2HCI35pw==} engines: {node: '>=10'} @@ -1680,6 +1802,12 @@ packages: eslint: 8.56.0 dev: false + /eslint-config-flat-gitignore@0.1.2: + resolution: {integrity: sha512-PcBsqtd5QHEZH4ROvpnRN4EP0qcHh9voCCHgtyHxnJZHGspJREcZn7oPqRG/GfWt9m3C0fkC2l5CuBtMig2wXQ==} + dependencies: + parse-gitignore: 2.0.0 + dev: false + /eslint-config-prettier@9.1.0(eslint@8.56.0): resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true @@ -1720,6 +1848,15 @@ packages: - utf-8-validate dev: true + /eslint-formatting-reporter@0.0.0(eslint@8.56.0): + resolution: {integrity: sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==} + peerDependencies: + eslint: '>=8.40.0' + dependencies: + eslint: 8.56.0 + prettier-linter-helpers: 1.0.0 + dev: false + /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: @@ -1730,6 +1867,14 @@ packages: - supports-color dev: false + /eslint-merge-processors@0.1.0(eslint@8.56.0): + resolution: {integrity: sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==} + peerDependencies: + eslint: '*' + dependencies: + eslint: 8.56.0 + dev: false + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} @@ -1759,6 +1904,10 @@ packages: - supports-color dev: false + /eslint-parser-plain@0.1.0: + resolution: {integrity: sha512-oOeA6FWU0UJT/Rxc3XF5Cq0nbIZbylm7j8+plqq0CZoE6m4u32OXJrR+9iy4srGMmF6v6pmgvP1zPxSRIGh3sg==} + dev: false + /eslint-plugin-antfu@2.1.1(eslint@8.56.0): resolution: {integrity: sha512-HCPo3IP15/gOaruIb1ce6R4LUv/MKBZCmWzqYiLGDFW43WW4juPURnjaQIE3AgWNSoCURqD3wxerXYKzokKTgA==} peerDependencies: @@ -1767,6 +1916,18 @@ packages: eslint: 8.56.0 dev: false + /eslint-plugin-es-x@7.5.0(eslint@8.56.0): + resolution: {integrity: sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=8' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/regexpp': 4.10.0 + eslint: 8.56.0 + eslint-compat-utils: 0.1.2(eslint@8.56.0) + dev: false + /eslint-plugin-eslint-comments@3.2.0(eslint@8.56.0): resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} engines: {node: '>=6.5.0'} @@ -1778,6 +1939,21 @@ packages: ignore: 5.3.0 dev: false + /eslint-plugin-format@0.1.0(eslint@8.56.0): + resolution: {integrity: sha512-IgOu+GEH+PdKnpuPrFzY8q8QgnzAUijDZsNLhpp5jx0Lbu9u968/STcmEZGnIMVBw3zeTNN/FsU6d2Rdgcy6Aw==} + peerDependencies: + eslint: ^8.40.0 + dependencies: + '@dprint/formatter': 0.2.1 + '@dprint/markdown': 0.16.3 + '@dprint/toml': 0.5.4 + eslint: 8.56.0 + eslint-formatting-reporter: 0.0.0(eslint@8.56.0) + eslint-parser-plain: 0.1.0 + prettier: 3.1.1 + synckit: 0.8.8 + dev: false + /eslint-plugin-html@7.1.0: resolution: {integrity: sha512-fNLRraV/e6j8e3XYOC9xgND4j+U7b1Rq+OygMlLcMg+wI/IpVbF+ubQa3R78EjKB9njT6TQOlcK5rFKBVVtdfg==} dependencies: @@ -1833,7 +2009,55 @@ packages: - supports-color dev: false - /eslint-plugin-prettier@5.1.2(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.1.1): + /eslint-plugin-n@16.6.2(eslint@8.56.0): + resolution: {integrity: sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + eslint: '>=7.0.0' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + builtins: 5.0.1 + eslint: 8.56.0 + eslint-plugin-es-x: 7.5.0(eslint@8.56.0) + get-tsconfig: 4.7.2 + globals: 13.24.0 + ignore: 5.3.0 + is-builtin-module: 3.2.1 + is-core-module: 2.13.1 + minimatch: 3.1.2 + resolve: 1.22.8 + semver: 7.5.4 + dev: false + + /eslint-plugin-perfectionist@2.5.0(eslint@8.56.0)(typescript@5.3.3)(vue-eslint-parser@9.4.0): + resolution: {integrity: sha512-F6XXcq4mKKUe/SREoMGQqzgw6cgCgf3pFzkFfQVIGtqD1yXVpQjnhTepzhBeZfxZwgMzR9HO4yH4CUhIQ2WBcQ==} + peerDependencies: + astro-eslint-parser: ^0.16.0 + eslint: '>=8.0.0' + svelte: '>=3.0.0' + svelte-eslint-parser: ^0.33.0 + vue-eslint-parser: '>=9.0.0' + peerDependenciesMeta: + astro-eslint-parser: + optional: true + svelte: + optional: true + svelte-eslint-parser: + optional: true + vue-eslint-parser: + optional: true + dependencies: + '@typescript-eslint/utils': 6.18.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + minimatch: 9.0.3 + natural-compare-lite: 1.4.0 + vue-eslint-parser: 9.4.0(eslint@8.56.0) + transitivePeerDependencies: + - supports-color + - typescript + dev: false + + /eslint-plugin-prettier@5.1.2(@types/eslint@8.56.1)(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.1.1): resolution: {integrity: sha512-dhlpWc9vOwohcWmClFcA+HjlvUpuyynYs0Rf+L/P6/0iQE6vlHW9l5bkfzN62/Stm9fbq8ku46qzde76T1xlSg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -1847,6 +2071,7 @@ packages: eslint-config-prettier: optional: true dependencies: + '@types/eslint': 8.56.1 eslint: 8.56.0 eslint-config-prettier: 9.1.0(eslint@8.56.0) prettier: 3.1.1 @@ -1854,12 +2079,6 @@ packages: synckit: 0.8.8 dev: false - /eslint-plugin-sort-keys@2.3.5: - resolution: {integrity: sha512-2j/XKQ9sNJwK8kIp/U0EvuF6stS6/8aIc53/NskE4C5NRNh4dt3xzbZyOdrVC11cTH6Zo59/pdzA0Kb+2fQGWg==} - dependencies: - natural-compare: 1.4.0 - dev: false - /eslint-plugin-unicorn@50.0.1(eslint@8.56.0): resolution: {integrity: sha512-KxenCZxqSYW0GWHH18okDlOQcpezcitm5aOSz6EnobyJ6BIByiPDviQRjJIUAjG/tMN11958MxaQ+qCoU6lfDA==} engines: {node: '>=16'} @@ -1936,6 +2155,16 @@ packages: - supports-color dev: false + /eslint-processor-vue-blocks@0.1.1(@vue/compiler-sfc@3.4.6)(eslint@8.56.0): + resolution: {integrity: sha512-9+dU5lU881log570oBwpelaJmOfOzSniben7IWEDRYQPPWwlvaV7NhOtsTuUWDqpYT+dtKKWPsgz4OkOi+aZnA==} + peerDependencies: + '@vue/compiler-sfc': ^3.3.0 + eslint: ^8.50.0 + dependencies: + '@vue/compiler-sfc': 3.4.6 + eslint: 8.56.0 + dev: false + /eslint-rule-composer@0.3.0: resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} engines: {node: '>=4.0.0'} @@ -2024,7 +2253,6 @@ packages: /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} @@ -2062,7 +2290,6 @@ packages: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 - dev: true /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2185,7 +2412,6 @@ packages: /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - dev: true /get-tsconfig@4.7.2: resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} @@ -2330,7 +2556,6 @@ packages: /human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - dev: true /husky@8.0.3: resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} @@ -2478,7 +2703,6 @@ packages: /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true /is-text-path@2.0.0: resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} @@ -2583,7 +2807,6 @@ packages: /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - dev: true /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -2729,7 +2952,6 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - dev: true /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} @@ -2781,7 +3003,6 @@ packages: /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -2811,7 +3032,6 @@ packages: /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} - dev: true /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} @@ -2899,7 +3119,10 @@ packages: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: true + + /natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: false /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -2947,7 +3170,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: path-key: 4.0.0 - dev: true /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -3000,7 +3222,6 @@ packages: engines: {node: '>=12'} dependencies: mimic-fn: 4.0.0 - dev: true /open@10.0.3: resolution: {integrity: sha512-dtbI5oW7987hwC9qjJTyABldTaa19SuyJse1QboWv3b0qCcrrLNVDqBx1XgELAjh9QTVQaP/C5b1nhQebd1H2A==} @@ -3068,6 +3289,11 @@ packages: is-hexadecimal: 1.0.4 dev: false + /parse-gitignore@2.0.0: + resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} + engines: {node: '>=14'} + dev: false + /parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -3092,7 +3318,6 @@ packages: /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} - dev: true /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -3177,7 +3402,6 @@ packages: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 - dev: true /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -3202,7 +3426,6 @@ packages: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 - dev: true /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} @@ -3410,11 +3633,9 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - dev: true /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} @@ -3439,7 +3660,6 @@ packages: /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - dev: true /source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} @@ -3536,7 +3756,6 @@ packages: /strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} - dev: true /strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} @@ -3632,7 +3851,6 @@ packages: /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - dev: true /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} diff --git a/src/configs/comments.ts b/src/configs/comments.ts index af1734a..a97d0cc 100644 --- a/src/configs/comments.ts +++ b/src/configs/comments.ts @@ -1,7 +1,7 @@ import { pluginComments } from '../plugins'; -import type { FlatESLintConfig } from 'eslint-define-config'; +import type { FlatConfig } from '../types'; -export async function comments(): Promise { +export function comments(): FlatConfig[] { return [ { plugins: { @@ -14,9 +14,9 @@ export async function comments(): Promise { 'eslint-comments/no-unused-enable': 'error', 'eslint-comments/disable-enable-pair': [ 'error', - {allowWholeFile: true}, + { allowWholeFile: true }, ], }, }, - ] + ]; } diff --git a/src/configs/formatters.ts b/src/configs/formatters.ts new file mode 100644 index 0000000..29a4da2 --- /dev/null +++ b/src/configs/formatters.ts @@ -0,0 +1,161 @@ +import { GLOB_CSS, GLOB_LESS, GLOB_MARKDOWN, GLOB_POSTCSS, GLOB_SCSS } from '../globs'; +import { ensurePackages, interopDefault, parserPlain } from '../utils'; +import { StylisticConfigDefaults } from './stylistic'; +import type { VendoredPrettierOptions } from '../vendor/prettier'; +import type { FlatConfig, OptionsFormatters, StylisticConfig } from '../types'; + +export async function formatters( + options: OptionsFormatters | true = {}, + stylistic: StylisticConfig = {}, +): Promise { + await ensurePackages([ + 'eslint-plugin-format', + ]); + + if (options === true) { + options = { + css: true, + html: true, + markdown: true, + }; + } + + const { + indent, + quotes, + semi, + } = { + ...StylisticConfigDefaults, + ...stylistic, + }; + + const prettierOptions: VendoredPrettierOptions = Object.assign( + { + endOfLine: 'auto', + semi, + singleQuote: quotes === 'single', + tabWidth: typeof indent === 'number' ? indent : 2, + trailingComma: 'all', + useTabs: indent === 'tab', + } satisfies VendoredPrettierOptions, + options.prettierOptions || {}, + ); + + const dprintOptions = Object.assign( + { + indentWidth: typeof indent === 'number' ? indent : 2, + quoteStyle: quotes === 'single' ? 'preferSingle' : 'preferDouble', + useTabs: indent === 'tab', + }, + options.dprintOptions || {}, + ); + + const pluginFormat = await interopDefault(import('eslint-plugin-format')); + + const configs: FlatConfig[] = [ + { + plugins: { + format: pluginFormat, + }, + }, + ]; + + if (options.css) { + configs.push( + { + files: [GLOB_CSS, GLOB_POSTCSS], + languageOptions: { + parser: parserPlain, + }, + rules: { + 'format/prettier': [ + 'error', + { + ...prettierOptions, + parser: 'css', + }, + ], + }, + }, + { + files: [GLOB_SCSS], + languageOptions: { + parser: parserPlain, + }, + rules: { + 'format/prettier': [ + 'error', + { + ...prettierOptions, + parser: 'scss', + }, + ], + }, + }, + { + files: [GLOB_LESS], + languageOptions: { + parser: parserPlain, + }, + rules: { + 'format/prettier': [ + 'error', + { + ...prettierOptions, + parser: 'less', + }, + ], + }, + }, + ); + } + + if (options.html) { + configs.push({ + files: ['**/*.html'], + languageOptions: { + parser: parserPlain, + }, + rules: { + 'format/prettier': [ + 'error', + { + ...prettierOptions, + parser: 'html', + }, + ], + }, + }); + } + + if (options.markdown) { + const formater = options.markdown === true + ? 'prettier' + : options.markdown; + + configs.push({ + files: [GLOB_MARKDOWN], + languageOptions: { + parser: parserPlain, + }, + rules: { + [`format/${formater}`]: [ + 'error', + formater === 'prettier' + ? { + printWidth: 120, + ...prettierOptions, + embeddedLanguageFormatting: 'off', + parser: 'markdown', + } + : { + ...dprintOptions, + language: 'markdown', + }, + ], + }, + }); + } + + return configs; +} diff --git a/src/configs/ignores.ts b/src/configs/ignores.ts index 467952e..69db380 100644 --- a/src/configs/ignores.ts +++ b/src/configs/ignores.ts @@ -1,6 +1,6 @@ import { GLOB_EXCLUDE } from '../globs'; -import type {FlatESLintConfig} from 'eslint-define-config'; +import type { FlatConfig } from '../types'; -export async function ignores(): Promise< FlatESLintConfig[]> { - return [{ ignores: GLOB_EXCLUDE }]; -} \ No newline at end of file +export function ignores(): FlatConfig[] { + return [{ ignores: GLOB_EXCLUDE }]; +} diff --git a/src/configs/imports.ts b/src/configs/imports.ts index bfefb88..ca6ce36 100644 --- a/src/configs/imports.ts +++ b/src/configs/imports.ts @@ -1,74 +1,85 @@ import { pluginAntfu, pluginImport } from '../plugins'; -import { GLOB_MARKDOWN, GLOB_SRC, GLOB_SRC_EXT } from '../globs'; -import type { FlatESLintConfig } from 'eslint-define-config'; +import { GLOB_SRC_EXT, GLOB_TESTS } from '../globs'; +import type { FlatConfig, OptionsStylistic } from '../types'; -export async function imports(): Promise { return [ - { - plugins: { - antfu: pluginAntfu, - import: pluginImport, +export function imports( + options: OptionsStylistic = {}, +): FlatConfig[] { + const { + stylistic = true, + } = options; + + return [ + { + plugins: { + antfu: pluginAntfu, + import: pluginImport, + }, + rules: { + 'antfu/import-dedupe': 'error', + 'antfu/no-import-dist': 'error', + 'antfu/no-import-node-modules-by-path': 'error', + + 'import/first': 'error', + 'import/no-cycle': 'error', + 'import/no-default-export': 'error', + 'import/no-duplicates': 'error', + 'import/no-mutable-exports': 'error', + 'import/no-named-default': 'error', + 'import/no-self-import': 'error', + 'import/no-webpack-loader-syntax': 'error', + 'import/order': [ + 'error', + { + 'groups': [ + 'builtin', + 'external', + 'internal', + 'parent', + 'sibling', + 'index', + 'object', + 'type', + ], + 'pathGroups': [{ group: 'internal', pattern: '{{@,~}/,#}**' }], + 'pathGroupsExcludedImportTypes': ['type'], + 'newlines-between': 'never', + }, + ], + 'import/max-dependencies': [ + 'error', + { + max: 20, + ignoreTypeImports: false, + }, + ], + ...stylistic + ? { + 'import/newline-after-import': ['error', { considerComments: true, count: 1 }], + } + : {}, + }, }, - rules: { - 'antfu/import-dedupe': 'error', - 'import/first': 'error', - 'import/no-cycle': 'error', - 'import/no-default-export': 'error', - 'import/no-duplicates': 'error', - 'import/no-mutable-exports': 'error', - 'import/no-named-default': 'error', - 'import/no-self-import': 'error', - 'import/no-webpack-loader-syntax': 'error', - 'import/order': [ - 'error', - { - groups: [ - 'builtin', - 'external', - 'internal', - 'parent', - 'sibling', - 'index', - 'object', - 'type', - ], - pathGroups: [{ group: 'internal', pattern: '{{@,~}/,#}**' }], - pathGroupsExcludedImportTypes: ['type'], - 'newlines-between': 'never', - }, - ], - 'import/max-dependencies': [ - 'error', - { - max: 20, - ignoreTypeImports: false, - }, + { + files: [ + ...GLOB_TESTS, ], + rules: { + 'import/max-dependencies': 'off', + }, }, - }, - { - files: [ - '**/*.spec.ts', - '**/*.cy.ts' - ], - rules: { - 'import/max-dependencies': 'off' - } - }, - { - files: [ - `**/*config*.${GLOB_SRC_EXT}`, - `**/views/${GLOB_SRC}`, - `**/pages/${GLOB_SRC}`, - `**/{index,vite,esbuild,rollup,webpack,rspack}.ts`, - '**/*.d.ts', - `${GLOB_MARKDOWN}/**`, - ], - plugins: { - import: pluginImport, + { + files: [`**/*.config.${GLOB_SRC_EXT}`], + rules: { + 'import/no-default-export': 'off', + }, }, - rules: { - 'import/no-default-export': 'off', + { + files: ['**/bin/**/*', `**/bin.${GLOB_SRC_EXT}`, `**/eslint.config.${GLOB_SRC_EXT}`], + rules: { + 'antfu/no-import-dist': 'off', + 'antfu/no-import-node-modules-by-path': 'off', + }, }, - }, -]; -} \ No newline at end of file + ]; +} diff --git a/src/configs/index.ts b/src/configs/index.ts index 8f91634..d633519 100644 --- a/src/configs/index.ts +++ b/src/configs/index.ts @@ -1,5 +1,7 @@ export * from './comments'; +export * from './formatters'; + export * from './ignores'; export * from './imports'; @@ -10,12 +12,14 @@ export * from './jsonc'; export * from './markdown'; -export * from './prettier'; +export * from './node'; -export * from './sort-keys'; +export * from './perfectionist'; export * from './sort'; +export * from './stylistic'; + export * from './typescript'; export * from './unicorn'; diff --git a/src/configs/javascript.ts b/src/configs/javascript.ts index 3f97501..252980e 100644 --- a/src/configs/javascript.ts +++ b/src/configs/javascript.ts @@ -1,182 +1,203 @@ import globals from 'globals'; -import { pluginUnusedImports } from '../plugins'; -import { isInEditor } from '../env'; -import type { FlatESLintConfig } from 'eslint-define-config'; +import { pluginAntfu, pluginUnusedImports } from '../plugins'; +import { GLOB_SRC, GLOB_SRC_EXT, GLOB_TESTS } from '../globs'; +import type { FlatConfig, OptionsIsInEditor, OptionsOverrides } from '../types'; -export async function javascript(): Promise { return [ - { - languageOptions: { - globals: { - ...globals.browser, - ...globals.es2021, - ...globals.node, - }, - parserOptions: { - ecmaFeatures: { - jsx: true, +export function javascript( + options: OptionsIsInEditor & OptionsOverrides = {}, +): FlatConfig[] { + const { + isInEditor = false, + overrides = {}, + } = options; + + return [ + { + languageOptions: { + ecmaVersion: 2022, + globals: { + ...globals.browser, + ...globals.es2021, + ...globals.node, + document: 'readonly', + navigator: 'readonly', + window: 'readonly', + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 2022, + sourceType: 'module', }, sourceType: 'module', }, - sourceType: 'module', - }, - plugins: { - 'unused-imports': pluginUnusedImports, - }, - rules: { - 'array-callback-return': 'error', - 'block-scoped-var': 'error', - 'constructor-super': 'error', - 'dot-notation': 'warn', - eqeqeq: ['error', 'smart'], - 'for-direction': 'error', - 'getter-return': 'error', - 'no-alert': 'warn', - 'no-async-promise-executor': 'error', - 'no-case-declarations': 'error', - 'no-class-assign': 'error', - 'no-compare-neg-zero': 'error', - 'no-cond-assign': 'error', - 'no-console': ['warn', { allow: ['warn', 'error'] }], - 'no-const-assign': 'error', - 'no-constant-condition': 'error', - 'no-control-regex': 'error', - 'no-debugger': 'warn', - 'no-delete-var': 'error', - 'no-dupe-args': 'error', - 'no-dupe-class-members': 'error', - 'no-dupe-else-if': 'error', - 'no-dupe-keys': 'error', - 'no-duplicate-case': 'error', - 'no-duplicate-imports': 'error', - 'no-empty': ['error', { allowEmptyCatch: true }], - 'no-empty-character-class': 'error', - 'no-empty-pattern': 'error', - 'no-ex-assign': 'error', - 'no-extra-boolean-cast': 'error', - 'no-fallthrough': ['warn', { commentPattern: 'break[\\s\\w]*omitted' }], - 'no-func-assign': 'error', - 'no-global-assign': 'error', - 'no-import-assign': 'error', - 'no-inner-declarations': 'error', - 'no-invalid-regexp': 'error', - 'no-irregular-whitespace': 'error', - 'no-lonely-if': 'error', - 'no-loss-of-precision': 'error', - 'no-misleading-character-class': 'error', - 'no-mixed-spaces-and-tabs': 'error', - 'no-multi-str': 'error', - 'no-new-symbol': 'error', - 'no-nonoctal-decimal-escape': 'error', - 'no-obj-calls': 'error', - 'no-octal': 'error', - 'no-prototype-builtins': 'error', - 'no-redeclare': 'error', - 'no-regex-spaces': 'error', - 'no-restricted-syntax': [ - 'error', - //'ForInStatement', TODO: for futrue evaluation - 'LabeledStatement', - 'WithStatement', - ], - 'no-self-assign': 'error', - 'no-setter-return': 'error', - 'no-shadow-restricted-names': 'error', - 'no-sparse-arrays': 'error', - 'no-this-before-super': 'error', - 'no-undef': 'error', - 'no-unexpected-multiline': 'error', - 'no-unreachable': 'error', - 'no-unsafe-finally': 'error', + linterOptions: { + reportUnusedDisableDirectives: true, + }, + plugins: { + 'antfu': pluginAntfu, + 'unused-imports': pluginUnusedImports, + }, + rules: { + 'accessor-pairs': ['error', { enforceForClassMembers: true, setWithoutGet: true }], - 'no-unsafe-negation': 'error', - 'no-unsafe-optional-chaining': 'error', - 'no-unused-expressions': [ - 'error', - { - allowShortCircuit: true, - allowTaggedTemplates: true, - allowTernary: true, - }, - ], - 'no-unused-labels': 'error', - 'no-unused-vars': 'off', - 'no-useless-backreference': 'error', - 'no-useless-catch': 'error', - 'no-useless-escape': 'error', - 'no-void': 'error', - 'no-with': 'error', - 'object-shorthand': [ - 'error', - 'always', - { avoidQuotes: true, ignoreConstructors: false }, - ], - 'prefer-arrow-callback': [ - 'error', - { allowNamedFunctions: false, allowUnboundThis: true }, - ], - 'prefer-const': [ - 'warn', - { destructuring: 'all', ignoreReadBeforeAssign: true }, - ], - 'prefer-exponentiation-operator': 'error', - 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }], - 'prefer-rest-params': 'error', - 'prefer-spread': 'error', - 'prefer-template': 'error', - 'require-await': 'warn', - 'require-yield': 'error', - 'sort-imports': [ - 'error', - { - allowSeparatedGroups: false, - ignoreCase: false, - ignoreDeclarationSort: true, - ignoreMemberSort: false, - memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], - }, - ], - 'unicode-bom': ['error', 'never'], - 'unused-imports/no-unused-imports': isInEditor ? 'off' : 'error', - 'unused-imports/no-unused-vars': [ - 'error', - { args: 'after-used', ignoreRestSiblings: true }, - ], - 'use-isnan': [ - 'error', - { enforceForIndexOf: true, enforceForSwitchCase: true }, - ], - 'valid-typeof': ['error', { requireStringLiterals: true }], - 'vars-on-top': 'error', - 'wrap-iife': ['error', 'any', { functionPrototypeMethods: true }], + 'array-callback-return': 'error', + 'block-scoped-var': 'error', + 'constructor-super': 'error', + 'dot-notation': 'warn', + 'eqeqeq': ['error', 'smart'], + 'for-direction': 'error', + 'getter-return': 'error', + 'no-alert': 'warn', + 'no-async-promise-executor': 'error', + 'no-case-declarations': 'error', + 'no-class-assign': 'error', + 'no-compare-neg-zero': 'error', + 'no-cond-assign': 'error', + 'no-console': ['warn', { allow: ['warn', 'error'] }], + 'no-const-assign': 'error', + 'no-constant-condition': 'error', + 'no-control-regex': 'error', + 'no-debugger': 'warn', + 'no-delete-var': 'error', + 'no-dupe-args': 'error', + 'no-dupe-class-members': 'error', + 'no-dupe-else-if': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-duplicate-imports': 'error', + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-empty-character-class': 'error', + 'no-empty-pattern': 'error', + 'no-ex-assign': 'error', + 'no-extra-boolean-cast': 'error', + 'no-fallthrough': ['warn', { commentPattern: 'break[\\s\\w]*omitted' }], + 'no-func-assign': 'error', + 'no-global-assign': 'error', + 'no-import-assign': 'error', + 'no-inner-declarations': 'error', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'error', + 'no-lonely-if': 'error', + 'no-loss-of-precision': 'error', + 'no-misleading-character-class': 'error', + 'no-mixed-spaces-and-tabs': 'error', + 'no-multi-str': 'error', + 'no-new-symbol': 'error', + 'no-nonoctal-decimal-escape': 'error', + 'no-obj-calls': 'error', + 'no-octal': 'error', + 'no-prototype-builtins': 'error', + 'no-redeclare': 'error', + 'no-regex-spaces': 'error', + 'no-restricted-syntax': [ + 'error', + // 'ForInStatement', TODO: for futrue evaluation + 'LabeledStatement', + 'WithStatement', + ], + 'no-self-assign': 'error', + 'no-setter-return': 'error', + 'no-shadow-restricted-names': 'error', + 'no-sparse-arrays': 'error', + 'no-this-before-super': 'error', + 'no-undef': 'error', + 'no-unexpected-multiline': 'error', + 'no-unreachable': 'error', + 'no-unsafe-finally': 'error', - // extended rules - curly: ['error', 'all'], - 'arrow-body-style': ['error', 'as-needed'], + 'no-unsafe-negation': 'error', + 'no-unsafe-optional-chaining': 'error', + 'no-unused-expressions': [ + 'error', + { + allowShortCircuit: true, + allowTaggedTemplates: true, + allowTernary: true, + }, + ], + 'no-unused-labels': 'error', + 'no-unused-vars': 'off', + 'no-useless-backreference': 'error', + 'no-useless-catch': 'error', + 'no-useless-escape': 'error', + 'no-void': 'error', + 'no-with': 'error', + 'object-shorthand': [ + 'error', + 'always', + { avoidQuotes: true, ignoreConstructors: false }, + ], + 'prefer-arrow-callback': [ + 'error', + { allowNamedFunctions: false, allowUnboundThis: true }, + ], + 'prefer-const': [ + 'warn', + { destructuring: 'all', ignoreReadBeforeAssign: true }, + ], + 'prefer-exponentiation-operator': 'error', + 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }], + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'prefer-template': 'error', + 'require-await': 'warn', + 'require-yield': 'error', + 'sort-imports': [ + 'error', + { + allowSeparatedGroups: false, + ignoreCase: false, + ignoreDeclarationSort: true, + ignoreMemberSort: false, + memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], + }, + ], + 'unicode-bom': ['error', 'never'], + 'unused-imports/no-unused-imports': isInEditor ? 'off' : 'error', + 'unused-imports/no-unused-vars': [ + 'error', + { args: 'after-used', ignoreRestSiblings: true }, + ], + 'use-isnan': [ + 'error', + { enforceForIndexOf: true, enforceForSwitchCase: true }, + ], + 'valid-typeof': ['error', { requireStringLiterals: true }], + 'vars-on-top': 'error', + 'wrap-iife': ['error', 'any', { functionPrototypeMethods: true }], - 'padding-line-between-statements': [ - 'error', - { - blankLine: 'always', - prev: '*', - next: ['class', 'function', 'export'], - }, - ], - 'max-lines': ['error', { max: 400 }], + // extended rules + 'curly': ['error', 'all'], + 'arrow-body-style': ['error', 'as-needed'], + + 'padding-line-between-statements': [ + 'error', + { + blankLine: 'always', + prev: '*', + next: ['class', 'function', 'export'], + }, + ], + 'max-lines': ['error', { max: 400 }], + + ...overrides, + }, }, - }, - { - files: ['**/scripts/*', '**/cli.*'], - rules: { - 'no-console': 'off', - 'max-lines': 'off', + { + files: [`**/scripts/${GLOB_SRC}`, `**/cli.${GLOB_SRC_EXT}`], + rules: { + 'no-console': 'off', + 'max-lines': 'off', + }, }, - }, - { - files: ['**/*.{test,spec,cy}.[jt]s?(x)'], - rules: { - 'no-unused-expressions': 'off', - 'max-lines': 'off', + { + files: [...GLOB_TESTS], + rules: { + 'no-unused-expressions': 'off', + 'max-lines': 'off', + }, }, - }, -] + ]; }; diff --git a/src/configs/jsonc.ts b/src/configs/jsonc.ts index 6237de6..18f78b5 100644 --- a/src/configs/jsonc.ts +++ b/src/configs/jsonc.ts @@ -1,21 +1,82 @@ -import { parserJsonc, pluginJsonc } from '../plugins'; import { GLOB_JSON, GLOB_JSON5, GLOB_JSONC } from '../globs'; -import type { FlatESLintConfig, Rules } from 'eslint-define-config'; +import { interopDefault } from '../utils'; +import type { FlatConfig, OptionsFiles, OptionsOverrides, OptionsStylistic } from '../types'; -export async function jsonc(): Promise { return [ - { - files: [GLOB_JSON, GLOB_JSON5, GLOB_JSONC], - languageOptions: { - parser: parserJsonc, - }, - plugins: { - jsonc: pluginJsonc, - }, - rules: { - ...(pluginJsonc.configs['recommended-with-jsonc'].rules as Rules), - 'jsonc/quote-props': 'off', - 'jsonc/quotes': 'off', +export async function jsonc( + options: OptionsFiles & OptionsStylistic & OptionsOverrides = {}, +): Promise { + const { + files = [GLOB_JSON, GLOB_JSON5, GLOB_JSONC], + overrides = {}, + stylistic = true, + } = options; + + const { + indent = 2, + } = typeof stylistic === 'boolean' ? {} : stylistic; + + const [ + pluginJsonc, + parserJsonc, + ] = await Promise.all([ + interopDefault(import('eslint-plugin-jsonc')), + interopDefault(import('jsonc-eslint-parser')), + ] as const); + + return [ + { + files, + languageOptions: { + parser: parserJsonc, + }, + plugins: { + jsonc: pluginJsonc as any, + }, + rules: { + 'jsonc/no-bigint-literals': 'error', + 'jsonc/no-binary-expression': 'error', + 'jsonc/no-binary-numeric-literals': 'error', + 'jsonc/no-dupe-keys': 'error', + 'jsonc/no-escape-sequence-in-identifier': 'error', + 'jsonc/no-floating-decimal': 'error', + 'jsonc/no-hexadecimal-numeric-literals': 'error', + 'jsonc/no-infinity': 'error', + 'jsonc/no-multi-str': 'error', + 'jsonc/no-nan': 'error', + 'jsonc/no-number-props': 'error', + 'jsonc/no-numeric-separators': 'error', + 'jsonc/no-octal': 'error', + 'jsonc/no-octal-escape': 'error', + 'jsonc/no-octal-numeric-literals': 'error', + 'jsonc/no-parenthesized': 'error', + 'jsonc/no-plus-sign': 'error', + 'jsonc/no-regexp-literals': 'error', + 'jsonc/no-sparse-arrays': 'error', + 'jsonc/no-template-literals': 'error', + 'jsonc/no-undefined-value': 'error', + 'jsonc/no-unicode-codepoint-escapes': 'error', + 'jsonc/no-useless-escape': 'error', + 'jsonc/space-unary-ops': 'error', + 'jsonc/valid-json-number': 'error', + 'jsonc/vue-custom-block/no-parsing-error': 'error', + + ...stylistic + ? { + 'jsonc/array-bracket-spacing': ['error', 'never'], + 'jsonc/comma-dangle': ['error', 'never'], + 'jsonc/comma-style': ['error', 'last'], + 'jsonc/indent': ['error', indent], + 'jsonc/key-spacing': ['error', { afterColon: true, beforeColon: false }], + 'jsonc/object-curly-newline': ['error', { consistent: true, multiline: true }], + 'jsonc/object-curly-spacing': ['error', 'always'], + 'jsonc/object-property-newline': ['error', { allowMultiplePropertiesPerLine: true }], + 'jsonc/quote-props': 'error', + 'jsonc/quotes': 'error', + } + : {}, + + ...overrides, + }, }, - }, -]; + ]; } diff --git a/src/configs/markdown.ts b/src/configs/markdown.ts index 1be4016..d080aaf 100644 --- a/src/configs/markdown.ts +++ b/src/configs/markdown.ts @@ -1,47 +1,106 @@ -import { pluginMarkdown } from '../plugins'; -import { GLOB_MARKDOWN, GLOB_SRC, GLOB_VUE } from '../globs'; -import type { FlatESLintConfig } from 'eslint-define-config'; - -export async function markdown(): Promise {return [ - { - files: [GLOB_MARKDOWN], - plugins: { - markdown: pluginMarkdown, +import { mergeProcessors, processorPassThrough } from 'eslint-merge-processors'; +import { GLOB_MARKDOWN, GLOB_MARKDOWN_CODE, GLOB_MARKDOWN_IN_MARKDOWN } from '../globs'; +import { interopDefault, parserPlain } from '../utils'; +import type { FlatConfig, OptionsComponentExts, OptionsFiles, OptionsOverrides } from '../types'; + +export async function markdown( + options: OptionsFiles & OptionsComponentExts & OptionsOverrides = {}, +): Promise { + const { + componentExts = [], + files = [GLOB_MARKDOWN], + overrides = {}, + } = options; + + // @ts-expect-error missing types + const markdown = await interopDefault(import('eslint-plugin-markdown')); + + return [ + { + plugins: { + markdown, + }, }, - processor: 'markdown/markdown', - }, - { - files: [`${GLOB_MARKDOWN}/${GLOB_SRC}`, `${GLOB_MARKDOWN}/${GLOB_VUE}`], - languageOptions: { - parserOptions: { - ecmaFeatures: { - impliedStrict: true, - }, + { + files, + ignores: [GLOB_MARKDOWN_IN_MARKDOWN], + // `eslint-plugin-markdown` only creates virtual files for code blocks, + // but not the markdown file itself. We use `eslint-merge-processors` to + // add a pass-through processor for the markdown file itself. + processor: mergeProcessors([ + markdown.processors.markdown, + processorPassThrough, + ]), + }, + { + files, + languageOptions: { + parser: parserPlain, }, }, - rules: { - ...pluginMarkdown.configs.recommended.overrides[1].rules, - - '@typescript-eslint/comma-dangle': 'off', - '@typescript-eslint/consistent-type-imports': 'off', - '@typescript-eslint/no-extraneous-class': 'off', - '@typescript-eslint/no-namespace': 'off', - '@typescript-eslint/no-redeclare': 'off', - '@typescript-eslint/no-require-imports': 'off', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/no-var-requires': 'off', - - 'no-alert': 'off', - 'no-console': 'off', - 'no-restricted-imports': 'off', - 'no-undef': 'off', - 'no-unused-expressions': 'off', - 'no-unused-vars': 'off', - - 'unused-imports/no-unused-imports': 'off', - 'unused-imports/no-unused-vars': 'off', + { + files: [ + GLOB_MARKDOWN_CODE, + ...componentExts.map(ext => `${GLOB_MARKDOWN}/**/*.${ext}`), + ], + languageOptions: { + parserOptions: { + ecmaFeatures: { + impliedStrict: true, + }, + }, + }, + rules: { + 'import/newline-after-import': 'off', + + 'no-alert': 'off', + 'no-console': 'off', + 'no-labels': 'off', + 'no-lone-blocks': 'off', + 'no-restricted-syntax': 'off', + 'no-undef': 'off', + 'no-unused-expressions': 'off', + 'no-unused-labels': 'off', + 'no-unused-vars': 'off', + + 'node/prefer-global/process': 'off', + 'style/comma-dangle': 'off', + + 'style/eol-last': 'off', + '@typescript-eslint/consistent-type-imports': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-redeclare': 'off', + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-var-requires': 'off', + + 'unicode-bom': 'off', + 'unused-imports/no-unused-imports': 'off', + 'unused-imports/no-unused-vars': 'off', + + // Type aware rules + ...{ + '@typescript-eslint/await-thenable': 'off', + '@typescript-eslint/dot-notation': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-for-in-array': 'off', + '@typescript-eslint/no-implied-eval': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-throw-literal': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/restrict-plus-operands': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/unbound-method': 'off', + }, + + ...overrides, + }, }, - }, -]; + ]; } diff --git a/src/configs/node.ts b/src/configs/node.ts new file mode 100644 index 0000000..0c0c6d3 --- /dev/null +++ b/src/configs/node.ts @@ -0,0 +1,22 @@ +import { pluginNode } from '../plugins'; +import type { FlatConfig } from '../types'; + +export function node(): FlatConfig[] { + return [ + { + plugins: { + node: pluginNode, + }, + rules: { + 'node/handle-callback-err': ['error', '^(err|error)$'], + 'node/no-deprecated-api': 'error', + 'node/no-exports-assign': 'error', + 'node/no-new-require': 'error', + 'node/no-path-concat': 'error', + 'node/prefer-global/buffer': ['error', 'never'], + 'node/prefer-global/process': ['error', 'never'], + 'node/process-exit-as-throw': 'error', + }, + }, + ]; +} diff --git a/src/configs/perfectionist.ts b/src/configs/perfectionist.ts new file mode 100644 index 0000000..998ddf6 --- /dev/null +++ b/src/configs/perfectionist.ts @@ -0,0 +1,17 @@ +import { pluginPerfectionist } from '../plugins'; +import type { FlatConfig } from '../types'; + +/** + * Perfectionist plugin for props and items sorting. + * + * @see https://github.com/azat-io/eslint-plugin-perfectionist + */ +export function perfectionist(): FlatConfig[] { + return [ + { + plugins: { + perfectionist: pluginPerfectionist, + }, + }, + ]; +} diff --git a/src/configs/prettier.ts b/src/configs/prettier.ts deleted file mode 100644 index b373280..0000000 --- a/src/configs/prettier.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { configPrettier, pluginPrettier } from '../plugins'; -import type { FlatESLintConfig } from 'eslint-define-config'; - -const prettierConflictRules = { ...configPrettier.rules }; -delete prettierConflictRules['vue/html-self-closing']; - -export async function prettier(): Promise { - return [ - { - plugins: { - prettier: pluginPrettier, - }, - rules: { - ...prettierConflictRules, - ...pluginPrettier.configs.recommended.rules, - 'prettier/prettier': 'error', - }, - }, - ]; -} diff --git a/src/configs/sort-keys.ts b/src/configs/sort-keys.ts deleted file mode 100644 index 67605b2..0000000 --- a/src/configs/sort-keys.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { pluginSortKeys } from '../plugins'; -import type { FlatESLintConfig } from 'eslint-define-config'; - -export async function sortKeys(): Promise { - return [ - { - plugins: { - 'sort-keys': pluginSortKeys, - }, - }, - ]; -} diff --git a/src/configs/sort.ts b/src/configs/sort.ts index c8cfbb1..f02c572 100644 --- a/src/configs/sort.ts +++ b/src/configs/sort.ts @@ -1,205 +1,205 @@ -import type { FlatESLintConfig } from 'eslint-define-config'; +import type { FlatConfig } from '../types'; -export const sortPackageJson: FlatESLintConfig[] = [ - { - files: ['**/package.json'], - rules: { - 'jsonc/sort-array-values': [ - 'error', - { - order: { type: 'asc' }, - pathPattern: '^files$', - }, - ], - 'jsonc/sort-keys': [ - 'error', - { - order: [ - 'name', - 'version', - 'private', - 'packageManager', - 'description', - 'type', - 'keywords', - 'license', - 'homepage', - 'bugs', - 'repository', - 'author', - 'contributors', - 'funding', - 'files', - 'main', - 'module', - 'types', - 'exports', - 'typesVersions', - 'sideEffects', - 'unpkg', - 'jsdelivr', - 'browser', - 'bin', - 'man', - 'directories', - 'publishConfig', - 'scripts', - 'peerDependencies', - 'peerDependenciesMeta', - 'optionalDependencies', - 'dependencies', - 'devDependencies', - 'engines', - 'config', - 'overrides', - 'pnpm', - 'husky', - 'lint-staged', - 'eslintConfig', - 'prettier', - ], - pathPattern: '^$', - }, - { - order: { type: 'asc' }, - pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies$', - }, - { - order: ['types', 'require', 'import', 'default'], - pathPattern: '^exports.*$', - }, - { - order: { type: 'asc' }, - pathPattern: '^resolutions$', - }, - { - order: { type: 'asc' }, - pathPattern: '^pnpm.overrides$', - }, - ], +export function sortPackageJson(): FlatConfig[] { + return [ + { + files: ['**/package.json'], + rules: { + 'jsonc/sort-array-values': [ + 'error', + { + order: { type: 'asc' }, + pathPattern: '^files$', + }, + ], + 'jsonc/sort-keys': [ + 'error', + { + order: [ + 'name', + 'version', + 'private', + 'packageManager', + 'description', + 'type', + 'keywords', + 'license', + 'homepage', + 'bugs', + 'repository', + 'author', + 'contributors', + 'funding', + 'files', + 'main', + 'module', + 'types', + 'exports', + 'typesVersions', + 'sideEffects', + 'unpkg', + 'jsdelivr', + 'browser', + 'bin', + 'man', + 'directories', + 'publishConfig', + 'scripts', + 'peerDependencies', + 'peerDependenciesMeta', + 'optionalDependencies', + 'dependencies', + 'devDependencies', + 'engines', + 'config', + 'overrides', + 'pnpm', + 'husky', + 'lint-staged', + 'eslintConfig', + 'prettier', + ], + pathPattern: '^$', + }, + { + order: { type: 'asc' }, + pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$', + }, + { + order: { type: 'asc' }, + pathPattern: '^(?:resolutions|overrides|pnpm.overrides)$', + }, + { + order: ['types', 'require', 'import', 'default'], + pathPattern: '^exports.*$', + }, + ], + }, }, - }, -]; + ]; +} -export const sortTsconfig: FlatESLintConfig[] = [ - { - files: ['**/tsconfig.json', '**/tsconfig.*.json'], - rules: { - 'jsonc/sort-keys': [ - 'error', - { - order: [ - 'extends', - 'compilerOptions', - 'references', - 'files', - 'include', - 'exclude', - ], - pathPattern: '^$', - }, - { - order: [ - /* Projects */ - 'incremental', - 'composite', - 'tsBuildInfoFile', - 'disableSourceOfProjectReferenceRedirect', - 'disableSolutionSearching', - 'disableReferencedProjectLoad', - /* Language and Environment */ - 'target', - 'jsx', - 'jsxFactory', - 'jsxFragmentFactory', - 'jsxImportSource', - 'lib', - 'moduleDetection', - 'noLib', - 'reactNamespace', - 'useDefineForClassFields', - 'emitDecoratorMetadata', - 'experimentalDecorators', - /* Modules */ - 'baseUrl', - 'rootDir', - 'rootDirs', - 'customConditions', - 'module', - 'moduleResolution', - 'moduleSuffixes', - 'noResolve', - 'paths', - 'resolveJsonModule', - 'resolvePackageJsonExports', - 'resolvePackageJsonImports', - 'typeRoots', - 'types', - 'allowArbitraryExtensions', - 'allowImportingTsExtensions', - 'allowUmdGlobalAccess', - /* JavaScript Support */ - 'allowJs', - 'checkJs', - 'maxNodeModuleJsDepth', - /* Type Checking */ - 'strict', - 'strictBindCallApply', - 'strictFunctionTypes', - 'strictNullChecks', - 'strictPropertyInitialization', - 'allowUnreachableCode', - 'allowUnusedLabels', - 'alwaysStrict', - 'exactOptionalPropertyTypes', - 'noFallthroughCasesInSwitch', - 'noImplicitAny', - 'noImplicitOverride', - 'noImplicitReturns', - 'noImplicitThis', - 'noPropertyAccessFromIndexSignature', - 'noUncheckedIndexedAccess', - 'noUnusedLocals', - 'noUnusedParameters', - 'useUnknownInCatchVariables', - /* Emit */ - 'declaration', - 'declarationDir', - 'declarationMap', - 'downlevelIteration', - 'emitBOM', - 'emitDeclarationOnly', - 'importHelpers', - 'importsNotUsedAsValues', - 'inlineSourceMap', - 'inlineSources', - 'mapRoot', - 'newLine', - 'noEmit', - 'noEmitHelpers', - 'noEmitOnError', - 'outDir', - 'outFile', - 'preserveConstEnums', - 'preserveValueImports', - 'removeComments', - 'sourceMap', - 'sourceRoot', - 'stripInternal', - /* Interop Constraints */ - 'allowSyntheticDefaultImports', - 'esModuleInterop', - 'forceConsistentCasingInFileNames', - 'isolatedModules', - 'preserveSymlinks', - 'verbatimModuleSyntax', - /* Completeness */ - 'skipDefaultLibCheck', - 'skipLibCheck', - ], - pathPattern: '^compilerOptions$', - }, - ], +export function sortTsconfig(): FlatConfig[] { + return [ + { + files: ['**/tsconfig.json', '**/tsconfig.*.json'], + rules: { + 'jsonc/sort-keys': [ + 'error', + { + order: [ + 'extends', + 'compilerOptions', + 'references', + 'files', + 'include', + 'exclude', + ], + pathPattern: '^$', + }, + { + order: [ + /* Projects */ + 'incremental', + 'composite', + 'tsBuildInfoFile', + 'disableSourceOfProjectReferenceRedirect', + 'disableSolutionSearching', + 'disableReferencedProjectLoad', + /* Language and Environment */ + 'target', + 'jsx', + 'jsxFactory', + 'jsxFragmentFactory', + 'jsxImportSource', + 'lib', + 'moduleDetection', + 'noLib', + 'reactNamespace', + 'useDefineForClassFields', + 'emitDecoratorMetadata', + 'experimentalDecorators', + /* Modules */ + 'baseUrl', + 'rootDir', + 'rootDirs', + 'customConditions', + 'module', + 'moduleResolution', + 'moduleSuffixes', + 'noResolve', + 'paths', + 'resolveJsonModule', + 'resolvePackageJsonExports', + 'resolvePackageJsonImports', + 'typeRoots', + 'types', + 'allowArbitraryExtensions', + 'allowImportingTsExtensions', + 'allowUmdGlobalAccess', + /* JavaScript Support */ + 'allowJs', + 'checkJs', + 'maxNodeModuleJsDepth', + /* Type Checking */ + 'strict', + 'strictBindCallApply', + 'strictFunctionTypes', + 'strictNullChecks', + 'strictPropertyInitialization', + 'allowUnreachableCode', + 'allowUnusedLabels', + 'alwaysStrict', + 'exactOptionalPropertyTypes', + 'noFallthroughCasesInSwitch', + 'noImplicitAny', + 'noImplicitOverride', + 'noImplicitReturns', + 'noImplicitThis', + 'noPropertyAccessFromIndexSignature', + 'noUncheckedIndexedAccess', + 'noUnusedLocals', + 'noUnusedParameters', + 'useUnknownInCatchVariables', + /* Emit */ + 'declaration', + 'declarationDir', + 'declarationMap', + 'downlevelIteration', + 'emitBOM', + 'emitDeclarationOnly', + 'importHelpers', + 'importsNotUsedAsValues', + 'inlineSourceMap', + 'inlineSources', + 'mapRoot', + 'newLine', + 'noEmit', + 'noEmitHelpers', + 'noEmitOnError', + 'outDir', + 'outFile', + 'preserveConstEnums', + 'preserveValueImports', + 'removeComments', + 'sourceMap', + 'sourceRoot', + 'stripInternal', + /* Interop Constraints */ + 'allowSyntheticDefaultImports', + 'esModuleInterop', + 'forceConsistentCasingInFileNames', + 'isolatedModules', + 'preserveSymlinks', + 'verbatimModuleSyntax', + /* Completeness */ + 'skipDefaultLibCheck', + 'skipLibCheck', + ], + pathPattern: '^compilerOptions$', + }, + ], + }, }, - }, -]; + ]; +} diff --git a/src/configs/stylistic.ts b/src/configs/stylistic.ts new file mode 100644 index 0000000..5facd97 --- /dev/null +++ b/src/configs/stylistic.ts @@ -0,0 +1,56 @@ +import { interopDefault } from '../utils'; +import { pluginAntfu } from '../plugins'; +import type { FlatConfig, OptionsOverrides, StylisticConfig } from '../types'; + +export const StylisticConfigDefaults: StylisticConfig = { + indent: 2, + jsx: true, + quotes: 'single', + semi: true, +}; + +export async function stylistic( + options: StylisticConfig & OptionsOverrides = {}, +): Promise { + const { + indent, + jsx, + overrides = {}, + quotes, + semi, + } = { + ...StylisticConfigDefaults, + ...options, + }; + + const pluginStylistic = await interopDefault(import('@stylistic/eslint-plugin')); + + const config = pluginStylistic.configs.customize({ + flat: true, + indent, + jsx, + pluginName: 'style', + quotes, + semi, + }); + + return [ + { + plugins: { + antfu: pluginAntfu, + style: pluginStylistic, + }, + rules: { + ...config.rules, + + 'antfu/consistent-list-newline': 'error', + 'antfu/if-newline': 'error', + 'antfu/top-level-function': 'error', + + 'curly': ['error', 'multi-or-nest', 'consistent'], + + ...overrides, + }, + }, + ]; +} diff --git a/src/configs/typescript.ts b/src/configs/typescript.ts index 81d1433..1029453 100644 --- a/src/configs/typescript.ts +++ b/src/configs/typescript.ts @@ -1,20 +1,54 @@ -import { GLOB_TS, GLOB_TSX } from '../globs'; -import { parserTypeScript, pluginAntfu, pluginTypeScript } from '../plugins'; -import type { FlatESLintConfig } from 'eslint-define-config'; +import process from 'node:process'; +import { GLOB_SRC, GLOB_TS, GLOB_TSX } from '../globs'; +import { pluginAntfu } from '../plugins'; +import { interopDefault, toArray } from '../utils'; +import type { + FlatConfig, + OptionsComponentExts, + OptionsFiles, + OptionsOverrides, + OptionsTypeScriptParserOptions, + OptionsTypeScriptWithTypes, +} from '../types'; -const typeAwareRules = (enabled: boolean) => { - if (!enabled) { - return {}; - } +export async function typescript( + options: OptionsFiles & OptionsComponentExts & OptionsOverrides & OptionsTypeScriptWithTypes & OptionsTypeScriptParserOptions = {}, +): Promise { + const { + componentExts = [], + overrides = {}, + parserOptions = {}, + } = options; - return { + const files = options.files ?? [ + GLOB_SRC, + ...componentExts.map(ext => `**/*.${ext}`), + ]; + + const filesTypeAware = options.filesTypeAware ?? [GLOB_TS, GLOB_TSX]; + + const typeAwareRules: FlatConfig['rules'] = { 'dot-notation': 'off', 'no-implied-eval': 'off', 'no-throw-literal': 'off', - '@typescript-eslint/no-throw-literal': 'error', - '@typescript-eslint/no-implied-eval': 'error', + '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/dot-notation': ['error', { allowKeywords: true }], '@typescript-eslint/no-floating-promises': ['error', { ignoreIIFE: true }], + '@typescript-eslint/no-for-in-array': 'error', + '@typescript-eslint/no-implied-eval': 'error', + '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/no-throw-literal': 'error', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + '@typescript-eslint/no-unsafe-argument': 'error', + '@typescript-eslint/no-unsafe-assignment': 'error', + '@typescript-eslint/no-unsafe-call': 'error', + '@typescript-eslint/no-unsafe-member-access': 'error', + '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/restrict-plus-operands': 'error', + '@typescript-eslint/restrict-template-expressions': 'error', + '@typescript-eslint/unbound-method': 'error', + + // customizations '@typescript-eslint/naming-convention': [ 'error', { @@ -47,86 +81,129 @@ const typeAwareRules = (enabled: boolean) => { format: ['PascalCase'], }, ], + }; -}; -export const typescript: FlatESLintConfig[] = [ - { - files: [GLOB_TS, GLOB_TSX], - languageOptions: { - parser: parserTypeScript, - parserOptions: { - sourceType: 'module', + const customRules: FlatConfig['rules'] = { + '@typescript-eslint/prefer-literal-enum-member': [ + 'error', + { allowBitwiseExpressions: true }, + ], + '@typescript-eslint/prefer-as-const': 'warn', + '@typescript-eslint/consistent-type-assertions': [ + 'error', + { + assertionStyle: 'as', + objectLiteralTypeAssertions: 'allow-as-parameter', }, - }, - plugins: { - '@typescript-eslint': pluginTypeScript, - antfu: pluginAntfu, - }, - rules: { - ...pluginTypeScript.configs['eslint-recommended'].overrides![0].rules, - ...pluginTypeScript.configs.strict.rules, - - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/ban-types': 'off', - '@typescript-eslint/consistent-type-assertions': [ - 'error', - { - assertionStyle: 'as', - objectLiteralTypeAssertions: 'allow-as-parameter', - }, - ], - '@typescript-eslint/consistent-type-imports': [ - 'error', - { disallowTypeAnnotations: false, fixStyle: 'inline-type-imports' }, - ], - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-import-type-side-effects': 'error', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-redeclare': 'error', - - // handled by unused-imports/no-unused-imports - '@typescript-eslint/no-unused-vars': 'off', + ], + '@typescript-eslint/padding-line-between-statements': [ + 'error', + { + blankLine: 'always', + prev: '*', + next: ['interface', 'type', 'class', 'function', 'export'], + }, + ], + 'max-lines': ['error', { max: 400 }], + }; - '@typescript-eslint/prefer-as-const': 'warn', - '@typescript-eslint/prefer-literal-enum-member': [ - 'error', - { allowBitwiseExpressions: true }, - ], + const tsconfigPath = options?.tsconfigPath + ? toArray(options.tsconfigPath) + : undefined; - 'antfu/no-const-enum': 'error', + const [ + pluginTs, + parserTs, + ] = await Promise.all([ + interopDefault(import('@typescript-eslint/eslint-plugin')), + interopDefault(import('@typescript-eslint/parser')), + ] as const); - // customizations - '@typescript-eslint/padding-line-between-statements': [ - 'error', - { - blankLine: 'always', - prev: '*', - next: ['interface', 'type', 'class', 'function', 'export'], + return [ + { + plugins: { + 'antfu': pluginAntfu, + '@typescript-eslint': pluginTs as any, + }, + }, + { + files, + languageOptions: { + parser: parserTs, + parserOptions: { + extraFileExtensions: componentExts.map(ext => `.${ext}`), + sourceType: 'module', + ...tsconfigPath + ? { + project: tsconfigPath, + tsconfigRootDir: process.cwd(), + } + : {}, + ...parserOptions as any, }, - ], - 'max-lines': ['error', { max: 400 }], + }, + rules: { + ...pluginTs.configs['eslint-recommended'].overrides![0].rules, + ...pluginTs.configs.strict.rules, + 'no-dupe-class-members': 'off', + 'no-loss-of-precision': 'off', + 'no-redeclare': 'off', + 'no-use-before-define': 'off', + 'no-useless-constructor': 'off', + '@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow-with-description' }], + '@typescript-eslint/ban-types': ['error', { types: { Function: false } }], + '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], + '@typescript-eslint/consistent-type-imports': ['error', { disallowTypeAnnotations: false, prefer: 'type-imports', fixStyle: 'inline-type-imports' }], + '@typescript-eslint/no-dupe-class-members': 'error', + '@typescript-eslint/no-dynamic-delete': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-extraneous-class': 'off', + '@typescript-eslint/no-import-type-side-effects': 'error', + '@typescript-eslint/no-invalid-void-type': 'off', + '@typescript-eslint/no-loss-of-precision': 'error', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-redeclare': 'error', + '@typescript-eslint/no-require-imports': 'error', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-use-before-define': ['error', { classes: false, functions: false, variables: true }], + '@typescript-eslint/no-useless-constructor': 'off', + '@typescript-eslint/prefer-ts-expect-error': 'error', + '@typescript-eslint/triple-slash-reference': 'off', + '@typescript-eslint/unified-signatures': 'off', + ...customRules, + ...overrides, + }, + }, + { + files: filesTypeAware, + rules: { + ...tsconfigPath ? typeAwareRules : {}, + ...overrides, + }, }, - }, - { - files: ['**/*.d.ts'], - rules: { - 'eslint-comments/no-unlimited-disable': 'off', - 'import/no-duplicates': 'off', - 'unused-imports/no-unused-vars': 'off', + { + files: ['**/*.d.ts'], + rules: { + 'eslint-comments/no-unlimited-disable': 'off', + 'import/no-duplicates': 'off', + 'no-restricted-syntax': 'off', + 'unused-imports/no-unused-vars': 'off', + }, }, - }, - { - files: ['**/*.{test,spec}.ts?(x)'], - rules: { - 'no-unused-expressions': 'off', + { + files: ['**/*.{test,spec,cy}.ts?(x)'], + rules: { + 'no-unused-expressions': 'off', + 'max-lines': 'off', + }, }, - }, - { - files: ['**/*.js', '**/*.cjs'], - rules: { - '@typescript-eslint/no-require-imports': 'off', - '@typescript-eslint/no-var-requires': 'off', + { + files: ['**/*.js', '**/*.cjs'], + rules: { + 'ts/no-require-imports': 'off', + 'ts/no-var-requires': 'off', + }, }, - }, -]; + ]; +} diff --git a/src/configs/unicorn.ts b/src/configs/unicorn.ts index ce57e44..a736bbd 100644 --- a/src/configs/unicorn.ts +++ b/src/configs/unicorn.ts @@ -1,7 +1,7 @@ import { pluginUnicorn } from '../plugins'; -import type { FlatESLintConfig } from 'eslint-define-config'; +import type { FlatConfig } from '../types'; -export async function unicorn(): Promise { +export function unicorn(): FlatConfig[] { return [ { plugins: { @@ -17,7 +17,7 @@ export async function unicorn(): Promise { 'unicorn/filename-case': [ 'error', { - cases: {kebabCase: true, pascalCase: true}, + cases: { kebabCase: true, pascalCase: true }, ignore: [/^[A-Z]+\..*$/], }, ], @@ -71,4 +71,4 @@ export async function unicorn(): Promise { }, }, ]; -} \ No newline at end of file +} diff --git a/src/configs/vue.ts b/src/configs/vue.ts index 78a0dd2..2da10c3 100644 --- a/src/configs/vue.ts +++ b/src/configs/vue.ts @@ -1,203 +1,256 @@ -import process from 'node:process'; -import { getPackageInfoSync } from 'local-pkg'; +import { mergeProcessors } from 'eslint-merge-processors'; import { GLOB_VUE } from '../globs'; -import { parserVue, pluginTypeScript, pluginVue } from '../plugins'; -import { typescript } from './typescript'; -import type { FlatESLintConfig, Rules } from 'eslint-define-config'; +import { interopDefault } from '../utils'; +import type { + FlatConfig, + OptionsFiles, + OptionsHasTypeScript, + OptionsOverrides, + OptionsStylistic, + OptionsVue, +} from '../types'; -export function getVueVersion() { - const pkg = getPackageInfoSync('vue', { paths: [process.cwd()] }); - if ( - pkg && - typeof pkg.version === 'string' && - !Number.isNaN(+pkg.version[0]) - ) { - return +pkg.version[0]; - } - return 3; -} -const isVue3 = getVueVersion() === 3; +export async function vue( + options: OptionsVue & OptionsHasTypeScript & OptionsOverrides & OptionsStylistic & OptionsFiles = {}, +): Promise { + const { + files = [GLOB_VUE], + overrides = {}, + stylistic = true, + vueVersion = 3, + } = options; -export const reactivityTransform: FlatESLintConfig[] = [ - { - languageOptions: { - globals: { - $: 'readonly', - $$: 'readonly', - $computed: 'readonly', - $customRef: 'readonly', - $ref: 'readonly', - $shallowRef: 'readonly', - $toRef: 'readonly', - }, - }, - plugins: { - vue: pluginVue, - }, - rules: { - 'vue/no-setup-props-reactivity-loss': 'off', - }, - }, -]; + const sfcBlocks = options.sfcBlocks === true + ? {} + : options.sfcBlocks ?? {}; -const vueCustomRules: Rules = { - 'vue/block-order': ['error', { order: ['script', 'template', 'style'] }], - 'vue/custom-event-name-casing': ['error', 'camelCase'], - 'vue/eqeqeq': ['error', 'smart'], - 'vue/html-self-closing': [ - 'error', - { - html: { - component: 'always', - normal: 'always', - void: 'always', + const { + indent = 2, + } = typeof stylistic === 'boolean' ? {} : stylistic; + + const [ + pluginVue, + parserVue, + processorVueBlocks, + ] = await Promise.all([ + // @ts-expect-error missing types + interopDefault(import('eslint-plugin-vue')), + interopDefault(import('vue-eslint-parser')), + interopDefault(import('eslint-processor-vue-blocks')), + ] as const); + + const customRules: typeof pluginVue['rules'] = { + 'vue/html-self-closing': [ + 'error', + { + html: { + component: 'always', + normal: 'always', + void: 'always', + }, + math: 'always', + svg: 'always', }, - math: 'always', - svg: 'always', - }, - ], - 'vue/max-attributes-per-line': [ - 'error', - { - singleline: { - max: 1, + ], + 'vue/no-constant-condition': 'warn', + // migration + 'vue/component-api-style': ['error', ['script-setup']], + 'vue/no-deprecated-dollar-listeners-api': 'error', + 'vue/no-deprecated-events-api': 'error', + 'vue/no-deprecated-filter': 'error', + 'vue/prefer-import-from-vue': 'error', + 'vue/require-explicit-emits': 'error', + // custom + 'vue/valid-v-slot': [ + 'error', + { + allowModifiers: true, }, - multiline: { - max: 1, + ], + 'vue/no-empty-component-block': 'error', + 'vue/multiline-html-element-content-newline': [ + 'error', + { + ignoreWhenEmpty: true, + ignores: ['pre', 'textarea'], + allowEmptyLines: false, }, - }, - ], + ], + 'vue/v-bind-style': ['error', 'shorthand'], + 'vue/v-on-style': ['error', 'shorthand'], + 'vue/v-slot-style': [ + 'error', + { + atComponent: 'shorthand', + default: 'shorthand', + named: 'shorthand', + }, + ], + 'vue/no-static-inline-styles': [ + 'error', + { + allowBinding: false, + }, + ], + 'vue/v-on-handler-style': [ + 'error', + 'inline', + { + ignoreIncludesComment: false, + }, + ], + 'vue/define-props-declaration': ['warn', 'error'], - 'vue/multi-word-component-names': 'off', - 'vue/no-constant-condition': 'warn', - 'vue/no-empty-pattern': 'error', - 'vue/no-loss-of-precision': 'error', - 'vue/no-unused-refs': 'error', - 'vue/no-useless-v-bind': 'error', + }; - 'vue/no-v-html': 'off', - 'vue/object-shorthand': [ - 'error', - 'always', + return [ { - avoidQuotes: true, - ignoreConstructors: false, + plugins: { + vue: pluginVue, + }, }, - ], - 'vue/padding-line-between-blocks': ['error', 'always'], - 'vue/prefer-template': 'error', - 'vue/require-default-prop': 'off', - 'vue/require-prop-types': 'off', + { + files, + languageOptions: { + parser: parserVue, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + extraFileExtensions: ['.vue'], + parser: options.typescript + ? await interopDefault(import('@typescript-eslint/parser')) as any + : null, + sourceType: 'module', + }, + }, + processor: sfcBlocks === false + ? pluginVue.processors['.vue'] + : mergeProcessors([ + pluginVue.processors['.vue'], + processorVueBlocks({ + ...sfcBlocks, + blocks: { + styles: true, + ...sfcBlocks.blocks, + }, + }), + ]), + rules: { + ...pluginVue.configs.base.rules, - // migration - 'vue/component-api-style': ['error', ['script-setup']], - 'vue/no-deprecated-dollar-listeners-api': 'error', - 'vue/no-deprecated-events-api': 'error', - 'vue/no-deprecated-filter': 'error', - 'vue/prefer-import-from-vue': 'error', - 'vue/require-explicit-emits': 'error', + ...vueVersion === 2 + ? { + ...pluginVue.configs.essential.rules, + ...pluginVue.configs['strongly-recommended'].rules, + ...pluginVue.configs.recommended.rules, + } + : { + ...pluginVue.configs['vue3-essential'].rules, + ...pluginVue.configs['vue3-strongly-recommended'].rules, + ...pluginVue.configs['vue3-recommended'].rules, + }, - // custom - 'vue/valid-v-slot': [ - 'error', - { - allowModifiers: true, - }, - ], - 'vue/no-empty-component-block': 'error', - 'vue/multiline-html-element-content-newline': [ - 'error', - { - ignoreWhenEmpty: true, - ignores: ['pre', 'textarea'], - allowEmptyLines: false, - }, - ], - 'vue/v-bind-style': ['error', 'shorthand'], - 'vue/v-on-style': ['error', 'shorthand'], - 'vue/v-slot-style': [ - 'error', - { - atComponent: 'shorthand', - default: 'shorthand', - named: 'shorthand', - }, - ], - 'vue/component-name-in-template-casing': [ - 'error', - 'PascalCase', - { - registeredComponentsOnly: false, - ignores: ['i18n', 'i18n-t', 'i18n-d', 'i18n-n'], - }, - ], - 'vue/no-static-inline-styles': [ - 'error', - { - allowBinding: false, - }, - ], - 'vue/v-on-handler-style': [ - 'error', - 'inline', - { - ignoreIncludesComment: false, - }, - ], - 'vue/define-props-declaration': ['warn', 'type-based'], - 'vue/define-macros-order': [ - 'error', - { - order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots'], - }, - ], -}; + 'node/prefer-global/process': 'off', -const vue3Rules: Rules = { - ...pluginVue.configs.base.rules, - ...pluginVue.configs['vue3-essential'].rules, - ...pluginVue.configs['vue3-strongly-recommended'].rules, - ...pluginVue.configs['vue3-recommended'].rules, -}; + 'vue/block-order': ['error', { + order: ['script', 'template', 'style'], + }], + 'vue/component-name-in-template-casing': [ + 'error', + 'PascalCase', + { + registeredComponentsOnly: false, + ignores: ['i18n', 'i18n-t', 'i18n-d', 'i18n-n'], + }, + ], + 'vue/component-options-name-casing': ['error', 'PascalCase'], + 'vue/custom-event-name-casing': ['error', 'camelCase'], + 'vue/define-macros-order': ['error', { + order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots'], + }], + 'vue/dot-location': ['error', 'property'], + 'vue/dot-notation': ['error', { allowKeywords: true }], + 'vue/eqeqeq': ['error', 'smart'], + 'vue/html-indent': ['error', indent], + 'vue/html-quotes': ['error', 'double'], + 'vue/max-attributes-per-line': [ + 'error', + { + singleline: { + max: 1, + }, + multiline: { + max: 1, + }, + }, + ], + 'vue/multi-word-component-names': 'off', + 'vue/no-dupe-keys': 'off', + 'vue/no-empty-pattern': 'error', + 'vue/no-irregular-whitespace': 'error', + 'vue/no-loss-of-precision': 'error', + 'vue/no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'LabeledStatement', + 'WithStatement', + ], + 'vue/no-restricted-v-bind': ['error', '/^v-/'], + 'vue/no-setup-props-reactivity-loss': 'off', + 'vue/no-sparse-arrays': 'error', + 'vue/no-unused-refs': 'error', + 'vue/no-useless-v-bind': 'error', + 'vue/no-v-html': 'off', + 'vue/object-shorthand': [ + 'error', + 'always', + { + avoidQuotes: true, + ignoreConstructors: false, + }, + ], + 'vue/prefer-separate-static-class': 'error', + 'vue/prefer-template': 'error', + 'vue/prop-name-casing': ['error', 'camelCase'], + 'vue/require-default-prop': 'off', + 'vue/require-prop-types': 'off', + 'vue/space-infix-ops': 'error', + 'vue/space-unary-ops': ['error', { nonwords: false, words: true }], -const vue2Rules: Rules = { - ...pluginVue.configs.base.rules, - ...pluginVue.configs.essential.rules, - ...pluginVue.configs['strongly-recommended'].rules, - ...pluginVue.configs.recommended.rules, -}; + ...stylistic + ? { + 'vue/array-bracket-spacing': ['error', 'never'], + 'vue/arrow-spacing': ['error', { after: true, before: true }], + 'vue/block-spacing': ['error', 'always'], + 'vue/block-tag-newline': ['error', { + multiline: 'always', + singleline: 'always', + }], + 'vue/brace-style': ['error', 'stroustrup', { allowSingleLine: true }], + 'vue/comma-dangle': ['error', 'always-multiline'], + 'vue/comma-spacing': ['error', { after: true, before: false }], + 'vue/comma-style': ['error', 'last'], + 'vue/html-comment-content-spacing': ['error', 'always', { + exceptions: ['-'], + }], + 'vue/key-spacing': ['error', { afterColon: true, beforeColon: false }], + 'vue/keyword-spacing': ['error', { after: true, before: true }], + 'vue/object-curly-newline': 'off', + 'vue/object-curly-spacing': ['error', 'always'], + 'vue/object-property-newline': ['error', { allowMultiplePropertiesPerLine: true }], + 'vue/operator-linebreak': ['error', 'before'], + 'vue/padding-line-between-blocks': ['error', 'always'], + 'vue/quote-props': ['error', 'consistent-as-needed'], + 'vue/space-in-parens': ['error', 'never'], + 'vue/template-curly-spacing': 'error', + } + : {}, -export const vue: FlatESLintConfig[] = [ - { - files: [GLOB_VUE], - languageOptions: { - parser: parserVue, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - extraFileExtensions: ['.vue'], - parser: '@typescript-eslint/parser', - sourceType: 'module', + ...customRules, + + ...overrides, }, }, - plugins: { - '@typescript-eslint': pluginTypeScript, - vue: pluginVue, - }, - processor: pluginVue.processors['.vue'], - rules: { - ...typescript[0].rules, - }, - }, - { - plugins: { - vue: pluginVue, - }, - rules: { - ...(isVue3 ? vue3Rules : vue2Rules), - ...vueCustomRules, - }, - }, - ...reactivityTransform, -]; + ]; +} diff --git a/src/configs/yaml.ts b/src/configs/yaml.ts index 33abe84..98b2c3a 100644 --- a/src/configs/yaml.ts +++ b/src/configs/yaml.ts @@ -1,23 +1,70 @@ import { GLOB_YAML } from '../globs'; -import { parserYaml, pluginYaml } from '../plugins'; -import type { FlatESLintConfig, Rules } from 'eslint-define-config'; +import { interopDefault } from '../utils'; +import type { FlatConfig, OptionsFiles, OptionsOverrides, OptionsStylistic } from '../types'; +export async function yaml( + options: OptionsOverrides & OptionsStylistic & OptionsFiles = {}, +): Promise { + const { + files = [GLOB_YAML], + overrides = {}, + stylistic = true, + } = options; -export async function yaml(): Promise { - return [ + const { + indent = 2, + quotes = 'single', + } = typeof stylistic === 'boolean' ? {} : stylistic; + + const [ + pluginYaml, + parserYaml, + ] = await Promise.all([ + interopDefault(import('eslint-plugin-yml')), + interopDefault(import('yaml-eslint-parser')), + ] as const); + + return [ + { + plugins: { + yaml: pluginYaml, + }, + }, { - files: [GLOB_YAML], + files, languageOptions: { parser: parserYaml, }, - plugins: { - yml: pluginYaml, - }, rules: { - ...(pluginYaml.configs.standard.rules as Rules), - ...(pluginYaml.configs.prettier.rules as Rules), - 'yml/no-empty-mapping-value': 'off', + 'style/spaced-comment': 'off', + + 'yaml/block-mapping': 'error', + 'yaml/block-sequence': 'error', + 'yaml/no-empty-key': 'error', + 'yaml/no-empty-sequence-entry': 'error', + 'yaml/no-irregular-whitespace': 'error', + 'yaml/plain-scalar': 'error', + + 'yaml/vue-custom-block/no-parsing-error': 'error', + + ...stylistic + ? { + 'yaml/block-mapping-question-indicator-newline': 'error', + 'yaml/block-sequence-hyphen-indicator-newline': 'error', + 'yaml/flow-mapping-curly-newline': 'error', + 'yaml/flow-mapping-curly-spacing': 'error', + 'yaml/flow-sequence-bracket-newline': 'error', + 'yaml/flow-sequence-bracket-spacing': 'error', + 'yaml/indent': ['error', indent === 'tab' ? 2 : indent], + 'yaml/key-spacing': 'error', + 'yaml/no-tab-indent': 'error', + 'yaml/quotes': ['error', { avoidEscape: false, prefer: quotes }], + 'yaml/spaced-comment': 'error', + } + : {}, + + ...overrides, }, }, - ] + ]; } diff --git a/src/env.ts b/src/env.ts index a011f59..ef64627 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,13 +1,13 @@ import process from 'node:process'; import { isPackageExists } from 'local-pkg'; -export const isInEditor = - (process.env.VSCODE_PID || process.env.JETBRAINS_IDE) && !process.env.CI; +export const isInEditor + = (process.env.VSCODE_PID || process.env.JETBRAINS_IDE) && !process.env.CI; export const hasTypeScript = isPackageExists('typescript'); -export const hasVue = - isPackageExists('vue') || - isPackageExists('nuxt') || - isPackageExists('vitepress') || - isPackageExists('@slidev/cli'); +export const hasVue + = isPackageExists('vue') + || isPackageExists('nuxt') + || isPackageExists('vitepress') + || isPackageExists('@slidev/cli'); diff --git a/src/factory.ts b/src/factory.ts new file mode 100644 index 0000000..474f864 --- /dev/null +++ b/src/factory.ts @@ -0,0 +1,197 @@ +import process from 'node:process'; +import fs from 'node:fs'; +import { isPackageExists } from 'local-pkg'; +import { + comments, + formatters, + ignores, + imports, + javascript, + jsonc, + markdown, + node, + perfectionist, + sortPackageJson, + sortTsconfig, + stylistic, + typescript, + unicorn, + vue, + yaml, +} from './configs'; +import { combine, interopDefault } from './utils'; +import type { Awaitable, FlatConfig, OptionsConfig, UserConfigItem } from './types'; + +const flatConfigProps: (keyof FlatConfig)[] = [ + 'files', + 'ignores', + 'languageOptions', + 'linterOptions', + 'processor', + 'plugins', + 'rules', + 'settings', +]; + +const VuePackages = [ + 'vue', + 'nuxt', + 'vitepress', + '@slidev/cli', +]; + +/** + * Construct an array of ESLint flat config items. + */ +export async function rotki( + options: OptionsConfig & FlatConfig = {}, + ...userConfigs: Awaitable[] +): Promise { + const { + componentExts = [], + gitignore: enableGitignore = true, + isInEditor = !!((process.env.VSCODE_PID || process.env.JETBRAINS_IDE || process.env.VIM) && !process.env.CI), + typescript: enableTypeScript = isPackageExists('typescript'), + vue: enableVue = VuePackages.some(i => isPackageExists(i)), + } = options; + + const stylisticOptions = options.stylistic === false + ? false + : typeof options.stylistic === 'object' + ? options.stylistic + : {}; + + if (stylisticOptions && !('jsx' in stylisticOptions)) + stylisticOptions.jsx = options.jsx ?? true; + + const configs: Awaitable[] = []; + + if (enableGitignore) { + if (typeof enableGitignore !== 'boolean') + configs.push(interopDefault(import('eslint-config-flat-gitignore')).then(r => [r(enableGitignore)])); + + else if (fs.existsSync('.gitignore')) + configs.push(interopDefault(import('eslint-config-flat-gitignore')).then(r => [r()])); + } + + // Base configs + configs.push( + ignores(), + javascript({ + isInEditor, + overrides: getOverrides(options, 'javascript'), + }), + comments(), + node(), + imports({ + stylistic: stylisticOptions, + }), + unicorn(), + + // Optional plugins (installed but not enabled by default) + perfectionist(), + ); + + if (enableVue) + componentExts.push('vue'); + + if (enableTypeScript) { + configs.push(typescript({ + ...resolveSubOptions(options, 'typescript'), + componentExts, + overrides: getOverrides(options, 'typescript'), + })); + } + + if (stylisticOptions) { + configs.push(stylistic({ + ...stylisticOptions, + overrides: getOverrides(options, 'stylistic'), + })); + } + + if (enableVue) { + configs.push(vue({ + ...resolveSubOptions(options, 'vue'), + overrides: getOverrides(options, 'vue'), + stylistic: stylisticOptions, + typescript: !!enableTypeScript, + })); + } + + if (options.jsonc ?? true) { + configs.push( + jsonc({ + overrides: getOverrides(options, 'jsonc'), + stylistic: stylisticOptions, + }), + sortPackageJson(), + sortTsconfig(), + ); + } + + if (options.yaml ?? true) { + configs.push(yaml({ + overrides: getOverrides(options, 'yaml'), + stylistic: stylisticOptions, + })); + } + + if (options.markdown ?? true) { + configs.push( + markdown( + { + componentExts, + overrides: getOverrides(options, 'markdown'), + }, + ), + ); + } + + if (options.formatters) { + configs.push(formatters( + options.formatters, + typeof stylisticOptions === 'boolean' ? {} : stylisticOptions, + )); + } + + // User can optionally pass a flat config item to the first argument + // We pick the known keys as ESLint would do schema validation + const fusedConfig = flatConfigProps.reduce((acc, key) => { + if (key in options) + acc[key] = options[key] as any; + return acc; + }, {} as FlatConfig); + if (Object.keys(fusedConfig).length > 0) + configs.push([fusedConfig]); + + return await combine( + ...configs, + ...userConfigs, + ); +} + +export type ResolvedOptions = T extends boolean + ? never + : NonNullable; + +export function resolveSubOptions( + options: OptionsConfig, + key: K, +): ResolvedOptions { + return typeof options[key] === 'boolean' + ? {} as any + : options[key] || {}; +} + +export function getOverrides( + options: OptionsConfig, + key: K, +) { + const sub = resolveSubOptions(options, key); + return { + ...'overrides' in sub + ? sub.overrides + : {}, + }; +} diff --git a/src/globs.ts b/src/globs.ts index 4683289..eba8025 100644 --- a/src/globs.ts +++ b/src/globs.ts @@ -14,6 +14,8 @@ export const GLOB_STYLE = '**/*.{c,le,sc}ss'; export const GLOB_CSS = '**/*.css'; +export const GLOB_POSTCSS = '**/*.{p,post}css'; + export const GLOB_LESS = '**/*.less'; export const GLOB_SCSS = '**/*.scss'; @@ -26,12 +28,25 @@ export const GLOB_JSONC = '**/*.jsonc'; export const GLOB_MARKDOWN = '**/*.md'; +export const GLOB_MARKDOWN_IN_MARKDOWN = '**/*.md/*.md'; + export const GLOB_VUE = '**/*.vue'; export const GLOB_YAML = '**/*.y?(a)ml'; export const GLOB_HTML = '**/*.htm?(l)'; +export const GLOB_MARKDOWN_CODE = `${GLOB_MARKDOWN}/${GLOB_SRC}`; + +export const GLOB_TESTS = [ + `**/__tests__/**/*.${GLOB_SRC_EXT}`, + `**/*.spec.${GLOB_SRC_EXT}`, + `**/*.test.${GLOB_SRC_EXT}`, + `**/*.cy.${GLOB_SRC_EXT}`, + `**/*.bench.${GLOB_SRC_EXT}`, + `**/*.benchmark.${GLOB_SRC_EXT}`, +]; + export const GLOB_ALL_SRC = [ GLOB_SRC, GLOB_STYLE, diff --git a/src/index.ts b/src/index.ts index 3bf49f8..dede2bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ +import { rotki } from './factory'; + export * from './configs'; export * from './env'; @@ -6,4 +8,4 @@ export * from './globs'; export * from './plugins'; -export * from './presets'; +export { rotki }; diff --git a/src/plugins.ts b/src/plugins.ts index f615242..ec9dc02 100644 --- a/src/plugins.ts +++ b/src/plugins.ts @@ -9,32 +9,15 @@ export * as pluginImport from 'eslint-plugin-i'; export * as pluginJsonc from 'eslint-plugin-jsonc'; -export { default as pluginMarkdown } from 'eslint-plugin-markdown'; +export { default as pluginNode } from 'eslint-plugin-n'; export { default as pluginUnicorn } from 'eslint-plugin-unicorn'; export { default as pluginUnusedImports } from 'eslint-plugin-unused-imports'; -export { default as pluginVue } from 'eslint-plugin-vue'; - export * as pluginYaml from 'eslint-plugin-yml'; -export { default as pluginPrettier } from 'eslint-plugin-prettier'; - -export { default as pluginSortKeys } from 'eslint-plugin-sort-keys'; - -export { default as pluginTypeScript } from '@typescript-eslint/eslint-plugin'; +export { default as pluginPerfectionist } from 'eslint-plugin-perfectionist'; // Parsers - export * as parserTypeScript from '@typescript-eslint/parser'; - -export { default as parserVue } from 'vue-eslint-parser'; - -export { default as parserYaml } from 'yaml-eslint-parser'; - -export { default as parserJsonc } from 'jsonc-eslint-parser'; - -// Configs - -export { default as configPrettier } from 'eslint-config-prettier'; diff --git a/src/presets.ts b/src/presets.ts deleted file mode 100644 index 3a8d7db..0000000 --- a/src/presets.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - comments, - ignores, - imports, - javascript, - jsonc, - markdown, - prettier, - sortKeys, - sortPackageJson, - sortTsconfig, - typescript, - unicorn, - vue, - yaml, -} from './configs'; -import type { FlatESLintConfig } from 'eslint-define-config'; - -export const presetJavaScript = [ - ...ignores, - ...javascript, - ...comments, - ...imports, - ...unicorn, -]; - -export const presetLangsExtensions = [ - ...markdown, - ...yaml, - ...jsonc, - ...sortPackageJson, - ...sortTsconfig, -]; - -export const basic = [ - ...presetJavaScript, - ...typescript, - ...presetLangsExtensions, -]; - -export { basic as presetBasic }; - -export const all = [...basic, ...sortKeys, ...vue, ...prettier]; - -export function rotki( - config: FlatESLintConfig | FlatESLintConfig[] = [], - { - vue: enableVue = true, - prettier: enablePrettier = true, - markdown: enableMarkdown = true, - sortKeys: enableSortKeys = true, - }: Partial<{ - vue: boolean; - prettier: boolean; - markdown: boolean; - sortKeys: boolean; - }> = {}, -): FlatESLintConfig[] { - const configs = []; - configs.push(...basic); - if (enableSortKeys) { - configs.push(...sortKeys); - } - if (enableVue) { - configs.push(...vue); - } - if (enableMarkdown) { - configs.push(...markdown); - } - if (enablePrettier) { - configs.push(...prettier); - } - if (Object.keys(config).length > 0) { - configs.push(...(Array.isArray(config) ? config : [config])); - } - return configs; -} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..b693a54 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,235 @@ +import type { VendoredPrettierOptions } from './vendor/prettier'; +import type { FlatESLintConfig, ParserOptions } from 'eslint-define-config'; +import type { FlatGitignoreOptions } from 'eslint-config-flat-gitignore'; +import type { StylisticCustomizeOptions } from '@stylistic/eslint-plugin'; +import type { Options as VueBlocksOptions } from 'eslint-processor-vue-blocks'; +import type { Linter } from 'eslint'; + +export type UserConfigItem = FlatConfig | Linter.FlatConfig; + +export type Awaitable = T | Promise; + +export type FlatConfig = Omit & { + plugins?: Record +}; + +export type OptionsTypescript = + (OptionsTypeScriptWithTypes & OptionsOverrides) + | (OptionsTypeScriptParserOptions & OptionsOverrides); + +export interface OptionsFormatters { + /** + * Enable formatting support for CSS, Less, Sass, and SCSS. + * + * Currently only support Prettier. + */ + css?: 'prettier' | boolean + + /** + * Enable formatting support for HTML. + * + * Currently only support Prettier. + */ + html?: 'prettier' | boolean + + /** + * Enable formatting support for Markdown. + * + * Support both Prettier and dprint. + * + * When set to `true`, it will use Prettier. + */ + markdown?: 'prettier' | 'dprint' | boolean + + /** + * Custom options for Prettier. + * + * By default it's controlled by our own config. + */ + prettierOptions?: VendoredPrettierOptions + + /** + * Custom options for dprint. + * + * By default it's controlled by our own config. + */ + dprintOptions?: boolean +} + +export interface OptionsFiles { + /** + * Override the `files` option to provide custom globs. + */ + files?: string[] +} + +export interface OptionsVue extends OptionsOverrides { + /** + * Create virtual files for Vue SFC blocks to enable linting. + * + * @see https://github.com/antfu/eslint-processor-vue-blocks + * @default true + */ + sfcBlocks?: boolean | VueBlocksOptions + + /** + * Vue version. Apply different rules set from `eslint-plugin-vue`. + * + * @default 3 + */ + vueVersion?: 2 | 3 +} + +export interface OptionsOverrides { + overrides?: FlatConfig['rules'] +} + +export interface OptionsComponentExts { + /** + * Additional extensions for components. + * + * @example ['vue'] + * @default [] + */ + componentExts?: string[] +} + +export interface OptionsTypeScriptParserOptions { + /** + * Additional parser options for TypeScript. + */ + parserOptions?: Partial + + /** + * Glob patterns for files that should be type aware. + * @default ['**\/*.{ts,tsx}'] + */ + filesTypeAware?: string[] +} + +export interface OptionsTypeScriptWithTypes { + /** + * When this options is provided, type aware rules will be enabled. + * @see https://typescript-eslint.io/linting/typed-linting/ + */ + tsconfigPath?: string | string[] +} + +export interface OptionsHasTypeScript { + typescript?: boolean +} + +export interface OptionsStylistic { + stylistic?: boolean | StylisticConfig +} + +export interface StylisticConfig extends Pick { +} + +export interface OptionsIsInEditor { + isInEditor?: boolean +} + +export interface OptionsConfig extends OptionsComponentExts { + /** + * Enable gitignore support. + * + * Passing an object to configure the options. + * + * @see https://github.com/antfu/eslint-config-flat-gitignore + * @default true + */ + gitignore?: boolean | FlatGitignoreOptions + + /** + * Core rules. Can't be disabled. + */ + javascript?: OptionsOverrides + + /** + * Enable TypeScript support. + * + * Passing an object to enable TypeScript Language Server support. + * + * @default auto-detect based on the dependencies + */ + typescript?: boolean | OptionsTypescript + + /** + * Enable JSX related rules. + * + * Currently only stylistic rules are included. + * + * @default true + */ + jsx?: boolean + + /** + * Enable test support. + * + * @default true + */ + test?: boolean | OptionsOverrides + + /** + * Enable Vue support. + * + * @default auto-detect based on the dependencies + */ + vue?: boolean | OptionsVue + + /** + * Enable JSONC support. + * + * @default true + */ + jsonc?: boolean | OptionsOverrides + + /** + * Enable YAML support. + * + * @default true + */ + yaml?: boolean | OptionsOverrides + + /** + * Enable TOML support. + * + * @default true + */ + toml?: boolean | OptionsOverrides + + /** + * Enable linting for **code snippets** in Markdown. + * + * For formatting Markdown content, enable also `formatters.markdown`. + * + * @default true + */ + markdown?: boolean | OptionsOverrides + + /** + * Enable stylistic rules. + * + * @default true + */ + stylistic?: boolean | StylisticConfig + + /** + * Use external formatters to format files. + * + * Requires installing: + * - `eslint-plugin-format` + * + * When set to `true`, it will enable all formatters. + * + * @default false + */ + formatters?: boolean | OptionsFormatters + + /** + * Control to disable some rules in editors. + * @default auto-detect based on the process.env + */ + isInEditor?: boolean +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..a9a0c7d --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,61 @@ +import process from 'node:process'; +import { isPackageExists } from 'local-pkg'; +import type { Awaitable, UserConfigItem } from './types'; + +export const parserPlain = { + meta: { + name: 'parser-plain', + }, + parseForESLint: (code: string) => ({ + ast: { + body: [], + comments: [], + loc: { end: code.length, start: 0 }, + range: [0, code.length], + tokens: [], + type: 'Program', + }, + scopeManager: null, + services: { isPlain: true }, + visitorKeys: { + Program: [], + }, + }), +}; + +/** + * Combine array and non-array configs into a single array. + */ +export async function combine(...configs: Awaitable[]): Promise { + const resolved = await Promise.all(configs); + return resolved.flat(); +} + +export function toArray(value: T | T[]): T[] { + return Array.isArray(value) ? value : [value]; +} + +export async function interopDefault(m: Awaitable): Promise { + const resolved = await m; + return (resolved as any).default || resolved; +} + +export async function ensurePackages(packages: string[]) { + if (process.env.CI || process.stdout.isTTY === false) + return; + + const nonExistingPackages = packages.filter(i => !isPackageExists(i)); + if (nonExistingPackages.length === 0) + return; + + const { default: prompts } = await import('prompts'); + const { result } = await prompts([ + { + message: `${nonExistingPackages.length === 1 ? 'Package is' : 'Packages are'} required for this config: ${nonExistingPackages.join(', ')}. Do you want to install them?`, + name: 'result', + type: 'confirm', + }, + ]); + if (result) + await import('@antfu/install-pkg').then(i => i.installPackage(nonExistingPackages, { dev: true })); +} diff --git a/src/vendor/prettier.ts b/src/vendor/prettier.ts new file mode 100644 index 0000000..2560dd6 --- /dev/null +++ b/src/vendor/prettier.ts @@ -0,0 +1,136 @@ +/** + * Vendor types from Prettier so we don't rely on the dependency. + */ + +export type VendoredPrettierOptions = Partial; + +export interface VendoredPrettierOptionsRequired { + /** + * Specify the line length that the printer will wrap on. + * @default 120 + */ + printWidth: number + /** + * Specify the number of spaces per indentation-level. + */ + tabWidth: number + /** + * Indent lines with tabs instead of spaces + */ + useTabs?: boolean + /** + * Print semicolons at the ends of statements. + */ + semi: boolean + /** + * Use single quotes instead of double quotes. + */ + singleQuote: boolean + /** + * Use single quotes in JSX. + */ + jsxSingleQuote: boolean + /** + * Print trailing commas wherever possible. + */ + trailingComma: 'none' | 'es5' | 'all' + /** + * Print spaces between brackets in object literals. + */ + bracketSpacing: boolean + /** + * Put the `>` of a multi-line HTML (HTML, JSX, Vue, Angular) element at the end of the last line instead of being + * alone on the next line (does not apply to self closing elements). + */ + bracketSameLine: boolean + /** + * Put the `>` of a multi-line JSX element at the end of the last line instead of being alone on the next line. + * @deprecated use bracketSameLine instead + */ + jsxBracketSameLine: boolean + /** + * Format only a segment of a file. + */ + rangeStart: number + /** + * Format only a segment of a file. + * @default Number.POSITIVE_INFINITY + */ + rangeEnd: number + /** + * By default, Prettier will wrap markdown text as-is since some services use a linebreak-sensitive renderer. + * In some cases you may want to rely on editor/viewer soft wrapping instead, so this option allows you to opt out. + * @default "preserve" + */ + proseWrap: 'always' | 'never' | 'preserve' + /** + * Include parentheses around a sole arrow function parameter. + * @default "always" + */ + arrowParens: 'avoid' | 'always' + /** + * Provide ability to support new languages to prettier. + */ + plugins: Array + /** + * How to handle whitespaces in HTML. + * @default "css" + */ + htmlWhitespaceSensitivity: 'css' | 'strict' | 'ignore' + /** + * Which end of line characters to apply. + * @default "lf" + */ + endOfLine: 'auto' | 'lf' | 'crlf' | 'cr' + /** + * Change when properties in objects are quoted. + * @default "as-needed" + */ + quoteProps: 'as-needed' | 'consistent' | 'preserve' + /** + * Whether or not to indent the code inside