From 2944537892b07557c3f5db799c887ac1a4ee132d Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Sat, 8 Feb 2025 16:30:36 +0800 Subject: [PATCH 1/3] fix: proper support for `extends` field, fixes #136 --- .../.editorconfig | 6 + .../disable-type-checked-for-yml/.gitignore | 30 ++++ .../.prettierrc.json | 7 + .../.vscode/extensions.json | 8 + .../disable-type-checked-for-yml/README.md | 61 +++++++ .../cypress.config.ts | 8 + .../cypress/e2e/example.cy.ts | 8 + .../cypress/fixtures/example.json | 5 + .../cypress/support/commands.ts | 39 ++++ .../cypress/support/e2e.ts | 20 +++ .../cypress/tsconfig.json | 8 + .../disable-type-checked-for-yml/env.d.ts | 1 + .../eslint.config.js | 42 +++++ .../disable-type-checked-for-yml/index.html | 13 ++ .../disable-type-checked-for-yml/package.json | 49 +++++ .../public/favicon.ico | Bin 0 -> 4286 bytes .../disable-type-checked-for-yml/src/App.vue | 85 +++++++++ .../src/assets/base.css | 86 +++++++++ .../src/assets/logo.svg | 1 + .../src/assets/main.css | 35 ++++ .../src/components/HelloWorld.vue | 41 +++++ .../src/components/TheWelcome.vue | 88 +++++++++ .../src/components/WelcomeItem.vue | 87 +++++++++ .../components/__tests__/HelloWorld.spec.ts | 11 ++ .../src/components/icons/IconCommunity.vue | 7 + .../components/icons/IconDocumentation.vue | 7 + .../src/components/icons/IconEcosystem.vue | 7 + .../src/components/icons/IconSupport.vue | 7 + .../src/components/icons/IconTooling.vue | 19 ++ .../src/issue-136.yml | 1 + .../disable-type-checked-for-yml/src/main.ts | 14 ++ .../src/router/index.ts | 24 +++ .../src/stores/counter.ts | 12 ++ .../src/views/AboutView.vue | 15 ++ .../src/views/HomeView.vue | 9 + .../tsconfig.app.json | 14 ++ .../tsconfig.json | 17 ++ .../tsconfig.node.json | 19 ++ .../tsconfig.vitest.json | 11 ++ .../vite.config.ts | 20 +++ .../vitest.config.ts | 14 ++ pnpm-lock.yaml | 131 +++++++++++++- src/configs.ts | 62 +++++-- src/fpHelpers.ts | 57 ++++++ src/utilities.ts | 168 ++++++++++++------ 45 files changed, 1292 insertions(+), 82 deletions(-) create mode 100644 examples/disable-type-checked-for-yml/.editorconfig create mode 100644 examples/disable-type-checked-for-yml/.gitignore create mode 100644 examples/disable-type-checked-for-yml/.prettierrc.json create mode 100644 examples/disable-type-checked-for-yml/.vscode/extensions.json create mode 100644 examples/disable-type-checked-for-yml/README.md create mode 100644 examples/disable-type-checked-for-yml/cypress.config.ts create mode 100644 examples/disable-type-checked-for-yml/cypress/e2e/example.cy.ts create mode 100644 examples/disable-type-checked-for-yml/cypress/fixtures/example.json create mode 100644 examples/disable-type-checked-for-yml/cypress/support/commands.ts create mode 100644 examples/disable-type-checked-for-yml/cypress/support/e2e.ts create mode 100644 examples/disable-type-checked-for-yml/cypress/tsconfig.json create mode 100644 examples/disable-type-checked-for-yml/env.d.ts create mode 100644 examples/disable-type-checked-for-yml/eslint.config.js create mode 100644 examples/disable-type-checked-for-yml/index.html create mode 100644 examples/disable-type-checked-for-yml/package.json create mode 100644 examples/disable-type-checked-for-yml/public/favicon.ico create mode 100644 examples/disable-type-checked-for-yml/src/App.vue create mode 100644 examples/disable-type-checked-for-yml/src/assets/base.css create mode 100644 examples/disable-type-checked-for-yml/src/assets/logo.svg create mode 100644 examples/disable-type-checked-for-yml/src/assets/main.css create mode 100644 examples/disable-type-checked-for-yml/src/components/HelloWorld.vue create mode 100644 examples/disable-type-checked-for-yml/src/components/TheWelcome.vue create mode 100644 examples/disable-type-checked-for-yml/src/components/WelcomeItem.vue create mode 100644 examples/disable-type-checked-for-yml/src/components/__tests__/HelloWorld.spec.ts create mode 100644 examples/disable-type-checked-for-yml/src/components/icons/IconCommunity.vue create mode 100644 examples/disable-type-checked-for-yml/src/components/icons/IconDocumentation.vue create mode 100644 examples/disable-type-checked-for-yml/src/components/icons/IconEcosystem.vue create mode 100644 examples/disable-type-checked-for-yml/src/components/icons/IconSupport.vue create mode 100644 examples/disable-type-checked-for-yml/src/components/icons/IconTooling.vue create mode 100644 examples/disable-type-checked-for-yml/src/issue-136.yml create mode 100644 examples/disable-type-checked-for-yml/src/main.ts create mode 100644 examples/disable-type-checked-for-yml/src/router/index.ts create mode 100644 examples/disable-type-checked-for-yml/src/stores/counter.ts create mode 100644 examples/disable-type-checked-for-yml/src/views/AboutView.vue create mode 100644 examples/disable-type-checked-for-yml/src/views/HomeView.vue create mode 100644 examples/disable-type-checked-for-yml/tsconfig.app.json create mode 100644 examples/disable-type-checked-for-yml/tsconfig.json create mode 100644 examples/disable-type-checked-for-yml/tsconfig.node.json create mode 100644 examples/disable-type-checked-for-yml/tsconfig.vitest.json create mode 100644 examples/disable-type-checked-for-yml/vite.config.ts create mode 100644 examples/disable-type-checked-for-yml/vitest.config.ts create mode 100644 src/fpHelpers.ts diff --git a/examples/disable-type-checked-for-yml/.editorconfig b/examples/disable-type-checked-for-yml/.editorconfig new file mode 100644 index 0000000..ecea360 --- /dev/null +++ b/examples/disable-type-checked-for-yml/.editorconfig @@ -0,0 +1,6 @@ +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/examples/disable-type-checked-for-yml/.gitignore b/examples/disable-type-checked-for-yml/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/examples/disable-type-checked-for-yml/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/examples/disable-type-checked-for-yml/.prettierrc.json b/examples/disable-type-checked-for-yml/.prettierrc.json new file mode 100644 index 0000000..effc164 --- /dev/null +++ b/examples/disable-type-checked-for-yml/.prettierrc.json @@ -0,0 +1,7 @@ + +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "singleQuote": true, + "arrowParens": "avoid" +} diff --git a/examples/disable-type-checked-for-yml/.vscode/extensions.json b/examples/disable-type-checked-for-yml/.vscode/extensions.json new file mode 100644 index 0000000..de51a0a --- /dev/null +++ b/examples/disable-type-checked-for-yml/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "Vue.volar", + "vitest.explorer", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] +} diff --git a/examples/disable-type-checked-for-yml/README.md b/examples/disable-type-checked-for-yml/README.md new file mode 100644 index 0000000..64ea94a --- /dev/null +++ b/examples/disable-type-checked-for-yml/README.md @@ -0,0 +1,61 @@ +# disable-type-checked-for-yml + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +npm run build +``` + +### Run Unit Tests with [Vitest](https://vitest.dev/) + +```sh +npm run test:unit +``` + +### Run End-to-End Tests with [Cypress](https://www.cypress.io/) + +```sh +npm run test:e2e:dev +``` + +This runs the end-to-end tests against the Vite development server. +It is much faster than the production build. + +But it's still recommended to test the production build with `test:e2e` before deploying (e.g. in CI environments): + +```sh +npm run build +npm run test:e2e +``` + +### Lint with [ESLint](https://eslint.org/) + +```sh +npm run lint +``` diff --git a/examples/disable-type-checked-for-yml/cypress.config.ts b/examples/disable-type-checked-for-yml/cypress.config.ts new file mode 100644 index 0000000..0f66080 --- /dev/null +++ b/examples/disable-type-checked-for-yml/cypress.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'cypress' + +export default defineConfig({ + e2e: { + specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}', + baseUrl: 'http://localhost:4173' + } +}) diff --git a/examples/disable-type-checked-for-yml/cypress/e2e/example.cy.ts b/examples/disable-type-checked-for-yml/cypress/e2e/example.cy.ts new file mode 100644 index 0000000..7554c35 --- /dev/null +++ b/examples/disable-type-checked-for-yml/cypress/e2e/example.cy.ts @@ -0,0 +1,8 @@ +// https://on.cypress.io/api + +describe('My First Test', () => { + it('visits the app root url', () => { + cy.visit('/') + cy.contains('h1', 'You did it!') + }) +}) diff --git a/examples/disable-type-checked-for-yml/cypress/fixtures/example.json b/examples/disable-type-checked-for-yml/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/examples/disable-type-checked-for-yml/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/examples/disable-type-checked-for-yml/cypress/support/commands.ts b/examples/disable-type-checked-for-yml/cypress/support/commands.ts new file mode 100644 index 0000000..9b7bb8e --- /dev/null +++ b/examples/disable-type-checked-for-yml/cypress/support/commands.ts @@ -0,0 +1,39 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } + +export {} diff --git a/examples/disable-type-checked-for-yml/cypress/support/e2e.ts b/examples/disable-type-checked-for-yml/cypress/support/e2e.ts new file mode 100644 index 0000000..d68db96 --- /dev/null +++ b/examples/disable-type-checked-for-yml/cypress/support/e2e.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/examples/disable-type-checked-for-yml/cypress/tsconfig.json b/examples/disable-type-checked-for-yml/cypress/tsconfig.json new file mode 100644 index 0000000..a13c5d6 --- /dev/null +++ b/examples/disable-type-checked-for-yml/cypress/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["./e2e/**/*", "./support/**/*"], + "compilerOptions": { + "isolatedModules": false, + "types": ["cypress"] + } +} diff --git a/examples/disable-type-checked-for-yml/env.d.ts b/examples/disable-type-checked-for-yml/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/examples/disable-type-checked-for-yml/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/disable-type-checked-for-yml/eslint.config.js b/examples/disable-type-checked-for-yml/eslint.config.js new file mode 100644 index 0000000..43ab449 --- /dev/null +++ b/examples/disable-type-checked-for-yml/eslint.config.js @@ -0,0 +1,42 @@ +import pluginVue from 'eslint-plugin-vue' +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' +import pluginVitest from '@vitest/eslint-plugin' +import pluginCypress from 'eslint-plugin-cypress/flat' +import pluginYml from 'eslint-plugin-yml'; +import skipFormatting from '@vue/eslint-config-prettier/skip-formatting' + +export default defineConfigWithVueTs( + { + name: 'app/files-to-lint', + files: ['**/*.{ts,mts,tsx,vue,yml,yaml}'], + }, + + { + name: 'app/files-to-ignore', + ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'], + }, + + pluginVue.configs['flat/essential'], + vueTsConfigs.recommendedTypeChecked, + + { + ...pluginVitest.configs.recommended, + files: ['src/**/__tests__/*'], + }, + + { + ...pluginCypress.configs.recommended, + files: [ + 'cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}', + 'cypress/support/**/*.{js,ts,jsx,tsx}', + ], + }, + skipFormatting, + + ...pluginYml.configs['flat/recommended'], + + { + files: ["**/*.yml", "**/*.yaml"], + extends: [vueTsConfigs.disableTypeChecked], + }, +) diff --git a/examples/disable-type-checked-for-yml/index.html b/examples/disable-type-checked-for-yml/index.html new file mode 100644 index 0000000..a888544 --- /dev/null +++ b/examples/disable-type-checked-for-yml/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/disable-type-checked-for-yml/package.json b/examples/disable-type-checked-for-yml/package.json new file mode 100644 index 0000000..d6f4fad --- /dev/null +++ b/examples/disable-type-checked-for-yml/package.json @@ -0,0 +1,49 @@ +{ + "name": "disable-type-checked-for-yml", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "test:unit": "vitest", + "test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'", + "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'", + "build-only": "vite build", + "type-check": "vue-tsc --build --force", + "lint": "eslint . --fix", + "format": "prettier --write src/" + }, + "dependencies": { + "pinia": "^2.3.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@tsconfig/node20": "^20.1.4", + "@types/jsdom": "^21.1.7", + "@types/node": "^20.17.14", + "@vitejs/plugin-vue": "^5.2.1", + "@vitejs/plugin-vue-jsx": "^4.1.1", + "@vitest/eslint-plugin": "^1.1.25", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "workspace:*", + "@vue/test-utils": "^2.4.6", + "@vue/tsconfig": "^0.7.0", + "cypress": "^14.0.0", + "eslint": "^9.18.0", + "eslint-plugin-cypress": "^4.1.0", + "eslint-plugin-vue": "^9.32.0", + "eslint-plugin-yml": "^1.16.0", + "jsdom": "^26.0.0", + "npm-run-all2": "^7.0.2", + "prettier": "^3.4.2", + "start-server-and-test": "^2.0.10", + "typescript": "~5.7.3", + "vite": "^6.0.10", + "vite-plugin-vue-devtools": "^7.7.0", + "vitest": "^3.0.2", + "vue-tsc": "^2.2.0" + } +} diff --git a/examples/disable-type-checked-for-yml/public/favicon.ico b/examples/disable-type-checked-for-yml/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/examples/disable-type-checked-for-yml/src/App.vue b/examples/disable-type-checked-for-yml/src/App.vue new file mode 100644 index 0000000..7905b05 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/App.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/examples/disable-type-checked-for-yml/src/assets/base.css b/examples/disable-type-checked-for-yml/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/examples/disable-type-checked-for-yml/src/assets/logo.svg b/examples/disable-type-checked-for-yml/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/examples/disable-type-checked-for-yml/src/assets/main.css b/examples/disable-type-checked-for-yml/src/assets/main.css new file mode 100644 index 0000000..36fb845 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/assets/main.css @@ -0,0 +1,35 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + body { + display: flex; + place-items: center; + } + + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/examples/disable-type-checked-for-yml/src/components/HelloWorld.vue b/examples/disable-type-checked-for-yml/src/components/HelloWorld.vue new file mode 100644 index 0000000..d174cf8 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/examples/disable-type-checked-for-yml/src/components/TheWelcome.vue b/examples/disable-type-checked-for-yml/src/components/TheWelcome.vue new file mode 100644 index 0000000..e65a66b --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/components/TheWelcome.vue @@ -0,0 +1,88 @@ + + + diff --git a/examples/disable-type-checked-for-yml/src/components/WelcomeItem.vue b/examples/disable-type-checked-for-yml/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/examples/disable-type-checked-for-yml/src/components/__tests__/HelloWorld.spec.ts b/examples/disable-type-checked-for-yml/src/components/__tests__/HelloWorld.spec.ts new file mode 100644 index 0000000..2533202 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/components/__tests__/HelloWorld.spec.ts @@ -0,0 +1,11 @@ +import { describe, it, expect } from 'vitest' + +import { mount } from '@vue/test-utils' +import HelloWorld from '../HelloWorld.vue' + +describe('HelloWorld', () => { + it('renders properly', () => { + const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } }) + expect(wrapper.text()).toContain('Hello Vitest') + }) +}) diff --git a/examples/disable-type-checked-for-yml/src/components/icons/IconCommunity.vue b/examples/disable-type-checked-for-yml/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/examples/disable-type-checked-for-yml/src/components/icons/IconDocumentation.vue b/examples/disable-type-checked-for-yml/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/examples/disable-type-checked-for-yml/src/components/icons/IconEcosystem.vue b/examples/disable-type-checked-for-yml/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/examples/disable-type-checked-for-yml/src/components/icons/IconSupport.vue b/examples/disable-type-checked-for-yml/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/examples/disable-type-checked-for-yml/src/components/icons/IconTooling.vue b/examples/disable-type-checked-for-yml/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/examples/disable-type-checked-for-yml/src/issue-136.yml b/examples/disable-type-checked-for-yml/src/issue-136.yml new file mode 100644 index 0000000..a818c6f --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/issue-136.yml @@ -0,0 +1 @@ +just: some random yaml fields diff --git a/examples/disable-type-checked-for-yml/src/main.ts b/examples/disable-type-checked-for-yml/src/main.ts new file mode 100644 index 0000000..5dcad83 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/main.ts @@ -0,0 +1,14 @@ +import './assets/main.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/examples/disable-type-checked-for-yml/src/router/index.ts b/examples/disable-type-checked-for-yml/src/router/index.ts new file mode 100644 index 0000000..ce51fcf --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/router/index.ts @@ -0,0 +1,24 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomeView from '../views/HomeView.vue' +import type { Component } from 'vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: HomeView + }, + { + path: '/about', + name: 'about', + // route level code-splitting + // this generates a separate chunk (About.[hash].js) for this route + // which is lazy-loaded when the route is visited. + component: async (): Promise<{ default: Component }> => import('../views/AboutView.vue') + } + ] +}) + +export default router diff --git a/examples/disable-type-checked-for-yml/src/stores/counter.ts b/examples/disable-type-checked-for-yml/src/stores/counter.ts new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/stores/counter.ts @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/examples/disable-type-checked-for-yml/src/views/AboutView.vue b/examples/disable-type-checked-for-yml/src/views/AboutView.vue new file mode 100644 index 0000000..756ad2a --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/views/AboutView.vue @@ -0,0 +1,15 @@ + + + diff --git a/examples/disable-type-checked-for-yml/src/views/HomeView.vue b/examples/disable-type-checked-for-yml/src/views/HomeView.vue new file mode 100644 index 0000000..d5c0217 --- /dev/null +++ b/examples/disable-type-checked-for-yml/src/views/HomeView.vue @@ -0,0 +1,9 @@ + + + diff --git a/examples/disable-type-checked-for-yml/tsconfig.app.json b/examples/disable-type-checked-for-yml/tsconfig.app.json new file mode 100644 index 0000000..e14c754 --- /dev/null +++ b/examples/disable-type-checked-for-yml/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/examples/disable-type-checked-for-yml/tsconfig.json b/examples/disable-type-checked-for-yml/tsconfig.json new file mode 100644 index 0000000..5304731 --- /dev/null +++ b/examples/disable-type-checked-for-yml/tsconfig.json @@ -0,0 +1,17 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.vitest.json" + } + ], + "compilerOptions": { + "module": "NodeNext" + } +} diff --git a/examples/disable-type-checked-for-yml/tsconfig.node.json b/examples/disable-type-checked-for-yml/tsconfig.node.json new file mode 100644 index 0000000..f094063 --- /dev/null +++ b/examples/disable-type-checked-for-yml/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*" + ], + "compilerOptions": { + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/examples/disable-type-checked-for-yml/tsconfig.vitest.json b/examples/disable-type-checked-for-yml/tsconfig.vitest.json new file mode 100644 index 0000000..571995d --- /dev/null +++ b/examples/disable-type-checked-for-yml/tsconfig.vitest.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.app.json", + "exclude": [], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo", + + "lib": [], + "types": ["node", "jsdom"] + } +} diff --git a/examples/disable-type-checked-for-yml/vite.config.ts b/examples/disable-type-checked-for-yml/vite.config.ts new file mode 100644 index 0000000..c036b6f --- /dev/null +++ b/examples/disable-type-checked-for-yml/vite.config.ts @@ -0,0 +1,20 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueJsx(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } +}) diff --git a/examples/disable-type-checked-for-yml/vitest.config.ts b/examples/disable-type-checked-for-yml/vitest.config.ts new file mode 100644 index 0000000..4b1c897 --- /dev/null +++ b/examples/disable-type-checked-for-yml/vitest.config.ts @@ -0,0 +1,14 @@ +import { fileURLToPath } from 'node:url' +import { mergeConfig, defineConfig, configDefaults } from 'vitest/config' +import viteConfig from './vite.config' + +export default mergeConfig( + viteConfig, + defineConfig({ + test: { + environment: 'jsdom', + exclude: [...configDefaults.exclude, 'e2e/**'], + root: fileURLToPath(new URL('./', import.meta.url)) + } + }) +) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 654e297..6251701 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -220,6 +220,91 @@ importers: specifier: ^2.2.0 version: 2.2.0(typescript@5.7.3) + examples/disable-type-checked-for-yml: + dependencies: + pinia: + specifier: ^2.3.1 + version: 2.3.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)) + vue: + specifier: ^3.5.13 + version: 3.5.13(typescript@5.7.3) + vue-router: + specifier: ^4.5.0 + version: 4.5.0(vue@3.5.13(typescript@5.7.3)) + devDependencies: + '@tsconfig/node20': + specifier: ^20.1.4 + version: 20.1.4 + '@types/jsdom': + specifier: ^21.1.7 + version: 21.1.7 + '@types/node': + specifier: ^20.17.14 + version: 20.17.14 + '@vitejs/plugin-vue': + specifier: ^5.2.1 + version: 5.2.1(vite@6.0.10(@types/node@20.17.14)(tsx@4.19.2)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3)) + '@vitejs/plugin-vue-jsx': + specifier: ^4.1.1 + version: 4.1.1(vite@6.0.10(@types/node@20.17.14)(tsx@4.19.2)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3)) + '@vitest/eslint-plugin': + specifier: ^1.1.25 + version: 1.1.25(@typescript-eslint/utils@8.21.0(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0)(typescript@5.7.3)(vitest@3.0.2(@types/node@20.17.14)(jsdom@26.0.0)(tsx@4.19.2)(yaml@2.7.0)) + '@vue/eslint-config-prettier': + specifier: ^10.2.0 + version: 10.2.0(eslint@9.18.0)(prettier@3.4.2) + '@vue/eslint-config-typescript': + specifier: workspace:* + version: link:../.. + '@vue/test-utils': + specifier: ^2.4.6 + version: 2.4.6 + '@vue/tsconfig': + specifier: ^0.7.0 + version: 0.7.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)) + cypress: + specifier: ^14.0.0 + version: 14.0.0 + eslint: + specifier: ^9.18.0 + version: 9.18.0 + eslint-plugin-cypress: + specifier: ^4.1.0 + version: 4.1.0(eslint@9.18.0) + eslint-plugin-vue: + specifier: ^9.32.0 + version: 9.32.0(eslint@9.18.0) + eslint-plugin-yml: + specifier: ^1.16.0 + version: 1.16.0(eslint@9.18.0) + jsdom: + specifier: ^26.0.0 + version: 26.0.0 + npm-run-all2: + specifier: ^7.0.2 + version: 7.0.2 + prettier: + specifier: ^3.4.2 + version: 3.4.2 + start-server-and-test: + specifier: ^2.0.10 + version: 2.0.10 + typescript: + specifier: ~5.7.3 + version: 5.7.3 + vite: + specifier: ^6.0.10 + version: 6.0.10(@types/node@20.17.14)(tsx@4.19.2)(yaml@2.7.0) + vite-plugin-vue-devtools: + specifier: ^7.7.0 + version: 7.7.0(rollup@4.30.1)(vite@6.0.10(@types/node@20.17.14)(tsx@4.19.2)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3)) + vitest: + specifier: ^3.0.2 + version: 3.0.2(@types/node@20.17.14)(jsdom@26.0.0)(tsx@4.19.2)(yaml@2.7.0) + vue-tsc: + specifier: ^2.2.0 + version: 2.2.0(typescript@5.7.3) + examples/minimal: dependencies: vue: @@ -3058,6 +3143,12 @@ packages: engines: {node: '>=6.0'} hasBin: true + eslint-compat-utils@0.6.4: + resolution: {integrity: sha512-/u+GQt8NMfXO8w17QendT4gvO5acfxQsAKirAt0LVxDnr2N8YLCVbregaNc/Yhp7NM128DwCaRvr8PLDfeNkQw==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + eslint-config-prettier@10.0.1: resolution: {integrity: sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==} hasBin: true @@ -3095,6 +3186,12 @@ packages: peerDependencies: eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + eslint-plugin-yml@1.16.0: + resolution: {integrity: sha512-t4MNCetPjTn18/fUDlQ/wKkcYjnuLYKChBrZ0qUaNqRigVqChHWzTP8SrfFi5s4keX3vdlkWRSu8zHJMdKwxWQ==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5185,6 +5282,10 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml-eslint-parser@1.2.3: + resolution: {integrity: sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==} + engines: {node: ^14.17.0 || >=16.0.0} + yaml@2.7.0: resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} engines: {node: '>= 14'} @@ -6115,7 +6216,7 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 20.17.14 + '@types/node': 22.10.7 '@types/tough-cookie': 4.0.5 parse5: 7.2.1 @@ -6124,7 +6225,7 @@ snapshots: '@types/nightwatch@2.3.32': dependencies: '@types/chai': 5.0.1 - '@types/node': 20.17.14 + '@types/node': 22.10.7 '@types/selenium-webdriver': 4.1.28 devtools-protocol: 0.0.1025565 @@ -6140,7 +6241,7 @@ snapshots: '@types/selenium-webdriver@4.1.28': dependencies: - '@types/node': 20.17.14 + '@types/node': 22.10.7 '@types/ws': 8.5.13 '@types/sinonjs__fake-timers@8.1.1': {} @@ -6151,7 +6252,7 @@ snapshots: '@types/ws@8.5.13': dependencies: - '@types/node': 20.17.14 + '@types/node': 22.10.7 '@types/yauzl@2.10.3': dependencies: @@ -7394,6 +7495,11 @@ snapshots: optionalDependencies: source-map: 0.6.1 + eslint-compat-utils@0.6.4(eslint@9.18.0): + dependencies: + eslint: 9.18.0 + semver: 7.6.3 + eslint-config-prettier@10.0.1(eslint@9.18.0): dependencies: eslint: 9.18.0 @@ -7431,6 +7537,17 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-yml@1.16.0(eslint@9.18.0): + dependencies: + debug: 4.4.0(supports-color@8.1.1) + eslint: 9.18.0 + eslint-compat-utils: 0.6.4(eslint@9.18.0) + lodash: 4.17.21 + natural-compare: 1.4.0 + yaml-eslint-parser: 1.2.3 + transitivePeerDependencies: + - supports-color + eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 @@ -9806,6 +9923,12 @@ snapshots: yallist@4.0.0: {} + yaml-eslint-parser@1.2.3: + dependencies: + eslint-visitor-keys: 3.4.3 + lodash: 4.17.21 + yaml: 2.7.0 + yaml@2.7.0: {} yargs-parser@20.2.4: {} diff --git a/src/configs.ts b/src/configs.ts index 4207eb9..177fc4b 100644 --- a/src/configs.ts +++ b/src/configs.ts @@ -1,4 +1,4 @@ -import tseslint from 'typescript-eslint' +import tseslint, { config } from 'typescript-eslint' import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint' const CONFIG_NAMES = [ @@ -22,12 +22,46 @@ function toArray(value: T | T[]): T[] { return Array.isArray(value) ? value : [value] } +/** + * The options that a config in the `extends` should inherit. + */ +type ExtendsOptions = { + name?: string + files?: (string | string[])[] + ignores?: string[] +} + export class TsEslintConfigForVue { - // the name property is here to provide better error messages when ESLint throws an error + /** + * The name of the config object as defined in `typescript-eslint`. + */ configName: ExtendableConfigName + /** + * the name property is here to provide better error messages when ESLint throws an error + */ + name: string + constructor(configName: ExtendableConfigName) { this.configName = configName + this.name = `vueTsConfigs.${configName}` + } + + extendsOptions?: ExtendsOptions + /** + * Create a new instance of `TsEslintConfigForVue` with the `restOfConfig` merged into it. + * Should be used when the config is used in the `extends` field of another config. + */ + asExtendedWith(restOfConfig: ExtendsOptions): TsEslintConfigForVue { + const extendedConfig = new TsEslintConfigForVue(this.configName) + + extendedConfig.extendsOptions = { + name: [restOfConfig.name, this.name].filter(Boolean).join('__'), + ...(restOfConfig.files && { files: restOfConfig.files }), + ...(restOfConfig.ignores && { ignores: restOfConfig.ignores }), + } + + return extendedConfig } needsTypeChecking(): boolean { @@ -43,14 +77,13 @@ export class TsEslintConfigForVue { toConfigArray(): FlatConfig.ConfigArray { return toArray(tseslint.configs[this.configName]) .flat() - .map(config => - config.files && config.files.includes('**/*.ts') - ? { - ...config, - files: [...config.files, '**/*.vue'], - } - : config, - ) + .map(config => ({ + ...config, + ...(config.files && config.files.includes('**/*.ts') + ? { files: [...config.files, '**/*.vue'] } + : {}), + ...this.extendsOptions, + })) } } @@ -68,15 +101,6 @@ export const vueTsConfigs = Object.fromEntries( 'Please wrap the config object with `defineConfigWithVueTs()`', ) }, - - get(target, prop) { - // for clearer error messages on where the config is coming from - if (prop === 'name') { - return `vueTsConfigs.${Reflect.get(target, 'configName')}` - } - - return Reflect.get(target, prop) - }, }), ]), ) as Record diff --git a/src/fpHelpers.ts b/src/fpHelpers.ts new file mode 100644 index 0000000..a5e612b --- /dev/null +++ b/src/fpHelpers.ts @@ -0,0 +1,57 @@ +// This file is for some generic helper functions that aren't tied to the main functionality of the project. + +export function omit, K extends keyof T>(obj: T, keys: K[]): Omit { + return Object.fromEntries( + Object.entries(obj).filter(([key]) => !keys.includes(key as K)) + ) as Omit; +} + +// https://dev.to/nexxeln/implementing-the-pipe-operator-in-typescript-30ip +interface Pipe { + (value: A): A; + (value: A, fn1: (input: A) => B): B; + (value: A, fn1: (input: A) => B, fn2: (input: B) => C): C; + ( + value: A, + fn1: (input: A) => B, + fn2: (input: B) => C, + fn3: (input: C) => D + ): D; + ( + value: A, + fn1: (input: A) => B, + fn2: (input: B) => C, + fn3: (input: C) => D, + fn4: (input: D) => E + ): E; + ( + value: A, + fn1: (input: A) => B, + fn2: (input: B) => C, + fn3: (input: C) => D, + fn4: (input: D) => E, + fn5: (input: E) => F + ): F; + // ... and so on +} + +export const pipe: Pipe = (value: any, ...fns: Function[]): unknown => { + return fns.reduce((acc, fn) => fn(acc), value); +}; + + +export function partition( + array: T[], + predicate: (element: T) => boolean, +): [T[], T[]] { + const truthy: T[] = [] + const falsy: T[] = [] + for (const element of array) { + if (predicate(element)) { + truthy.push(element) + } else { + falsy.push(element) + } + } + return [truthy, falsy] +} diff --git a/src/utilities.ts b/src/utilities.ts index 508f7dc..fe8a761 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,5 +1,7 @@ import process from 'node:process' import * as tseslint from 'typescript-eslint' +import type { TSESLint } from '@typescript-eslint/utils' + import { TsEslintConfigForVue } from './configs' import groupVueFiles from './groupVueFiles' import { @@ -8,16 +10,18 @@ import { createSkipTypeCheckingConfigs, createTypeCheckingConfigs, } from './internals' - import type { ScriptLang } from './internals' -type ConfigArray = ReturnType -type Rules = NonNullable +import { omit, pipe, partition } from './fpHelpers' -type ConfigObjectOrPlaceholder = ConfigArray[number] | TsEslintConfigForVue -type InfiniteDepthConfigWithVueSupport = - | ConfigObjectOrPlaceholder - | InfiniteDepthConfigWithVueSupport[] +type ConfigItem = TSESLint.FlatConfig.Config +type InfiniteDepthConfigWithExtendsAndVueSupport = + | TsEslintConfigForVue + | ConfigItemWithExtendsAndVueSupport + | InfiniteDepthConfigWithExtendsAndVueSupport[] +interface ConfigItemWithExtendsAndVueSupport extends ConfigItem { + extends?: InfiniteDepthConfigWithExtendsAndVueSupport[] +} export type ProjectOptions = { scriptLangs?: ScriptLang[] @@ -40,51 +44,115 @@ export function configureVueProject(userOptions: ProjectOptions): void { } } +// The *Raw* types are those with placeholders not yet resolved. +type RawConfigItemWithExtends = + | ConfigItemWithExtendsAndVueSupport + | TsEslintConfigForVue +type RawConfigItem = ConfigItem | TsEslintConfigForVue + export function defineConfigWithVueTs( - ...configs: InfiniteDepthConfigWithVueSupport[] -): ConfigArray { - // @ts-ignore - const flattenedConfigs: ConfigObjectOrPlaceholder[] = configs.flat(Infinity) + ...configs: InfiniteDepthConfigWithExtendsAndVueSupport[] +): ConfigItem[] { + return pipe( + configs, + flattenConfigs, + insertAndReorderConfigs, + resolveVueTsConfigs, + tseslint.config, + (configs: ConfigItem[]) => { + console.log(configs) + return configs + }, + ) +} - const reorderedConfigs = insertAndReorderConfigs(flattenedConfigs) +function flattenConfigs( + configs: InfiniteDepthConfigWithExtendsAndVueSupport[], +): RawConfigItem[] { + // Be careful that our TS types don't guarantee that `extends` is removed from the final config + // Modified from + // https://github.com/typescript-eslint/typescript-eslint/blob/d30a497ef470b5a06ca0a5dde9543b6e00c87a5f/packages/typescript-eslint/src/config-helper.ts#L98-L143 + // No handling of undefined for now for simplicity + + // @ts-expect-error -- intentionally an infinite type + return (configs.flat(Infinity) as RawConfigItemWithExtends[]).flatMap( + (c: RawConfigItemWithExtends): RawConfigItem | RawConfigItem[] => { + if (c instanceof TsEslintConfigForVue) { + return c + } + + const { extends: extendsArray, ...restOfConfig } = c + if (extendsArray == null || extendsArray.length === 0) { + return restOfConfig + } + + const flattenedExtends: RawConfigItem[] = extendsArray.flatMap( + configToExtend => + Array.isArray(configToExtend) + ? flattenConfigs(configToExtend) + : [configToExtend], + ) + + return [ + ...flattenedExtends.map((extension: RawConfigItem) => { + if (extension instanceof TsEslintConfigForVue) { + return extension.asExtendedWith(restOfConfig) + } else { + const name = [restOfConfig.name, extension.name] + .filter(Boolean) + .join('__') + return { + ...extension, + ...(restOfConfig.files && { files: restOfConfig.files }), + ...(restOfConfig.ignores && { ignores: restOfConfig.ignores }), + ...(name && { name }), + } + } + }), + + // If restOfConfig contains nothing but `ignores`/`name`, we shouldn't return it + // Because that would make it a global `ignores` config, which is not what we want + ...(Object.keys(omit(restOfConfig, ['ignores', 'name'])).length > 0 + ? [restOfConfig] + : []), + ] + }, + ) +} - const normalizedConfigs = reorderedConfigs.map(config => +function resolveVueTsConfigs(configs: RawConfigItem[]): ConfigItem[] { + return configs.flatMap(config => config instanceof TsEslintConfigForVue ? config.toConfigArray() : config, ) - - return tseslint.config(...normalizedConfigs) } -// This function reorders the config array to make sure it satisfies the following layout: -// -// [FIRST-EXTENDED-CONFIG] -// ... -// [LAST-EXTENDED-CONFIG] -// -// [BASIC SETUP] -// pluginVue.configs['flat/base'], -// '@vue/typescript/setup' -// -// [ALL-OTHER-TYPE-AWARE-RULES-CONFIGURED-BY-USERS] -// -// [ERROR PREVENTION & PERFORMANCE OPTIMIZATION] -// '@vue/typescript/skip-type-checking-for-js-files' -// '@vue/typescript/skip-type-checking-for-vue-files-without-ts' -// -// [ONLY REQUIRED WHEN ONE-OR-MORE TYPE-AWARE RULES ARE TURNED-ON] -// '@vue/typescript/default-project-service-for-ts-files' -// '@vue/typescript/default-project-service-for-vue-files' -// '@vue/typescript/type-aware-rules-in-conflit-with-vue' - type ExtractedConfig = { files?: (string | string[])[] - rules: Rules + rules: NonNullable } const userTypeAwareConfigs: ExtractedConfig[] = [] -function insertAndReorderConfigs( - configs: ConfigObjectOrPlaceholder[], -): ConfigObjectOrPlaceholder[] { +/** + * This function reorders the config array to make sure it satisfies the following layout: + * + * ``` + * [FIRST-EXTENDED-CONFIG] + * ... + * [LAST-EXTENDED-CONFIG] + * [BASIC SETUP] + * pluginVue.configs['flat/base'], + * '@vue/typescript/setup' + * [ALL-OTHER-TYPE-AWARE-RULES-CONFIGURED-BY-USERS] + * [ERROR PREVENTION & PERFORMANCE OPTIMIZATION] + * '@vue/typescript/skip-type-checking-for-js-files' + * '@vue/typescript/skip-type-checking-for-vue-files-without-ts' + * [ONLY REQUIRED WHEN ONE-OR-MORE TYPE-AWARE-RULES ARE TURNED-ON] + * '@vue/typescript/default-project-service-for-ts-files' + * '@vue/typescript/default-project-service-for-vue-files' + * '@vue/typescript/type-aware-rules-in-conflit-with-vue' + * ``` + */ +function insertAndReorderConfigs(configs: RawConfigItem[]): RawConfigItem[] { const lastExtendedConfigIndex = configs.findLastIndex( config => config instanceof TsEslintConfigForVue, ) @@ -125,9 +193,7 @@ function insertAndReorderConfigs( ] } -function extractTypeAwareRules( - config: ConfigObjectOrPlaceholder, -): ConfigObjectOrPlaceholder { +function extractTypeAwareRules(config: RawConfigItem): RawConfigItem { if (config instanceof TsEslintConfigForVue) { return config } @@ -164,19 +230,3 @@ const rulesRequiringTypeInformation = new Set( function doesRuleRequireTypeInformation(ruleName: string): boolean { return rulesRequiringTypeInformation.has(ruleName) } - -function partition( - array: T[], - predicate: (element: T) => boolean, -): [T[], T[]] { - const truthy: T[] = [] - const falsy: T[] = [] - for (const element of array) { - if (predicate(element)) { - truthy.push(element) - } else { - falsy.push(element) - } - } - return [truthy, falsy] -} From d121015175d5ebe5390ef83091761814cbce0b99 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Sat, 8 Feb 2025 16:33:55 +0800 Subject: [PATCH 2/3] fix: extraneous console.log --- src/utilities.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/utilities.ts b/src/utilities.ts index fe8a761..47287c6 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -59,10 +59,6 @@ export function defineConfigWithVueTs( insertAndReorderConfigs, resolveVueTsConfigs, tseslint.config, - (configs: ConfigItem[]) => { - console.log(configs) - return configs - }, ) } From dd01589b06136094d75becc1b925c7fe31b0069f Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Sun, 9 Feb 2025 20:10:49 +0800 Subject: [PATCH 3/3] refactor: use default import for typescript-eslint, and other small changes --- src/configs.ts | 2 +- src/createConfig.ts | 2 +- src/internals.ts | 2 +- src/utilities.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/configs.ts b/src/configs.ts index 177fc4b..d36833a 100644 --- a/src/configs.ts +++ b/src/configs.ts @@ -1,4 +1,4 @@ -import tseslint, { config } from 'typescript-eslint' +import tseslint from 'typescript-eslint' import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint' const CONFIG_NAMES = [ diff --git a/src/createConfig.ts b/src/createConfig.ts index 5bde0c5..806b067 100644 --- a/src/createConfig.ts +++ b/src/createConfig.ts @@ -1,7 +1,7 @@ // This is a compatibility layer for the `createConfig` function in <= 14.2.0 // Will be removed in 15.0.0 -import * as tseslint from 'typescript-eslint' +import tseslint from 'typescript-eslint' import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint' import { diff --git a/src/internals.ts b/src/internals.ts index e48b0dc..097c6ff 100644 --- a/src/internals.ts +++ b/src/internals.ts @@ -1,4 +1,4 @@ -import * as tseslint from 'typescript-eslint' +import tseslint from 'typescript-eslint' import vueParser from 'vue-eslint-parser' import pluginVue from 'eslint-plugin-vue' import type { Parser } from '@typescript-eslint/utils/ts-eslint' diff --git a/src/utilities.ts b/src/utilities.ts index 38cb453..0caa7e5 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,5 +1,5 @@ import process from 'node:process' -import * as tseslint from 'typescript-eslint' +import tseslint from 'typescript-eslint' import type { TSESLint } from '@typescript-eslint/utils' import { TsEslintConfigForVue } from './configs' @@ -80,7 +80,7 @@ export function defineConfigWithVueTs( flattenConfigs, insertAndReorderConfigs, resolveVueTsConfigs, - tseslint.config, + tseslint.config, // this might not be necessary, but it doesn't hurt to keep it ) }