diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index ed952f8895..0000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @johnsoncodehk @so1ve @KazariEX @zhiyuanzmj @KermanX @davidmatter diff --git a/.github/workflows/update-html-data.yml b/.github/workflows/update-html-data.yml index fde2f6bccf..b228081acd 100644 --- a/.github/workflows/update-html-data.yml +++ b/.github/workflows/update-html-data.yml @@ -1,6 +1,9 @@ name: update-html-data on: + push: + branches: + - 'master' workflow_dispatch: schedule: - cron: '0 0 * * *' diff --git a/.vscode/settings.json b/.vscode/settings.json index da50bdc4ff..0efd9ce70d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,9 @@ "typescript.format.semicolons": "insert", "editor.insertSpaces": false, "editor.detectIndentation": false, + "editor.codeActionsOnSave": { + "source.organizeImports": "always" + }, "json.format.keepLines": true, "typescript.tsdk": "node_modules/typescript/lib", "[typescript]": { diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e27573f7a..7379020c33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,69 @@ > [Join the Insiders Program](https://github.com/vuejs/language-tools/wiki/Get-Insiders-Edition) for more exclusive features and updates. +## 2.2.0 official, 2.2.1 insiders (2024-12-24) + +### Features + +- feat(language-core): support `@vue-generic` (#4971) - Thanks to @KazariEX! +- feat(vscode): add configuration for skipping automatic detection of Hybrid Mode (#5046) - Thanks to @KazariEX! +- feat(language-service): crawl html data of `data-allow-mismatch` - Thanks to @KazariEX! +- feat(language-core): type support of `$attrs` (#5076) - Thanks to @KazariEX! +- feat(language-core): type support of `useSlots` and `$slots` (#5055) - Thanks to @KazariEX! +- feat(language-core): type support of `v-model` modifiers (#5061) - Thanks to @KazariEX! +- feat(language-service): process references data at runtime to reduce bundle size (#5054) - Thanks to @KazariEX! +- feat(language-core): support the use of sfc root comment to configure `vueCompilerOptions` (#4987) - Thanks to @KazariEX! +- feat(vscode): add timeout logic for insiders fetching (#5048) - Thanks to @KazariEX! +- feat(vscode): add examples to inlay hints configuration (#5068) - Thanks to @KazariEX! + +### Performance + +- perf(typescript-plugin): use named pipe servers more efficiently (#5070) + +### Bug Fixes + +- fix(language-core): generate script setup starting from last leading comment without `@ts-check` - Thanks to @KazariEX! +- fix(language-core): make model modifiers optional (#4978) - Thanks to @stafyniaksacha! +- fix(language-core): always report missing props on `` (#4982) - Thanks to @KazariEX! +- fix(language-core): avoid unchecked index access when parsing `defineEmits` (#5028) - Thanks to @KazariEX! +- fix(language-service): handle text edit of special closing tags completion correctly (#5016) - Thanks to @KazariEX! +- fix(language-core): don't generate variable access of template refs using `useTemplateRef` (#5032) - Thanks to @KazariEX! +- fix(vscode): update `enabledHybridMode` before activate extension (#5019) - Thanks to @nieyuyao! +- fix(tsc): point to shimmed tsc entry point to support ts 5.7 (#5020) - Thanks to @davidmatter! +- fix(vscode): add `GitHub.copilot-chat` to hybrid mode compatible list (#5047) - Thanks to @KazariEX! +- fix(language-core): generate generics normally when `useTemplateRef` has no parameters (#5051) - Thanks to @KazariEX! +- fix(language-core): avoid clipping prop name using `.prop` or `.attr` on `v-model` - Thanks to @KazariEX! +- fix(language-core): handle named default import of components correctly (#5066) - Thanks to @KazariEX! +- fix(language-core): disable navigation feature on non-binding prop values (#5040) - Thanks to @KazariEX! +- fix(language-core): do not generate `useTemplateRef` parameter repeatedly (#5009) +- fix(language-core): generate macros after script setup content (#5071) - Thanks to @KazariEX! +- fix(language-core): correct type and completion support of `vue:` event (#4969) - Thanks to @KazariEX! +- fix(language-core): prevent visiting functional components for `parseScriptSetupRanges` (#5049) - Thanks to @zhiyuanzmj! +- fix(language-service): don't provide modifier completion for `@` and `:` (#5052) - Thanks to @KazariEX! +- fix(language-core): consistent interpolation behavior of shorthand binding (#4975) - Thanks to @KazariEX! +- fix(language-core): resolve components with various name cases correctly (#5067) - Thanks to @KazariEX! +- fix(language-core): map `v-slot` correctly to report error when missing default slot - Thanks to @KazariEX! +- fix(language-core): map component loc to instance variable for verification - Thanks to @KazariEX! + +### Other Changes + +- refactor: improve code consistency (#4976) - Thanks to @KazariEX! +- docs: update nvim guide (#4984) - Thanks to @zeromask1337! +- docs: fix broken marketplace page (#5004) - Thanks to @rioj7! +- chore: upgrade `reactive-vscode` to v0.2.7 (#4997) - Thanks to @KermanX! +- refactor(language-service): consistent style of source and virtual code operation (#5053) - Thanks to @KazariEX! +- refactor(language-core): remove unnecessary linked code mappings of `defineProp` (#5058) - Thanks to @KazariEX! +- refactor(language-core): simplify current component info passing (#5078) - Thanks to @KazariEX! +- Upgraded Volar from `v2.4.8` to `v2.4.11`: + - fix(typescript): avoid crash when converting relatedInformation from overly large files + - fix(typescript): fix interactive refactors (https://github.com/volarjs/volar.js/pull/244) - Thanks to @andrewbranch! + - fix(typescript): should not suppressing getLanguageId crashes (https://github.com/volarjs/volar.js/issues/253) + - fix(typescript): force update the opened script snapshot after the language plugin is ready (https://github.com/volarjs/volar.js/issues/254) + - feat(typescript): add typescriptObject option to runTsc (https://github.com/volarjs/volar.js/pull/245) - Thanks to @zhiyuanzmj! + - fix(typescript): fix issue with transpiled TypeScript files not being registered with a project at all (https://github.com/volarjs/volar.js/pull/250) - Thanks to @piotrtomiak! + - docs(source-map): updated API section based on #207 (https://github.com/volarjs/volar.js/pull/248) - Thanks to @alamhubb! + - fix(typescript): resolve the shim used for tsc in Typescript v5.7 and up (#252) - Thanks to @kitsune7! + ## 2.1.10 official, 2.1.11 insiders (2024-10-31) ### Features @@ -97,7 +160,7 @@ - **language-core:** split `__VLS_templateResult` (#4781) - Thanks to @KazariEX! - **language-core:** wrap template virtual code into a function (#4784) -- **language-core:** move `templateRef` into `composibles` (#4791) - Thanks to @KazariEX! +- **language-core:** move `templateRef` into `composables` (#4791) - Thanks to @KazariEX! - **language-core:** generate global types for the first parsed Vue component if cannot write global types file ### Tests diff --git a/README.md b/README.md index 182573c9e7..d00e4bffc3 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ Note: The "Take Over" mode has been discontinued. Instead, a new "Hybrid" mode has been introduced. In this mode, the Vue Language Server exclusively manages the CSS/HTML sections. As a result, you must run `@vue/language-server` in conjunction with a TypeScript server that employs `@vue/typescript-plugin`. Below is a streamlined configuration for Neovim's LSP, updated to accommodate the language server following the upgrade to version `2.0.0`. +> For nvim-lspconfig versions below [v1.0.0](https://newreleases.io/project/github/neovim/nvim-lspconfig/release/v1.0.0) use tsserver instead of ts_ls, e.g. `lspconfig.ts_ls.setup` + ```lua -- If you are using mason.nvim, you can get the ts_plugin_path like this -- local mason_registry = require('mason-registry') @@ -43,7 +45,7 @@ local vue_language_server_path = '/path/to/@vue/language-server' local lspconfig = require('lspconfig') -lspconfig.tsserver.setup { +lspconfig.ts_ls.setup { init_options = { plugins = { { @@ -62,7 +64,7 @@ lspconfig.volar.setup {} ### Non-Hybrid mode(similar to takeover mode) configuration (Requires `@vue/language-server` version `^2.0.7`) -Note: If `hybridMode` is set to `false` `Volar` will run embedded `tsserver` therefore there is no need to run it separately. +Note: If `hybridMode` is set to `false` `Volar` will run embedded `ts_ls` therefore there is no need to run it separately. For more information see [#4119](https://github.com/vuejs/language-tools/pull/4119) @@ -72,7 +74,7 @@ Use volar for all `.{vue,js,ts,tsx,jsx}` files. ```lua local lspconfig = require('lspconfig') --- lspconfig.tsserver.setup {} +-- lspconfig.ts_ls.setup {} lspconfig.volar.setup { filetypes = { 'typescript', 'javascript', 'javascriptreact', 'typescriptreact', 'vue' }, init_options = { @@ -83,11 +85,11 @@ lspconfig.volar.setup { } ``` -Use `volar` for only `.vue` files and `tsserver` for `.ts` and `.js` files. +Use `volar` for only `.vue` files and `ts_ls` for `.ts` and `.js` files. ```lua local lspconfig = require('lspconfig') -lspconfig.tsserver.setup { +lspconfig.ts_ls.setup { init_options = { plugins = { { diff --git a/extensions/vscode/.vscodeignore b/extensions/vscode/.vscodeignore index 2791123a21..df03891801 100644 --- a/extensions/vscode/.vscodeignore +++ b/extensions/vscode/.vscodeignore @@ -1,6 +1,7 @@ out scripts src +tests tsconfig.* meta.json stats.html diff --git a/extensions/vscode/README.md b/extensions/vscode/README.md index 9bdd0a42e2..c26404c94e 100644 --- a/extensions/vscode/README.md +++ b/extensions/vscode/README.md @@ -254,52 +254,3 @@ Finally you need to make VS Code recognize your new extension and automatically - [angular](https://github.com/angular/angular) shows how TS server plugin working with language service. - Syntax highlight is rewritten base on [vue-syntax-highlight](https://github.com/vuejs/vue-syntax-highlight). - [vscode-fenced-code-block-grammar-injection-example](https://github.com/mjbvz/vscode-fenced-code-block-grammar-injection-example) shows how to inject vue syntax highlight to markdown. - -## Commands - - - -| Command | Title | -| ------------------------------ | ------------------------------------------------- | -| `vue.action.restartServer` | Vue: Restart Vue and TS servers | -| `vue.action.doctor` | Vue: Doctor | -| `vue.action.writeVirtualFiles` | Vue (Debug): Write Virtual Files | -| `vue.action.splitEditors` | Vue: Split \n```" }, "vue.inlayHints.vBindShorthand": { "type": "boolean", "default": false, - "description": "Show inlay hints for v-bind shorthand." + "markdownDescription": "Show inlay hints for v-bind shorthand:\n\n```html\n\n \n```" }, "vue.format.template.initialIndent": { "type": "boolean", @@ -557,15 +565,14 @@ "devDependencies": { "@types/semver": "^7.5.3", "@types/vscode": "^1.82.0", - "@volar/vscode": "~2.4.8", + "@volar/vscode": "~2.4.11", "@vscode/vsce": "latest", - "@vue/language-core": "2.1.10", - "@vue/language-server": "2.1.10", - "@vue/typescript-plugin": "2.1.10", - "esbuild": "~0.21.0", - "esbuild-plugin-copy": "latest", + "@vue/language-core": "2.2.0", + "@vue/language-server": "2.2.0", + "@vue/typescript-plugin": "2.2.0", + "esbuild": "latest", "esbuild-visualizer": "latest", - "reactive-vscode": "0.2.7-beta.1", + "reactive-vscode": "^0.2.9", "semver": "^7.5.4", "vscode-ext-gen": "^0.5.0", "vscode-tmlanguage-snapshot": "latest" diff --git a/extensions/vscode/scripts/build.js b/extensions/vscode/scripts/build.js index eafe5d17a1..39acc64a1f 100644 --- a/extensions/vscode/scripts/build.js +++ b/extensions/vscode/scripts/build.js @@ -4,12 +4,14 @@ const fs = require('fs'); require('esbuild').context({ entryPoints: { - client: './out/nodeClientMain.js', - server: './node_modules/@vue/language-server/bin/vue-language-server.js', + 'dist/client': './out/nodeClientMain.js', + 'dist/server': './node_modules/@vue/language-server/bin/vue-language-server.js', + 'node_modules/vue-language-core-pack/index': './node_modules/@vue/language-core/index.js', + 'node_modules/vue-typescript-plugin-pack/index': './node_modules/@vue/typescript-plugin/index.js', }, bundle: true, metafile: process.argv.includes('--metafile'), - outdir: './dist', + outdir: '.', external: ['vscode'], format: 'cjs', platform: 'node', @@ -20,23 +22,50 @@ require('esbuild').context({ { name: 'umd2esm', setup(build) { - build.onResolve({ filter: /^(vscode-.*-languageservice|jsonc-parser)/ }, args => { - const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] }) + build.onResolve({ filter: /^(vscode-.*-languageservice|vscode-languageserver-types|jsonc-parser)$/ }, args => { + const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] }); // Call twice the replace is to solve the problem of the path in Windows - const pathEsm = pathUmdMay.replace('/umd/', '/esm/').replace('\\umd\\', '\\esm\\') - return { path: pathEsm } - }) + const pathEsm = pathUmdMay.replace('/umd/', '/esm/').replace('\\umd\\', '\\esm\\'); + return { path: pathEsm }; + }); + build.onResolve({ filter: /^vscode-uri$/ }, args => { + const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] }); + // v3 + let pathEsm = pathUmdMay.replace('/umd/index.js', '/esm/index.mjs').replace('\\umd\\index.js', '\\esm\\index.mjs'); + if (pathEsm !== pathUmdMay && fs.existsSync(pathEsm)) { + return { path: pathEsm }; + } + // v2 + pathEsm = pathUmdMay.replace('/umd/', '/esm/').replace('\\umd\\', '\\esm\\'); + return { path: pathEsm }; + }); + }, + }, + { + name: 'resolve-share-module', + setup(build) { + build.onResolve({ filter: /^@vue\/language-core$/ }, () => { + return { + path: 'vue-language-core-pack', + external: true, + }; + }); }, }, - require('esbuild-plugin-copy').copy({ - resolveFrom: 'cwd', - assets: { - from: ['./node_modules/@vue/language-core/schemas/**/*'], - to: ['./dist/schemas'], + { + name: 'schemas', + setup(build) { + build.onEnd(() => { + if (!fs.existsSync(path.resolve(__dirname, '../dist/schemas'))) { + fs.mkdirSync(path.resolve(__dirname, '../dist/schemas')); + } + fs.cpSync( + path.resolve(__dirname, '../node_modules/@vue/language-core/schemas/vue-tsconfig.schema.json'), + path.resolve(__dirname, '../dist/schemas/vue-tsconfig.schema.json'), + ); + }); }, - // @ts-expect-error - keepStructure: true, - }), + }, { name: 'meta', setup(build) { @@ -61,35 +90,4 @@ require('esbuild').context({ await ctx.dispose(); console.log('finished.'); } -}) - -require('esbuild').context({ - entryPoints: ['./node_modules/@vue/typescript-plugin/index.js'], - bundle: true, - outfile: './node_modules/typescript-vue-plugin-bundle/index.js', - external: ['vscode'], - format: 'cjs', - platform: 'node', - tsconfig: './tsconfig.json', - minify: process.argv.includes('--minify'), - plugins: [{ - name: 'umd2esm', - setup(build) { - build.onResolve({ filter: /^(vscode-.*-languageservice|jsonc-parser)/ }, args => { - const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] }) - const pathEsm = pathUmdMay.replace('/umd/', '/esm/') - return { path: pathEsm } - }) - }, - }], -}).then(async ctx => { - console.log('building...'); - if (process.argv.includes('--watch')) { - await ctx.watch(); - console.log('watching...'); - } else { - await ctx.rebuild(); - await ctx.dispose(); - console.log('finished.'); - } -}) +}); diff --git a/extensions/vscode/src/compatibility.ts b/extensions/vscode/src/compatibility.ts index 6bdecb770c..45aa9dd1e9 100644 --- a/extensions/vscode/src/compatibility.ts +++ b/extensions/vscode/src/compatibility.ts @@ -1,6 +1,29 @@ -import * as vscode from 'vscode'; -import * as semver from 'semver'; import { computed, useAllExtensions } from 'reactive-vscode'; +import * as semver from 'semver'; +import * as vscode from 'vscode'; +import { config } from './config'; + +const defaultCompatibleExtensions = new Set([ + 'astro-build.astro-vscode', + 'bierner.lit-html', + 'Divlo.vscode-styled-jsx-languageserver', + 'GitHub.copilot-chat', + 'ije.esm-vscode', + 'jenkey2011.string-highlight', + 'johnsoncodehk.vscode-tsslint', + 'kimuson.ts-type-expand', + 'miaonster.vscode-tsx-arrow-definition', + 'ms-dynamics-smb.al', + 'mxsdev.typescript-explorer', + 'nrwl.angular-console', + 'p42ai.refactor', + 'runem.lit-plugin', + 'ShenQingchuan.vue-vine-extension', + 'styled-components.vscode-styled-components', + 'unifiedjs.vscode-mdx', + 'VisualStudioExptTeam.vscodeintellicode', + 'Vue.volar', +]); const extensions = useAllExtensions(); @@ -18,24 +41,8 @@ export const unknownExtensions = computed(() => { function isExtensionCompatibleWithHybridMode(extension: vscode.Extension) { if ( - extension.id === 'Vue.volar' - || extension.id === 'unifiedjs.vscode-mdx' - || extension.id === 'astro-build.astro-vscode' - || extension.id === 'ije.esm-vscode' - || extension.id === 'johnsoncodehk.vscode-tsslint' - || extension.id === 'VisualStudioExptTeam.vscodeintellicode' - || extension.id === 'bierner.lit-html' - || extension.id === 'jenkey2011.string-highlight' - || extension.id === 'mxsdev.typescript-explorer' - || extension.id === 'miaonster.vscode-tsx-arrow-definition' - || extension.id === 'runem.lit-plugin' - || extension.id === 'kimuson.ts-type-expand' - || extension.id === 'p42ai.refactor' - || extension.id === 'styled-components.vscode-styled-components' - || extension.id === 'Divlo.vscode-styled-jsx-languageserver' - || extension.id === 'nrwl.angular-console' - || extension.id === 'ShenQingchuan.vue-vine-extension' - || extension.id === 'ms-dynamics-smb.al' + defaultCompatibleExtensions.has(extension.id) || + config.server.compatibleExtensions.includes(extension.id) ) { return true; } diff --git a/extensions/vscode/src/features/doctor.ts b/extensions/vscode/src/features/doctor.ts index b6281c5a07..db637d9dd8 100644 --- a/extensions/vscode/src/features/doctor.ts +++ b/extensions/vscode/src/features/doctor.ts @@ -40,11 +40,11 @@ export async function activate(client: BaseLanguageClient) { scheme, { onDidChange: docChangeEvent.event, - async provideTextDocumentContent(doctorUri: vscode.Uri): Promise { + async provideTextDocumentContent(doctorUri: vscode.Uri) { const fileUri = doctorUri.with({ scheme: 'file', - path: doctorUri.path.substring(0, doctorUri.path.length - '/Doctor.md'.length), + path: doctorUri.path.slice(0, -'/Doctor.md'.length), }); const problems = await getProblems(fileUri); @@ -157,12 +157,12 @@ export async function activate(client: BaseLanguageClient) { '', '- package.json', '```json', - JSON.stringify({ devDependencies: { "@vue/language-plugin-pug": "latest" } }, undefined, 2), + JSON.stringify({ devDependencies: { '@vue/language-plugin-pug': 'latest' } }, undefined, 2), '```', '', '- tsconfig.json / jsconfig.json', '```jsonc', - JSON.stringify({ vueCompilerOptions: { plugins: ["@vue/language-plugin-pug"] } }, undefined, 2), + JSON.stringify({ vueCompilerOptions: { plugins: ['@vue/language-plugin-pug'] } }, undefined, 2), '```', ].join('\n'), }); @@ -260,12 +260,12 @@ export async function activate(client: BaseLanguageClient) { } } -function getPackageJsonOfWorkspacePackage(folder: string, pkg: string): { path: string, json: { version: string; }; } | undefined { +function getPackageJsonOfWorkspacePackage(folder: string, pkg: string) { try { const path = require.resolve(pkg + '/package.json', { paths: [folder] }); return { path, - json: require(path), + json: require(path) as { version: string }, }; } catch { } } diff --git a/extensions/vscode/src/hybridMode.ts b/extensions/vscode/src/hybridMode.ts index 425a896e9f..606b8b2775 100644 --- a/extensions/vscode/src/hybridMode.ts +++ b/extensions/vscode/src/hybridMode.ts @@ -1,38 +1,58 @@ -import * as path from 'node:path'; import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { computed, executeCommand, useAllExtensions, useVscodeContext, watchEffect } from 'reactive-vscode'; import * as semver from 'semver'; import * as vscode from 'vscode'; -import { computed, executeCommand, ref, useAllExtensions, useVscodeContext, watchEffect } from "reactive-vscode"; import { incompatibleExtensions, unknownExtensions } from './compatibility'; import { config } from './config'; const extensions = useAllExtensions(); -export const enabledHybridMode = ref(true); +export const enabledHybridMode = computed(() => { + if (config.server.hybridMode === 'typeScriptPluginOnly') { + return false; + } + else if (config.server.hybridMode === 'auto') { + if ( + incompatibleExtensions.value.length || + unknownExtensions.value.length + ) { + return false; + } + else if ( + (vscodeTsdkVersion.value && !semver.gte(vscodeTsdkVersion.value, '5.3.0')) || + (workspaceTsdkVersion.value && !semver.gte(workspaceTsdkVersion.value, '5.3.0')) + ) { + return false; + } + return true; + } + return config.server.hybridMode; +}) export const enabledTypeScriptPlugin = computed(() => { return ( enabledHybridMode.value || - config.server.hybridMode === "typeScriptPluginOnly" + config.server.hybridMode === 'typeScriptPluginOnly' ); }); const vscodeTsdkVersion = computed(() => { const nightly = extensions.value.find( - ({ id }) => id === "ms-vscode.vscode-typescript-next" + ({ id }) => id === 'ms-vscode.vscode-typescript-next' ); if (nightly) { const libPath = path.join( - nightly.extensionPath.replace(/\\/g, "/"), - "node_modules/typescript/lib" + nightly.extensionPath.replace(/\\/g, '/'), + 'node_modules/typescript/lib' ); return getTsVersion(libPath); } if (vscode.env.appRoot) { const libPath = path.join( - vscode.env.appRoot.replace(/\\/g, "/"), - "extensions/node_modules/typescript/lib" + vscode.env.appRoot.replace(/\\/g, '/'), + 'extensions/node_modules/typescript/lib' ); return getTsVersion(libPath); } @@ -40,127 +60,111 @@ const vscodeTsdkVersion = computed(() => { const workspaceTsdkVersion = computed(() => { const libPath = vscode.workspace - .getConfiguration("typescript") - .get("tsdk") - ?.replace(/\\/g, "/"); + .getConfiguration('typescript') + .get('tsdk') + ?.replace(/\\/g, '/'); if (libPath) { return getTsVersion(libPath); } }); export function useHybridModeTips() { - useVscodeContext("vueHybridMode", enabledHybridMode); + useVscodeContext('vueHybridMode', enabledHybridMode); watchEffect(() => { - switch (config.server.hybridMode) { - case "typeScriptPluginOnly": { - enabledHybridMode.value = false; - break; - } - case "auto": { - if ( - incompatibleExtensions.value.length || - unknownExtensions.value.length - ) { - vscode.window - .showInformationMessage( - `Hybrid Mode is disabled automatically because there is a potentially incompatible ${[ - ...incompatibleExtensions.value, - ...unknownExtensions.value, - ].join(", ")} TypeScript plugin installed.`, - "Open Settings", - "Report a false positive" - ) - .then(value => { - if (value === "Open Settings") { - executeCommand( - "workbench.action.openSettings", - "vue.server.hybridMode" - ); - } - else if (value == "Report a false positive") { - vscode.env.openExternal( - vscode.Uri.parse( - "https://github.com/vuejs/language-tools/pull/4206" - ) - ); - } - }); - enabledHybridMode.value = false; - } - else if ( - (vscodeTsdkVersion.value && !semver.gte(vscodeTsdkVersion.value, "5.3.0")) || - (workspaceTsdkVersion.value && !semver.gte(workspaceTsdkVersion.value, "5.3.0")) - ) { - let msg = `Hybrid Mode is disabled automatically because TSDK >= 5.3.0 is required (VSCode TSDK: ${vscodeTsdkVersion.value}`; - if (workspaceTsdkVersion.value) { - msg += `, Workspace TSDK: ${workspaceTsdkVersion.value}`; - } - msg += `).`; - vscode.window - .showInformationMessage(msg, "Open Settings") - .then(value => { - if (value === "Open Settings") { - executeCommand( - "workbench.action.openSettings", - "vue.server.hybridMode" - ); - } - }); - enabledHybridMode.value = false; - } else { - enabledHybridMode.value = true; - } - break; + if (config.server.hybridMode === 'auto') { + if ( + incompatibleExtensions.value.length || + unknownExtensions.value.length + ) { + vscode.window + .showInformationMessage( + `Hybrid Mode is disabled automatically because there is a potentially incompatible ${[ + ...incompatibleExtensions.value, + ...unknownExtensions.value, + ].join(', ')} TypeScript plugin installed.`, + 'Open Settings', + 'Report a false positive' + ) + .then(value => { + if (value === 'Open Settings') { + executeCommand( + 'workbench.action.openSettings', + 'vue.server.hybridMode' + ); + } + else if (value == 'Report a false positive') { + vscode.env.openExternal( + vscode.Uri.parse( + 'https://github.com/vuejs/language-tools/pull/4206' + ) + ); + } + }); } - default: { - if ( - config.server.hybridMode && - incompatibleExtensions.value.length - ) { - vscode.window - .showWarningMessage( - `You have explicitly enabled Hybrid Mode, but you have installed known incompatible extensions: ${incompatibleExtensions.value.join( - ", " - )}. You may want to change vue.server.hybridMode to "auto" to avoid compatibility issues.`, - "Open Settings", - "Report a false positive" - ) - .then(value => { - if (value === "Open Settings") { - executeCommand( - "workbench.action.openSettings", - "vue.server.hybridMode" - ); - } else if (value == "Report a false positive") { - vscode.env.openExternal( - vscode.Uri.parse( - "https://github.com/vuejs/language-tools/pull/4206" - ) - ); - } - }); + else if ( + (vscodeTsdkVersion.value && !semver.gte(vscodeTsdkVersion.value, '5.3.0')) || + (workspaceTsdkVersion.value && !semver.gte(workspaceTsdkVersion.value, '5.3.0')) + ) { + let msg = `Hybrid Mode is disabled automatically because TSDK >= 5.3.0 is required (VSCode TSDK: ${vscodeTsdkVersion.value}`; + if (workspaceTsdkVersion.value) { + msg += `, Workspace TSDK: ${workspaceTsdkVersion.value}`; } - enabledHybridMode.value = config.server.hybridMode; + msg += `).`; + vscode.window + .showInformationMessage(msg, 'Open Settings') + .then(value => { + if (value === 'Open Settings') { + executeCommand( + 'workbench.action.openSettings', + 'vue.server.hybridMode' + ); + } + }); } } + else if (config.server.hybridMode && incompatibleExtensions.value.length) { + vscode.window + .showWarningMessage( + `You have explicitly enabled Hybrid Mode, but you have installed known incompatible extensions: ${incompatibleExtensions.value.join( + ', ' + )}. You may want to change vue.server.hybridMode to "auto" to avoid compatibility issues.`, + 'Open Settings', + 'Report a false positive' + ) + .then(value => { + if (value === 'Open Settings') { + executeCommand( + 'workbench.action.openSettings', + 'vue.server.hybridMode' + ); + } + else if (value == 'Report a false positive') { + vscode.env.openExternal( + vscode.Uri.parse( + 'https://github.com/vuejs/language-tools/pull/4206' + ) + ); + } + }); + } }); } export function useHybridModeStatusItem() { const item = vscode.languages.createLanguageStatusItem( - "vue-hybrid-mode", + 'vue-hybrid-mode', config.server.includeLanguages ); - item.text = "Hybrid Mode"; + item.text = 'Hybrid Mode'; item.detail = - (enabledHybridMode.value ? "Enabled" : "Disabled") + - (config.server.hybridMode === "auto" ? " (Auto)" : ""); + (enabledHybridMode.value ? 'Enabled' : 'Disabled') + + (config.server.hybridMode === 'auto' ? ' (Auto)' : ''); item.command = { - title: "Open Setting", - command: "workbench.action.openSettings", - arguments: ["vue.server.hybridMode"], + title: 'Open Setting', + command: 'workbench.action.openSettings', + arguments: ['vue.server.hybridMode'], }; if (!enabledHybridMode.value) { @@ -170,11 +174,11 @@ export function useHybridModeStatusItem() { function getTsVersion(libPath: string) { try { - const p = libPath.toString().split("/"); + const p = libPath.toString().split('/'); const p2 = p.slice(0, -1); - const modulePath = p2.join("/"); - const filePath = modulePath + "/package.json"; - const contents = fs.readFileSync(filePath, "utf-8"); + const modulePath = p2.join('/'); + const filePath = modulePath + '/package.json'; + const contents = fs.readFileSync(filePath, 'utf-8'); if (contents === undefined) { return; @@ -183,7 +187,8 @@ function getTsVersion(libPath: string) { let desc: any = null; try { desc = JSON.parse(contents); - } catch (err) { + } + catch (err) { return; } if (!desc || !desc.version) { diff --git a/extensions/vscode/src/insiders.ts b/extensions/vscode/src/insiders.ts index a0ccd80075..b6b1159ed0 100644 --- a/extensions/vscode/src/insiders.ts +++ b/extensions/vscode/src/insiders.ts @@ -1,33 +1,52 @@ -import { quickPick } from "@volar/vscode/lib/common"; +import { quickPick } from '@volar/vscode/lib/common'; import { executeCommand, useCommand } from 'reactive-vscode'; import * as vscode from 'vscode'; export function useInsidersStatusItem(context: vscode.ExtensionContext) { - const item = vscode.languages.createLanguageStatusItem("vue-insider", "vue"); - item.text = "Checking for Updates..."; - item.busy = true; - let succeed = false; + const item = vscode.languages.createLanguageStatusItem('vue-insider', 'vue'); + item.command = { + title: 'Fetch Versions', + command: 'vue-insiders.fetch', + }; + let status: 'idle' | 'pending' | 'success' = 'idle'; + + useCommand('vue-insiders.fetch', () => { + if (status === 'idle') { + fetchJson(); + } + }); fetchJson(); async function fetchJson() { + item.busy = true; + item.text = 'Checking for Updates...'; + item.severity = vscode.LanguageStatusSeverity.Warning; + status = 'pending'; + for (const url of [ - "https://raw.githubusercontent.com/vuejs/language-tools/HEAD/insiders.json", - "https://cdn.jsdelivr.net/gh/vuejs/language-tools/insiders.json", + 'https://raw.githubusercontent.com/vuejs/language-tools/HEAD/insiders.json', + 'https://cdn.jsdelivr.net/gh/vuejs/language-tools/insiders.json', ]) { try { - const res = await fetch(url); + const controller = new AbortController(); + setTimeout(() => controller.abort(), 15000); + + const res = await fetch(url, { + signal: controller.signal, + }); onJson(await res.json() as any); - succeed = true; + status = 'success'; break; } catch { }; } item.busy = false; - if (!succeed) { - item.text = "Failed to Fetch Versions"; + if (status !== 'success') { + item.text = 'Failed to Fetch Versions'; item.severity = vscode.LanguageStatusSeverity.Error; + status = 'idle'; } } @@ -44,40 +63,40 @@ export function useInsidersStatusItem(context: vscode.ExtensionContext) { }) { item.detail = undefined; item.command = { - title: "Select Version", - command: "vue-insiders.update", + title: 'Select Version', + command: 'vue-insiders.update', }; if ( json.versions.some( version => version.version === context.extension.packageJSON.version ) ) { - item.text = "🚀 Insiders Edition"; + item.text = '🚀 Insiders Edition'; item.severity = vscode.LanguageStatusSeverity.Information; if (context.extension.packageJSON.version !== json.latest) { - item.detail = "New Version Available!"; + item.detail = 'New Version Available!'; item.severity = vscode.LanguageStatusSeverity.Warning; vscode.window - .showInformationMessage("New Insiders Version Available!", "Download") + .showInformationMessage('New Insiders Version Available!', 'Download') .then(download => { if (download) { - executeCommand("vue-insiders.update"); + executeCommand('vue-insiders.update'); } }); } } else { - item.text = "✨ Get Insiders Edition"; + item.text = '✨ Get Insiders Edition'; item.severity = vscode.LanguageStatusSeverity.Warning; } - useCommand("vue-insiders.update", async () => { + useCommand('vue-insiders.update', async () => { const quickPickItems: { [version: string]: vscode.QuickPickItem; } = {}; for (const { version, date } of json.versions) { let description = date; if (context.extension.packageJSON.version === version) { - description += " (current)"; + description += ' (current)'; } quickPickItems[version] = { label: version, @@ -88,31 +107,34 @@ export function useInsidersStatusItem(context: vscode.ExtensionContext) { quickPickItems, { learnMore: { - label: "Learn more about Insiders Edition", + label: 'Learn more about Insiders Edition', }, joinViaGitHub: { - label: "Join via GitHub Sponsors", + label: 'Join via GitHub Sponsors', }, joinViaAFDIAN: { - label: "Join via AFDIAN (爱发电)", + label: 'Join via AFDIAN (爱发电)', }, }, ]); - if (version === "learnMore") { + if (version === 'learnMore') { vscode.env.openExternal( vscode.Uri.parse( - "https://github.com/vuejs/language-tools/wiki/Get-Insiders-Edition" + 'https://github.com/vuejs/language-tools/wiki/Get-Insiders-Edition' ) ); - } else if (version === "joinViaGitHub") { + } + else if (version === 'joinViaGitHub') { vscode.env.openExternal( - vscode.Uri.parse("https://github.com/sponsors/johnsoncodehk") + vscode.Uri.parse('https://github.com/sponsors/johnsoncodehk') ); - } else if (version === "joinViaAFDIAN") { + } + else if (version === 'joinViaAFDIAN') { vscode.env.openExternal( - vscode.Uri.parse("https://afdian.net/a/johnsoncodehk") + vscode.Uri.parse('https://afdian.net/a/johnsoncodehk') ); - } else { + } + else { const downloads = json.versions.find( v => v.version === version )?.downloads; @@ -120,42 +142,45 @@ export function useInsidersStatusItem(context: vscode.ExtensionContext) { const quickPickItems: { [key: string]: vscode.QuickPickItem; } = { GitHub: { label: `${version} - GitHub Releases`, - description: "Access via GitHub Sponsors", + description: 'Access via GitHub Sponsors', detail: downloads.GitHub, }, AFDIAN: { label: `${version} - Insiders 电圈`, - description: "Access via AFDIAN (爱发电)", + description: 'Access via AFDIAN (爱发电)', detail: downloads.AFDIAN, }, }; const otherItems: { [key: string]: vscode.QuickPickItem; } = { learnMore: { - label: "Learn more about Insiders Edition", + label: 'Learn more about Insiders Edition', }, joinViaGitHub: { - label: "Join via GitHub Sponsors", + label: 'Join via GitHub Sponsors', }, joinViaAFDIAN: { - label: "Join via AFDIAN (爱发电)", + label: 'Join via AFDIAN (爱发电)', }, }; const option = await quickPick([quickPickItems, otherItems]); - if (option === "learnMore") { + if (option === 'learnMore') { vscode.env.openExternal( vscode.Uri.parse( - "https://github.com/vuejs/language-tools/wiki/Get-Insiders-Edition" + 'https://github.com/vuejs/language-tools/wiki/Get-Insiders-Edition' ) ); - } else if (option === "joinViaGitHub") { + } + else if (option === 'joinViaGitHub') { vscode.env.openExternal( - vscode.Uri.parse("https://github.com/sponsors/johnsoncodehk") + vscode.Uri.parse('https://github.com/sponsors/johnsoncodehk') ); - } else if (option === "joinViaAFDIAN") { + } + else if (option === 'joinViaAFDIAN') { vscode.env.openExternal( - vscode.Uri.parse("https://afdian.net/a/johnsoncodehk") + vscode.Uri.parse('https://afdian.net/a/johnsoncodehk') ); - } else if (option) { + } + else if (option) { vscode.env.openExternal( vscode.Uri.parse(downloads[option as keyof typeof downloads]) ); diff --git a/extensions/vscode/src/languageClient.ts b/extensions/vscode/src/languageClient.ts index 991dec6b8d..01b2c7ff16 100644 --- a/extensions/vscode/src/languageClient.ts +++ b/extensions/vscode/src/languageClient.ts @@ -4,9 +4,9 @@ import { executeCommand, nextTick, useActiveTextEditor, - useVisibleTextEditors, - useOutputChannel, useCommand, + useOutputChannel, + useVisibleTextEditors, useVscodeContext, watch, } from 'reactive-vscode'; @@ -50,7 +50,7 @@ export function activate( }); } -export function deactivate(): Thenable | undefined { +export function deactivate() { return client?.stop(); } @@ -76,7 +76,8 @@ async function activateLc( requestReloadVscode( `Please reload VSCode to ${newValues[0] ? 'enable' : 'disable'} Hybrid Mode.` ); - } else if (newValues[1] !== oldValues[1]) { + } + else if (newValues[1] !== oldValues[1]) { requestReloadVscode( `Please reload VSCode to ${newValues[1] ? 'enable' : 'disable'} Vue TypeScript Plugin.` ); diff --git a/extensions/vscode/src/middleware.ts b/extensions/vscode/src/middleware.ts index d4b22781cb..d7a8d83ddc 100644 --- a/extensions/vscode/src/middleware.ts +++ b/extensions/vscode/src/middleware.ts @@ -1,8 +1,8 @@ +import * as lsp from '@volar/vscode'; import { AttrNameCasing, TagNameCasing } from '@vue/language-server/lib/types'; import * as vscode from 'vscode'; -import * as lsp from '@volar/vscode'; -import { attrNameCasings, tagNameCasings } from './features/nameCasing'; import { config } from './config'; +import { attrNameCasings, tagNameCasings } from './features/nameCasing'; export const middleware: lsp.Middleware = { ...lsp.middleware, diff --git a/extensions/vscode/src/nodeClientMain.ts b/extensions/vscode/src/nodeClientMain.ts index e10985fe8b..a828f15028 100644 --- a/extensions/vscode/src/nodeClientMain.ts +++ b/extensions/vscode/src/nodeClientMain.ts @@ -1,12 +1,12 @@ import { createLabsInfo } from '@volar/vscode'; -import * as protocol from '@vue/language-server/protocol'; -import * as fs from 'fs'; -import * as vscode from 'vscode'; import * as lsp from '@volar/vscode/node'; +import * as protocol from '@vue/language-server/protocol'; +import * as fs from 'node:fs'; import { defineExtension, executeCommand, extensionContext, onDeactivate } from 'reactive-vscode'; +import * as vscode from 'vscode'; +import { config } from './config'; import { enabledHybridMode, enabledTypeScriptPlugin } from './hybridMode'; import { activate as activateLanguageClient, deactivate as deactivateLanguageClient } from './languageClient'; -import { config } from './config'; import { middleware } from './middleware'; export const { activate, deactivate } = defineExtension(async () => { @@ -150,15 +150,16 @@ try { if (!enabledTypeScriptPlugin.value) { text = text.replace( 'for(const e of n.contributes.typescriptServerPlugins', - s => s + `.filter(p=>p.name!=='typescript-vue-plugin-bundle')` + s => s + `.filter(p=>p.name!=='vue-typescript-plugin-pack')` ); - } else if (enabledHybridMode.value) { + } + else if (enabledHybridMode.value) { // patch readPlugins text = text.replace( 'languages:Array.isArray(e.languages)', [ 'languages:', - `e.name==='typescript-vue-plugin-bundle'?[${config.server.includeLanguages + `e.name==='vue-typescript-plugin-pack'?[${config.server.includeLanguages .map(lang => `'${lang}'`) .join(',')}]`, ':Array.isArray(e.languages)' diff --git a/extensions/vscode/syntaxes/vue.tmLanguage.json b/extensions/vscode/syntaxes/vue.tmLanguage.json index 71109bc144..05d00ef84d 100644 --- a/extensions/vscode/syntaxes/vue.tmLanguage.json +++ b/extensions/vscode/syntaxes/vue.tmLanguage.json @@ -3,6 +3,9 @@ "name": "Vue", "scopeName": "source.vue", "patterns": [ + { + "include": "#vue-comments" + }, { "include": "text.html.basic#comment" }, @@ -1278,6 +1281,39 @@ ] } ] + }, + "vue-comments": { + "patterns": [ + { + "include": "#vue-comments-key-value" + } + ] + }, + "vue-comments-key-value": { + "begin": "()", + "endCaptures": { + "1": { + "name": "punctuation.definition.comment.vue" + } + }, + "name": "comment.block.vue", + "patterns": [ + { + "include": "source.json#value" + } + ] } } } \ No newline at end of file diff --git a/extensions/vscode/tests/grammar.spec.ts b/extensions/vscode/tests/grammar.spec.ts index 4790ba6431..e88e4a2659 100644 --- a/extensions/vscode/tests/grammar.spec.ts +++ b/extensions/vscode/tests/grammar.spec.ts @@ -1,5 +1,5 @@ -import * as path from 'path'; -import * as fs from 'fs'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { describe, expect, it } from 'vitest'; import { createGrammarSnapshot } from 'vscode-tmlanguage-snapshot'; diff --git a/insiders.json b/insiders.json index edc23dea70..697771f184 100644 --- a/insiders.json +++ b/insiders.json @@ -1,6 +1,22 @@ { - "latest": "2.1.9", + "latest": "2.2.1", "versions": [ + { + "version": "2.2.1", + "date": "2024-12-24", + "downloads": { + "GitHub": "https://github.com/volarjs/insiders/releases/tag/v2.2.1", + "AFDIAN": "https://afdian.com/p/0b679fe4c16a11ef98065254001e7c00" + } + }, + { + "version": "2.1.11", + "date": "2024-10-31", + "downloads": { + "GitHub": "https://github.com/volarjs/insiders/releases/tag/v2.1.11", + "AFDIAN": "https://afdian.com/p/c2e9365896f611ef9da75254001e7c00" + } + }, { "version": "2.1.9", "date": "2024-10-26", diff --git a/lerna.json b/lerna.json index c3df6eddb5..584f6d7318 100644 --- a/lerna.json +++ b/lerna.json @@ -6,5 +6,5 @@ "packages/*", "test-workspace" ], - "version": "2.1.10" + "version": "2.2.0" } diff --git a/package.json b/package.json index 4eddc3c5ed..b761c8a242 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "@tsslint/cli": "latest", "@tsslint/config": "latest", "typescript": "latest", - "vite": "latest", "vitest": "latest" } } diff --git a/packages/component-meta/index.ts b/packages/component-meta/index.ts index f38e447dfb..e9970a2e25 100644 --- a/packages/component-meta/index.ts +++ b/packages/component-meta/index.ts @@ -1,5 +1,5 @@ import * as ts from 'typescript'; -import { createCheckerByJsonConfigBase, createCheckerBase } from './lib/base'; +import { createCheckerBase, createCheckerByJsonConfigBase } from './lib/base'; import type { MetaCheckerOptions } from './lib/types'; export * from './lib/types'; diff --git a/packages/component-meta/lib/base.ts b/packages/component-meta/lib/base.ts index 901dda662a..c65a7cf43d 100644 --- a/packages/component-meta/lib/base.ts +++ b/packages/component-meta/lib/base.ts @@ -141,7 +141,7 @@ export function baseCreate( const globalTypesName = `${commandLine.vueOptions.lib}_${commandLine.vueOptions.target}_${commandLine.vueOptions.strictTemplates}.d.ts`; const globalTypesContents = `// @ts-nocheck\nexport {};\n` + vue.generateGlobalTypes(commandLine.vueOptions.lib, commandLine.vueOptions.target, commandLine.vueOptions.strictTemplates); const globalTypesSnapshot: ts.IScriptSnapshot = { - getText: (start, end) => globalTypesContents.substring(start, end), + getText: (start, end) => globalTypesContents.slice(start, end), getLength: () => globalTypesContents.length, getChangeRange: () => undefined, }; @@ -219,13 +219,13 @@ export function baseCreate( return ( commandLine.vueOptions.extensions.some(ext => fileName.endsWith(ext)) ? fileName - : fileName.substring(0, fileName.lastIndexOf('.')) + : fileName.slice(0, fileName.lastIndexOf('.')) ) + '.meta.ts'; } function getMetaScriptContent(fileName: string) { let code = ` -import * as Components from '${fileName.substring(0, fileName.length - '.meta.ts'.length)}'; +import * as Components from '${fileName.slice(0, -'.meta.ts'.length)}'; export default {} as { [K in keyof typeof Components]: ComponentMeta; }; interface ComponentMeta { @@ -315,7 +315,7 @@ ${commandLine.vueOptions.target < 3 ? vue2TypeHelpersCode : typeHelpersCode} return resolveNestedProperties(prop); }) - .filter(prop => !prop.name.match(propEventRegex)); + .filter(prop => !propEventRegex.test(prop.name)); } // fill global @@ -335,7 +335,7 @@ ${commandLine.vueOptions.target < 3 ? vue2TypeHelpersCode : typeHelpersCode} ? (vueFile instanceof vue.VueVirtualCode ? readVueComponentDefaultProps(vueFile, printer, ts, commandLine.vueOptions) : {}) : {}; const tsDefaults = !vueFile ? readTsComponentDefaultProps( - componentPath.substring(componentPath.lastIndexOf('.') + 1), // ts | js | tsx | jsx + componentPath.slice(componentPath.lastIndexOf('.') + 1), // ts | js | tsx | jsx snapshot.getText(0, snapshot.getLength()), exportName, printer, @@ -666,11 +666,11 @@ function createSchemaResolvers( return type; } - function getDeclarations(declaration: ts.Declaration[]): Declaration[] { + function getDeclarations(declaration: ts.Declaration[]) { if (noDeclarations) { return []; } - return declaration.map(getDeclaration).filter(d => !!d) as Declaration[]; + return declaration.map(getDeclaration).filter(d => !!d); } function getDeclaration(declaration: ts.Declaration): Declaration | undefined { const fileName = declaration.getSourceFile().fileName; @@ -724,9 +724,9 @@ function readVueComponentDefaultProps( const descriptor = vueSourceFile._sfc; const scriptSetupRanges = descriptor.scriptSetup ? vue.parseScriptSetupRanges(ts, descriptor.scriptSetup.ast, vueCompilerOptions) : undefined; - if (descriptor.scriptSetup && scriptSetupRanges?.props.withDefaults?.arg) { + if (descriptor.scriptSetup && scriptSetupRanges?.withDefaults?.arg) { - const defaultsText = descriptor.scriptSetup.content.substring(scriptSetupRanges.props.withDefaults.arg.start, scriptSetupRanges.props.withDefaults.arg.end); + const defaultsText = descriptor.scriptSetup.content.slice(scriptSetupRanges.withDefaults.arg.start, scriptSetupRanges.withDefaults.arg.end); const ast = ts.createSourceFile('/tmp.' + descriptor.scriptSetup.lang, '(' + defaultsText + ')', ts.ScriptTarget.Latest); const obj = findObjectLiteralExpression(ast); @@ -743,8 +743,8 @@ function readVueComponentDefaultProps( } } } - } else if (descriptor.scriptSetup && scriptSetupRanges?.props.define?.arg) { - const defaultsText = descriptor.scriptSetup.content.substring(scriptSetupRanges.props.define.arg.start, scriptSetupRanges.props.define.arg.end); + } else if (descriptor.scriptSetup && scriptSetupRanges?.defineProps?.arg) { + const defaultsText = descriptor.scriptSetup.content.slice(scriptSetupRanges.defineProps.arg.start, scriptSetupRanges.defineProps.arg.end); const ast = ts.createSourceFile('/tmp.' + descriptor.scriptSetup.lang, '(' + defaultsText + ')', ts.ScriptTarget.Latest); const obj = findObjectLiteralExpression(ast); diff --git a/packages/component-meta/package.json b/packages/component-meta/package.json index cd716dd515..8030b51e6d 100644 --- a/packages/component-meta/package.json +++ b/packages/component-meta/package.json @@ -1,6 +1,6 @@ { "name": "vue-component-meta", - "version": "2.1.10", + "version": "2.2.0", "license": "MIT", "files": [ "**/*.js", @@ -13,10 +13,10 @@ "directory": "packages/component-meta" }, "dependencies": { - "@volar/typescript": "~2.4.8", - "@vue/language-core": "2.1.10", + "@volar/typescript": "~2.4.11", + "@vue/language-core": "2.2.0", "path-browserify": "^1.0.1", - "vue-component-type-helpers": "2.1.10" + "vue-component-type-helpers": "2.2.0" }, "peerDependencies": { "typescript": "*" diff --git a/packages/component-meta/tests/index.spec.ts b/packages/component-meta/tests/index.spec.ts index 4c78583a0b..1ca3323775 100644 --- a/packages/component-meta/tests/index.spec.ts +++ b/packages/component-meta/tests/index.spec.ts @@ -1,6 +1,6 @@ -import * as path from 'path'; +import * as path from 'node:path'; import { describe, expect, test } from 'vitest'; -import { createChecker, createCheckerByJson, MetaCheckerOptions, ComponentMetaChecker, TypeMeta } from '..'; +import { ComponentMetaChecker, createChecker, createCheckerByJson, MetaCheckerOptions, TypeMeta } from '..'; const worker = (checker: ComponentMetaChecker, withTsconfig: boolean) => describe(`vue-component-meta ${withTsconfig ? 'with tsconfig' : 'without tsconfig'}`, () => { diff --git a/packages/component-type-helpers/package.json b/packages/component-type-helpers/package.json index 35460627cb..42addc99f1 100644 --- a/packages/component-type-helpers/package.json +++ b/packages/component-type-helpers/package.json @@ -1,6 +1,6 @@ { "name": "vue-component-type-helpers", - "version": "2.1.10", + "version": "2.2.0", "license": "MIT", "files": [ "**/*.js", diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index 9908372d43..ac0a213671 100644 --- a/packages/language-core/lib/codegen/globalTypes.ts +++ b/packages/language-core/lib/codegen/globalTypes.ts @@ -15,6 +15,7 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates const __VLS_intrinsicElements: __VLS_IntrinsicElements; const __VLS_directiveBindingRestFields: { instance: null, oldValue: null, modifiers: any, dir: any }; const __VLS_unref: typeof import('${lib}').unref; + const __VLS_placeholder: any; const __VLS_nativeElements = { ...{} as SVGElementTagNameMap, @@ -47,7 +48,7 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates N1 extends keyof __VLS_GlobalComponents ? N1 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N1] } : N2 extends keyof __VLS_GlobalComponents ? N2 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N2] } : N3 extends keyof __VLS_GlobalComponents ? N3 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N3] } : - ${strictTemplates ? '{}' : '{ [K in N0]: unknown }'} + ${strictTemplates ? '{}' : '{ [K in N0]: unknown }'}; type __VLS_FunctionalComponentProps = '__ctx' extends keyof __VLS_PickNotAny ? K extends { __ctx?: { props?: infer P } } ? NonNullable

: never : T extends (props: infer P, ...args: any) => any ? P : @@ -59,6 +60,15 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates : true : false : false; + type __VLS_NormalizeComponentEvent = ( + __VLS_IsFunction extends true + ? Props + : __VLS_IsFunction extends true + ? { [K in onEvent]?: Events[Event] } + : __VLS_IsFunction extends true + ? { [K in onEvent]?: Events[CamelizedEvent] } + : Props + )${strictTemplates ? '' : ' & Record'}; // fix https://github.com/vuejs/language-tools/issues/926 type __VLS_UnionToIntersection = (U extends unknown ? (arg: U) => unknown : never) extends ((arg: infer P) => unknown) ? P : never; type __VLS_OverloadUnionInner = U & T extends (...args: infer A) => infer R @@ -123,7 +133,6 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates : __VLS_unknownDirective; function __VLS_withScope(ctx: T, scope: K): ctx is T & K; function __VLS_makeOptional(t: T): { [K in keyof T]?: T[K] }; - function __VLS_nonNullable(t: T): T extends null | undefined ? never : T; function __VLS_asFunctionalComponent any ? InstanceType : unknown>(t: T, instance?: K): T extends new (...args: any) => any ? (props: ${fnPropsType}, ctx?: any) => __VLS_Element & { __ctx?: { diff --git a/packages/language-core/lib/codegen/localTypes.ts b/packages/language-core/lib/codegen/localTypes.ts index d4939c3bf6..61357423da 100644 --- a/packages/language-core/lib/codegen/localTypes.ts +++ b/packages/language-core/lib/codegen/localTypes.ts @@ -1,7 +1,7 @@ import type * as ts from 'typescript'; import { VueCompilerOptions } from '../types'; import { getSlotsPropertyName } from '../utils/shared'; -import { endOfLine } from './common'; +import { endOfLine } from './utils'; export function getLocalTypesGenerator(compilerOptions: ts.CompilerOptions, vueCompilerOptions: VueCompilerOptions) { const used = new Set(); diff --git a/packages/language-core/lib/codegen/script/component.ts b/packages/language-core/lib/codegen/script/component.ts index 0d2c89d20a..04707b9967 100644 --- a/packages/language-core/lib/codegen/script/component.ts +++ b/packages/language-core/lib/codegen/script/component.ts @@ -1,6 +1,6 @@ import type { ScriptSetupRanges } from '../../parsers/scriptSetupRanges'; import type { Code, Sfc } from '../../types'; -import { endOfLine, generateSfcBlockSection, newLine } from '../common'; +import { endOfLine, generateSfcBlockSection, newLine } from '../utils'; import type { ScriptCodegenContext } from './context'; import { ScriptCodegenOptions, codeFeatures } from './index'; @@ -24,7 +24,7 @@ export function* generateComponent( if (ctx.bypassDefineComponent) { yield* generateComponentSetupReturns(scriptSetupRanges); } - if (scriptSetupRanges.expose.define) { + if (scriptSetupRanges.defineExpose) { yield `...__VLS_exposed,${newLine}`; } yield `}${endOfLine}`; @@ -40,7 +40,7 @@ export function* generateComponent( const { args } = options.scriptRanges.exportDefault; yield generateSfcBlockSection(options.sfc.script, args.start + 1, args.end - 1, codeFeatures.all); } - if (options.vueCompilerOptions.target >= 3.5 && scriptSetupRanges.templateRefs.length) { + if (options.vueCompilerOptions.target >= 3.5 && options.templateCodegen?.templateRefs.size) { yield `__typeRefs: {} as __VLS_TemplateResult['refs'],${newLine}`; } if (options.vueCompilerOptions.target >= 3.5 && options.templateCodegen?.singleRootElType) { @@ -51,14 +51,14 @@ export function* generateComponent( export function* generateComponentSetupReturns(scriptSetupRanges: ScriptSetupRanges): Generator { // fill $props - if (scriptSetupRanges.props.define) { + if (scriptSetupRanges.defineProps) { // NOTE: defineProps is inaccurate for $props - yield `$props: __VLS_makeOptional(${scriptSetupRanges.props.name ?? `__VLS_props`}),${newLine}`; - yield `...${scriptSetupRanges.props.name ?? `__VLS_props`},${newLine}`; + yield `$props: __VLS_makeOptional(${scriptSetupRanges.defineProps.name ?? `__VLS_props`}),${newLine}`; + yield `...${scriptSetupRanges.defineProps.name ?? `__VLS_props`},${newLine}`; } // fill $emit - if (scriptSetupRanges.emits.define) { - yield `$emit: ${scriptSetupRanges.emits.name ?? '__VLS_emit'},${newLine}`; + if (scriptSetupRanges.defineEmits) { + yield `$emit: ${scriptSetupRanges.defineEmits.name ?? '__VLS_emit'},${newLine}`; } } @@ -78,10 +78,10 @@ export function* generateEmitsOption( typeOptionType: `__VLS_ModelEmit`, }); } - if (scriptSetupRanges.emits.define) { - const { typeArg, hasUnionTypeArg } = scriptSetupRanges.emits.define; + if (scriptSetupRanges.defineEmits) { + const { name, typeArg, hasUnionTypeArg } = scriptSetupRanges.defineEmits; codes.push({ - optionExp: `{} as __VLS_NormalizeEmits`, + optionExp: `{} as __VLS_NormalizeEmits`, typeOptionType: typeArg && !hasUnionTypeArg ? `__VLS_Emit` : undefined, @@ -139,15 +139,15 @@ export function* generatePropsOption( codes.push({ optionExp: [ `{} as `, - scriptSetupRanges.props.withDefaults?.arg ? `${ctx.localTypes.WithDefaults}<` : '', + scriptSetupRanges.withDefaults?.arg ? `${ctx.localTypes.WithDefaults}<` : '', `${ctx.localTypes.TypePropsToOption}<__VLS_PublicProps>`, - scriptSetupRanges.props.withDefaults?.arg ? `, typeof __VLS_withDefaultsArg>` : '', + scriptSetupRanges.withDefaults?.arg ? `, typeof __VLS_withDefaultsArg>` : '', ].join(''), typeOptionExp: `{} as __VLS_PublicProps`, }); } - if (scriptSetupRanges.props.define?.arg) { - const { arg } = scriptSetupRanges.props.define; + if (scriptSetupRanges.defineProps?.arg) { + const { arg } = scriptSetupRanges.defineProps; codes.push({ optionExp: generateSfcBlockSection(scriptSetup, arg.start, arg.end, codeFeatures.navigation), typeOptionExp: undefined, @@ -170,7 +170,7 @@ export function* generatePropsOption( } const useTypeOption = options.vueCompilerOptions.target >= 3.5 && codes.every(code => code.typeOptionExp); - const useOption = !useTypeOption || scriptSetupRanges.props.withDefaults; + const useOption = !useTypeOption || scriptSetupRanges.withDefaults; if (useTypeOption) { if (codes.length === 1) { diff --git a/packages/language-core/lib/codegen/script/componentSelf.ts b/packages/language-core/lib/codegen/script/componentSelf.ts index a75cb87718..331c21d823 100644 --- a/packages/language-core/lib/codegen/script/componentSelf.ts +++ b/packages/language-core/lib/codegen/script/componentSelf.ts @@ -1,7 +1,7 @@ import * as path from 'path-browserify'; import type { Code } from '../../types'; -import { endOfLine, generateSfcBlockSection, newLine } from '../common'; import type { TemplateCodegenContext } from '../template/context'; +import { endOfLine, generateSfcBlockSection, newLine } from '../utils'; import { generateComponentSetupReturns, generateEmitsOption, generatePropsOption } from './component'; import type { ScriptCodegenContext } from './context'; import { codeFeatures, type ScriptCodegenOptions } from './index'; @@ -27,8 +27,8 @@ export function* generateComponentSelf( ? [options.sfc.script.content, options.scriptRanges.bindings] as const : ['', []] as const, ]) { - for (const expose of bindings) { - const varName = content.substring(expose.start, expose.end); + for (const { range } of bindings) { + const varName = content.slice(range.start, range.end); if (!templateUsageVars.has(varName) && !templateCodegenCtx.accessExternalVariables.has(varName)) { continue; } diff --git a/packages/language-core/lib/codegen/script/context.ts b/packages/language-core/lib/codegen/script/context.ts index a67d018de2..bb7fe16ded 100644 --- a/packages/language-core/lib/codegen/script/context.ts +++ b/packages/language-core/lib/codegen/script/context.ts @@ -21,8 +21,12 @@ export function createScriptCodegenContext(options: ScriptCodegenOptions) { scriptSetupGeneratedOffset: undefined as number | undefined, bypassDefineComponent: options.lang === 'js' || options.lang === 'jsx', bindingNames: new Set([ - ...options.scriptRanges?.bindings.map(range => options.sfc.script!.content.substring(range.start, range.end)) ?? [], - ...options.scriptSetupRanges?.bindings.map(range => options.sfc.scriptSetup!.content.substring(range.start, range.end)) ?? [], + ...options.scriptRanges?.bindings.map( + ({ range }) => options.sfc.script!.content.slice(range.start, range.end) + ) ?? [], + ...options.scriptSetupRanges?.bindings.map( + ({ range }) => options.sfc.scriptSetup!.content.slice(range.start, range.end) + ) ?? [], ]), localTypes, inlayHints, diff --git a/packages/language-core/lib/codegen/script/index.ts b/packages/language-core/lib/codegen/script/index.ts index f9865e4eaa..9c975f325d 100644 --- a/packages/language-core/lib/codegen/script/index.ts +++ b/packages/language-core/lib/codegen/script/index.ts @@ -4,9 +4,9 @@ import type * as ts from 'typescript'; import type { ScriptRanges } from '../../parsers/scriptRanges'; import type { ScriptSetupRanges } from '../../parsers/scriptSetupRanges'; import type { Code, Sfc, VueCodeInformation, VueCompilerOptions } from '../../types'; -import { endOfLine, generateSfcBlockSection, newLine } from '../common'; import { generateGlobalTypes } from '../globalTypes'; import type { TemplateCodegenContext } from '../template/context'; +import { endOfLine, generateSfcBlockSection, newLine } from '../utils'; import { generateComponentSelf } from './componentSelf'; import { createScriptCodegenContext, ScriptCodegenContext } from './context'; import { generateScriptSetup, generateScriptSetupImports } from './scriptSetup'; @@ -38,19 +38,21 @@ export const codeFeatures = { }; export interface ScriptCodegenOptions { - fileName: string; ts: typeof ts; compilerOptions: ts.CompilerOptions; vueCompilerOptions: VueCompilerOptions; sfc: Sfc; + edited: boolean; + fileName: string; lang: string; scriptRanges: ScriptRanges | undefined; scriptSetupRanges: ScriptSetupRanges | undefined; templateCodegen: TemplateCodegenContext & { codes: Code[]; } | undefined; - edited: boolean; - appendGlobalTypes: boolean; + destructuredPropNames: Set; + templateRefNames: Set; getGeneratedLength: () => number; linkedCodeMappings: Mapping[]; + appendGlobalTypes: boolean; } export function* generateScript(options: ScriptCodegenOptions): Generator { @@ -82,7 +84,6 @@ export function* generateScript(options: ScriptCodegenOptions): Generator -): Generator { - const definePropProposalA = scriptSetup.content.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition') || options.vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition'; - const definePropProposalB = scriptSetup.content.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition') || options.vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition'; - - if (definePropProposalA || definePropProposalB) { - yield `type __VLS_PropOptions = Exclude, import('${options.vueCompilerOptions.lib}').PropType>${endOfLine}`; - if (definePropProposalA) { - yield `declare function defineProp(name: string, options: ({ required: true } | { default: T }) & __VLS_PropOptions): import('${options.vueCompilerOptions.lib}').ComputedRef${endOfLine}`; - yield `declare function defineProp(name?: string, options?: __VLS_PropOptions): import('${options.vueCompilerOptions.lib}').ComputedRef${endOfLine}`; - } - if (definePropProposalB) { - yield `declare function defineProp(value: T | (() => T), required?: boolean, options?: __VLS_PropOptions): import('${options.vueCompilerOptions.lib}').ComputedRef${endOfLine}`; - yield `declare function defineProp(value: T | (() => T) | undefined, required: true, options?: __VLS_PropOptions): import('${options.vueCompilerOptions.lib}').ComputedRef${endOfLine}`; - yield `declare function defineProp(value?: T | (() => T), required?: boolean, options?: __VLS_PropOptions): import('${options.vueCompilerOptions.lib}').ComputedRef${endOfLine}`; - } - } -} diff --git a/packages/language-core/lib/codegen/script/scriptSetup.ts b/packages/language-core/lib/codegen/script/scriptSetup.ts index 699f39e287..8f357a734b 100644 --- a/packages/language-core/lib/codegen/script/scriptSetup.ts +++ b/packages/language-core/lib/codegen/script/scriptSetup.ts @@ -1,6 +1,6 @@ import type { ScriptSetupRanges } from '../../parsers/scriptSetupRanges'; import type { Code, Sfc, TextRange } from '../../types'; -import { endOfLine, generateSfcBlockSection, newLine } from '../common'; +import { combineLastMapping, endOfLine, generateSfcBlockSection, newLine } from '../utils'; import { generateComponent, generateEmitsOption } from './component'; import { generateComponentSelf } from './componentSelf'; import type { ScriptCodegenContext } from './context'; @@ -12,12 +12,11 @@ export function* generateScriptSetupImports( scriptSetupRanges: ScriptSetupRanges ): Generator { yield [ - scriptSetup.content.substring(0, Math.max(scriptSetupRanges.importSectionEndOffset, scriptSetupRanges.leadingCommentEndOffset)), + scriptSetup.content.slice(0, Math.max(scriptSetupRanges.importSectionEndOffset, scriptSetupRanges.leadingCommentEndOffset)), 'scriptSetup', 0, codeFeatures.all, ]; - yield newLine; } export function* generateScriptSetup( @@ -26,8 +25,6 @@ export function* generateScriptSetup( scriptSetup: NonNullable, scriptSetupRanges: ScriptSetupRanges ): Generator { - const definePropMirrors = new Map(); - if (scriptSetup.generic) { if (!options.scriptRanges?.exportDefault) { if (options.sfc.scriptSetup) { @@ -56,57 +53,39 @@ export function* generateScriptSetup( + ` __VLS_ctx?: ${ctx.localTypes.PrettifyLocal}>, 'attrs' | 'emit' | 'slots'>>,${newLine}` // use __VLS_Prettify for less dts code + ` __VLS_expose?: NonNullable>['expose'],${newLine}` + ` __VLS_setup = (async () => {${newLine}`; - yield* generateSetupFunction(options, ctx, scriptSetup, scriptSetupRanges, undefined, definePropMirrors); + yield* generateSetupFunction(options, ctx, scriptSetup, scriptSetupRanges, undefined); const emitTypes: string[] = []; - if (scriptSetupRanges.emits.define) { - emitTypes.push(`typeof ${scriptSetupRanges.emits.name ?? '__VLS_emit'}`); + if (scriptSetupRanges.defineEmits) { + emitTypes.push(`typeof ${scriptSetupRanges.defineEmits.name ?? '__VLS_emit'}`); } if (scriptSetupRanges.defineProp.some(p => p.isModel)) { emitTypes.push(`typeof __VLS_modelEmit`); } - yield ` return {} as {${newLine}` - + ` props: ${ctx.localTypes.PrettifyLocal} & __VLS_BuiltInPublicProps,${newLine}` - + ` expose(exposed: import('${options.vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.expose.define ? 'typeof __VLS_exposed' : '{}'}>): void,${newLine}` - + ` attrs: any,${newLine}` - + ` slots: __VLS_TemplateResult['slots'],${newLine}` - + ` emit: ${emitTypes.length ? emitTypes.join(' & ') : `{}`},${newLine}` - + ` }${endOfLine}`; - yield ` })(),${newLine}`; // __VLS_setup = (async () => { + yield `return {} as {${newLine}` + + ` props: ${ctx.localTypes.PrettifyLocal}<__VLS_OwnProps & __VLS_PublicProps & __VLS_TemplateResult['attrs']> & __VLS_BuiltInPublicProps,${newLine}` + + ` expose(exposed: import('${options.vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.defineExpose ? 'typeof __VLS_exposed' : '{}'}>): void,${newLine}` + + ` attrs: any,${newLine}` + + ` slots: __VLS_TemplateResult['slots'],${newLine}` + + ` emit: ${emitTypes.length ? emitTypes.join(' & ') : `{}`},${newLine}` + + `}${endOfLine}`; + yield `})(),${newLine}`; // __VLS_setup = (async () => { yield `) => ({} as import('${options.vueCompilerOptions.lib}').VNode & { __ctx?: Awaited }))`; } else if (!options.sfc.script) { // no script block, generate script setup code at root - yield* generateSetupFunction(options, ctx, scriptSetup, scriptSetupRanges, 'export default', definePropMirrors); + yield* generateSetupFunction(options, ctx, scriptSetup, scriptSetupRanges, 'export default'); } else { if (!options.scriptRanges?.exportDefault) { yield `export default `; } yield `await (async () => {${newLine}`; - yield* generateSetupFunction(options, ctx, scriptSetup, scriptSetupRanges, 'return', definePropMirrors); + yield* generateSetupFunction(options, ctx, scriptSetup, scriptSetupRanges, 'return'); yield `})()`; } - - if (ctx.scriptSetupGeneratedOffset !== undefined) { - for (const defineProp of scriptSetupRanges.defineProp) { - if (!defineProp.localName) { - continue; - } - const [_, localName] = getPropAndLocalName(scriptSetup, defineProp); - const propMirror = definePropMirrors.get(localName!); - if (propMirror !== undefined) { - options.linkedCodeMappings.push({ - sourceOffsets: [defineProp.localName.start + ctx.scriptSetupGeneratedOffset], - generatedOffsets: [propMirror], - lengths: [defineProp.localName.end - defineProp.localName.start], - data: undefined, - }); - } - } - } } function* generateSetupFunction( @@ -114,142 +93,180 @@ function* generateSetupFunction( ctx: ScriptCodegenContext, scriptSetup: NonNullable, scriptSetupRanges: ScriptSetupRanges, - syntax: 'return' | 'export default' | undefined, - definePropMirrors: Map + syntax: 'return' | 'export default' | undefined ): Generator { - if (options.vueCompilerOptions.target >= 3.3) { - yield `const { `; - for (const macro of Object.keys(options.vueCompilerOptions.macros)) { - if (!ctx.bindingNames.has(macro) && macro !== 'templateRef') { - yield macro + `, `; - } - } - yield `} = await import('${options.vueCompilerOptions.lib}')${endOfLine}`; - } - ctx.scriptSetupGeneratedOffset = options.getGeneratedLength() - scriptSetupRanges.importSectionEndOffset; let setupCodeModifies: [Code[], number, number][] = []; - if (scriptSetupRanges.props.define) { + if (scriptSetupRanges.defineProps) { + const { name, statement, callExp, typeArg } = scriptSetupRanges.defineProps; setupCodeModifies.push(...generateDefineWithType( scriptSetup, - scriptSetupRanges.props.name, - scriptSetupRanges.props.define, - scriptSetupRanges.props.withDefaults ?? scriptSetupRanges.props.define, + statement, + scriptSetupRanges.withDefaults?.callExp ?? callExp, + typeArg, + name, '__VLS_props', '__VLS_Props' )); } - if (scriptSetupRanges.slots.define) { - if (scriptSetupRanges.slots.isObjectBindingPattern) { - setupCodeModifies.push([ - [`__VLS_slots;\nconst __VLS_slots = `], - scriptSetupRanges.slots.define.start, - scriptSetupRanges.slots.define.start, - ]); - } else if (!scriptSetupRanges.slots.name) { - setupCodeModifies.push([[`const __VLS_slots = `], scriptSetupRanges.slots.define.start, scriptSetupRanges.slots.define.start]); - } - } - if (scriptSetupRanges.emits.define) { + if (scriptSetupRanges.defineEmits) { + const { name, statement, callExp, typeArg } = scriptSetupRanges.defineEmits; setupCodeModifies.push(...generateDefineWithType( scriptSetup, - scriptSetupRanges.emits.name, - scriptSetupRanges.emits.define, - scriptSetupRanges.emits.define, + statement, + callExp, + typeArg, + name, '__VLS_emit', '__VLS_Emit' )); } - if (scriptSetupRanges.expose.define) { - if (scriptSetupRanges.expose.define?.typeArg) { + if (scriptSetupRanges.defineSlots) { + const { name, callExp, isObjectBindingPattern } = scriptSetupRanges.defineSlots; + if (isObjectBindingPattern) { + setupCodeModifies.push([ + [`__VLS_slots;\nconst __VLS_slots = `], + callExp.start, + callExp.start, + ]); + } else if (!name) { + setupCodeModifies.push([ + [`const __VLS_slots = `], + callExp.start, + callExp.start + ]); + } + } + if (scriptSetupRanges.defineExpose) { + const { callExp, arg, typeArg } = scriptSetupRanges.defineExpose; + if (typeArg) { setupCodeModifies.push([ [ `let __VLS_exposed!: `, - generateSfcBlockSection(scriptSetup, scriptSetupRanges.expose.define.typeArg.start, scriptSetupRanges.expose.define.typeArg.end, codeFeatures.navigation), + generateSfcBlockSection(scriptSetup, typeArg.start, typeArg.end, codeFeatures.navigation), `${endOfLine}`, ], - scriptSetupRanges.expose.define.start, - scriptSetupRanges.expose.define.start, + callExp.start, + callExp.start, ]); } - else if (scriptSetupRanges.expose.define?.arg) { + else if (arg) { setupCodeModifies.push([ [ `const __VLS_exposed = `, - generateSfcBlockSection(scriptSetup, scriptSetupRanges.expose.define.arg.start, scriptSetupRanges.expose.define.arg.end, codeFeatures.navigation), + generateSfcBlockSection(scriptSetup, arg.start, arg.end, codeFeatures.navigation), `${endOfLine}`, ], - scriptSetupRanges.expose.define.start, - scriptSetupRanges.expose.define.start, + callExp.start, + callExp.start, ]); } else { setupCodeModifies.push([ [`const __VLS_exposed = {}${endOfLine}`], - scriptSetupRanges.expose.define.start, - scriptSetupRanges.expose.define.start, + callExp.start, + callExp.start, ]); } } - if (scriptSetupRanges.cssModules.length) { - for (const { define } of scriptSetupRanges.cssModules) { + // TODO: circular reference + // for (const { callExp } of scriptSetupRanges.useAttrs) { + // setupCodeModifies.push([ + // [`(`], + // callExp.start, + // callExp.start + // ], [ + // [` as __VLS_TemplateResult['attrs'] & Record)`], + // callExp.end, + // callExp.end + // ]); + // } + for (const { callExp, exp, arg } of scriptSetupRanges.useCssModule) { + setupCodeModifies.push([ + [`(`], + callExp.start, + callExp.start + ], [ + arg ? [ + ` as Omit<__VLS_StyleModules, '$style'>[`, + generateSfcBlockSection(scriptSetup, arg.start, arg.end, codeFeatures.all), + `])` + ] : [ + ` as __VLS_StyleModules[`, + ['', scriptSetup.name, exp.start, codeFeatures.verification], + `'$style'`, + ['', scriptSetup.name, exp.end, combineLastMapping], + `])` + ], + callExp.end, + callExp.end + ]); + if (arg) { setupCodeModifies.push([ - [`(`], - define.start, - define.start - ], [ - define.arg ? [ - ` as Omit<__VLS_StyleModules, '$style'>[`, - generateSfcBlockSection(scriptSetup, define.arg.start, define.arg.end, codeFeatures.all), - `])` - ] : [ - ` as __VLS_StyleModules[`, - ['', scriptSetup.name, define.exp.start, codeFeatures.verification], - `'$style'`, - ['', scriptSetup.name, define.exp.end, codeFeatures.verification], - `])` - ], - define.end, - define.end + [`(__VLS_placeholder)`], + arg.start, + arg.end ]); } } + for (const { callExp } of scriptSetupRanges.useSlots) { + setupCodeModifies.push([ + [`(`], + callExp.start, + callExp.start + ], [ + [` as __VLS_TemplateResult['slots'])`], + callExp.end, + callExp.end + ]); + } const isTs = options.lang !== 'js' && options.lang !== 'jsx'; - for (const { define } of scriptSetupRanges.templateRefs) { - if (!define.arg) { - continue; - } + for (const { callExp, exp, arg } of scriptSetupRanges.useTemplateRef) { + const templateRefType = arg + ? [ + `__VLS_TemplateResult['refs'][`, + generateSfcBlockSection(scriptSetup, arg.start, arg.end, codeFeatures.all), + `]` + ] + : [`unknown`]; if (isTs) { setupCodeModifies.push([ [ - `<__VLS_TemplateResult['refs'][`, - generateSfcBlockSection(scriptSetup, define.arg.start, define.arg.end, codeFeatures.navigation), - `], keyof __VLS_TemplateResult['refs']>` + `<`, + ...templateRefType, + `>` ], - define.exp.end, - define.exp.end + exp.end, + exp.end ]); } else { setupCodeModifies.push([ [`(`], - define.start, - define.start + callExp.start, + callExp.start ], [ [ - ` as __VLS_UseTemplateRef<__VLS_TemplateResult['refs'][`, - generateSfcBlockSection(scriptSetup, define.arg.start, define.arg.end, codeFeatures.navigation), - `]>)` + ` as __VLS_UseTemplateRef<`, + ...templateRefType, + `>)` ], - define.end, - define.end + callExp.end, + callExp.end + ]); + } + if (arg) { + setupCodeModifies.push([ + [`(__VLS_placeholder)`], + arg.start, + arg.end ]); } } setupCodeModifies = setupCodeModifies.sort((a, b) => a[1] - b[1]); - let nextStart = scriptSetupRanges.importSectionEndOffset; + let nextStart = Math.max(scriptSetupRanges.importSectionEndOffset, scriptSetupRanges.leadingCommentEndOffset); for (const [codes, start, end] of setupCodeModifies) { yield generateSfcBlockSection(scriptSetup, nextStart, start, codeFeatures.all); for (const code of codes) { @@ -260,15 +277,22 @@ function* generateSetupFunction( yield generateSfcBlockSection(scriptSetup, nextStart, scriptSetup.content.length, codeFeatures.all); yield* generateScriptSectionPartiallyEnding(scriptSetup.name, scriptSetup.content.length, '#3632/scriptSetup.vue'); + yield* generateMacros(options, ctx); + yield* generateDefineProp(options); - if (scriptSetupRanges.props.define?.typeArg && scriptSetupRanges.props.withDefaults?.arg) { + if (scriptSetupRanges.defineProps?.typeArg && scriptSetupRanges.withDefaults?.arg) { // fix https://github.com/vuejs/language-tools/issues/1187 yield `const __VLS_withDefaultsArg = (function (t: T) { return t })(`; - yield generateSfcBlockSection(scriptSetup, scriptSetupRanges.props.withDefaults.arg.start, scriptSetupRanges.props.withDefaults.arg.end, codeFeatures.navigation); + yield generateSfcBlockSection( + scriptSetup, + scriptSetupRanges.withDefaults.arg.start, + scriptSetupRanges.withDefaults.arg.end, + codeFeatures.navigation + ); yield `)${endOfLine}`; } - yield* generateComponentProps(options, ctx, scriptSetup, scriptSetupRanges, definePropMirrors); + yield* generateComponentProps(options, ctx, scriptSetup, scriptSetupRanges); yield* generateModelEmit(scriptSetup, scriptSetupRanges); yield `function __VLS_template() {${newLine}`; const templateCodegenCtx = yield* generateTemplate(options, ctx); @@ -277,7 +301,7 @@ function* generateSetupFunction( yield `type __VLS_TemplateResult = ReturnType${endOfLine}`; if (syntax) { - if (!options.vueCompilerOptions.skipTemplateCodegen && (options.templateCodegen?.hasSlot || scriptSetupRanges?.slots.define)) { + if (!options.vueCompilerOptions.skipTemplateCodegen && (options.templateCodegen?.hasSlot || scriptSetupRanges.defineSlots)) { yield `const __VLS_component = `; yield* generateComponent(options, ctx, scriptSetup, scriptSetupRanges); yield endOfLine; @@ -292,18 +316,48 @@ function* generateSetupFunction( } } +function* generateMacros( + options: ScriptCodegenOptions, + ctx: ScriptCodegenContext +): Generator { + if (options.vueCompilerOptions.target >= 3.3) { + yield `declare const { `; + for (const macro of Object.keys(options.vueCompilerOptions.macros)) { + if (!ctx.bindingNames.has(macro)) { + yield `${macro}, `; + } + } + yield `}: typeof import('${options.vueCompilerOptions.lib}')${endOfLine}`; + } +} + +function* generateDefineProp(options: ScriptCodegenOptions): Generator { + const definePropProposalA = options.vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition'; + const definePropProposalB = options.vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition'; + + if (definePropProposalA || definePropProposalB) { + yield `type __VLS_PropOptions = Exclude, import('${options.vueCompilerOptions.lib}').PropType>${endOfLine}`; + if (definePropProposalA) { + yield `declare function defineProp(name: string, options: ({ required: true } | { default: T }) & __VLS_PropOptions): import('${options.vueCompilerOptions.lib}').ComputedRef${endOfLine}`; + yield `declare function defineProp(name?: string, options?: __VLS_PropOptions): import('${options.vueCompilerOptions.lib}').ComputedRef${endOfLine}`; + } + if (definePropProposalB) { + yield `declare function defineProp(value: T | (() => T), required?: boolean, options?: __VLS_PropOptions): import('${options.vueCompilerOptions.lib}').ComputedRef${endOfLine}`; + yield `declare function defineProp(value: T | (() => T) | undefined, required: true, options?: __VLS_PropOptions): import('${options.vueCompilerOptions.lib}').ComputedRef${endOfLine}`; + yield `declare function defineProp(value?: T | (() => T), required?: boolean, options?: __VLS_PropOptions): import('${options.vueCompilerOptions.lib}').ComputedRef${endOfLine}`; + } + } +} + function* generateDefineWithType( scriptSetup: NonNullable, + statement: TextRange, + callExp: TextRange, + typeArg: TextRange | undefined, name: string | undefined, - define: { - statement: TextRange; - typeArg?: TextRange; - }, - expression: TextRange, defaultName: string, typeName: string ): Generator<[Code[], number, number]> { - const { statement, typeArg } = define; if (typeArg) { yield [[ `type ${typeName} = `, @@ -313,29 +367,29 @@ function* generateDefineWithType( yield [[typeName], typeArg.start, typeArg.end]; } if (!name) { - if (statement.start === expression.start && statement.end === expression.end) { - yield [[`const ${defaultName} = `], expression.start, expression.start]; + if (statement.start === callExp.start && statement.end === callExp.end) { + yield [[`const ${defaultName} = `], callExp.start, callExp.start]; } else if (typeArg) { yield [[ `const ${defaultName} = `, - generateSfcBlockSection(scriptSetup, expression.start, typeArg.start, codeFeatures.all) + generateSfcBlockSection(scriptSetup, callExp.start, typeArg.start, codeFeatures.all) ], statement.start, typeArg.start]; yield [[ - generateSfcBlockSection(scriptSetup, typeArg.end, expression.end, codeFeatures.all), + generateSfcBlockSection(scriptSetup, typeArg.end, callExp.end, codeFeatures.all), endOfLine, - generateSfcBlockSection(scriptSetup, statement.start, expression.start, codeFeatures.all), + generateSfcBlockSection(scriptSetup, statement.start, callExp.start, codeFeatures.all), defaultName - ], typeArg.end, expression.end]; + ], typeArg.end, callExp.end]; } else { yield [[ `const ${defaultName} = `, - generateSfcBlockSection(scriptSetup, expression.start, expression.end, codeFeatures.all), + generateSfcBlockSection(scriptSetup, callExp.start, callExp.end, codeFeatures.all), endOfLine, - generateSfcBlockSection(scriptSetup, statement.start, expression.start, codeFeatures.all), + generateSfcBlockSection(scriptSetup, statement.start, callExp.start, codeFeatures.all), defaultName - ], statement.start, expression.end]; + ], statement.start, callExp.end]; } } } @@ -344,60 +398,69 @@ function* generateComponentProps( options: ScriptCodegenOptions, ctx: ScriptCodegenContext, scriptSetup: NonNullable, - scriptSetupRanges: ScriptSetupRanges, - definePropMirrors: Map + scriptSetupRanges: ScriptSetupRanges ): Generator { - yield `const __VLS_fnComponent = (await import('${options.vueCompilerOptions.lib}')).defineComponent({${newLine}`; + if (scriptSetup.generic) { + yield `const __VLS_fnComponent = (await import('${options.vueCompilerOptions.lib}')).defineComponent({${newLine}`; + + if (scriptSetupRanges.defineProps?.arg) { + yield `props: `; + yield generateSfcBlockSection( + scriptSetup, + scriptSetupRanges.defineProps.arg.start, + scriptSetupRanges.defineProps.arg.end, + codeFeatures.navigation + ); + yield `,${newLine}`; + } - if (scriptSetupRanges.props.define?.arg) { - yield `props: `; - yield generateSfcBlockSection(scriptSetup, scriptSetupRanges.props.define.arg.start, scriptSetupRanges.props.define.arg.end, codeFeatures.navigation); - yield `,${newLine}`; - } + yield* generateEmitsOption(options, scriptSetupRanges); - yield* generateEmitsOption(options, scriptSetupRanges); + yield `})${endOfLine}`; - yield `})${endOfLine}`; + yield `type __VLS_BuiltInPublicProps = ${options.vueCompilerOptions.target >= 3.4 + ? `import('${options.vueCompilerOptions.lib}').PublicProps` + : options.vueCompilerOptions.target >= 3.0 + ? `import('${options.vueCompilerOptions.lib}').VNodeProps` + + ` & import('${options.vueCompilerOptions.lib}').AllowedComponentProps` + + ` & import('${options.vueCompilerOptions.lib}').ComponentCustomProps` + : `globalThis.JSX.IntrinsicAttributes` + }`; + yield endOfLine; - yield `type __VLS_BuiltInPublicProps = ${options.vueCompilerOptions.target >= 3.4 - ? `import('${options.vueCompilerOptions.lib}').PublicProps;` - : options.vueCompilerOptions.target >= 3.0 - ? `import('${options.vueCompilerOptions.lib}').VNodeProps - & import('${options.vueCompilerOptions.lib}').AllowedComponentProps - & import('${options.vueCompilerOptions.lib}').ComponentCustomProps;` - : `globalThis.JSX.IntrinsicAttributes;` - }`; - yield endOfLine; - - yield `let __VLS_functionalComponentProps!: `; - yield `${ctx.localTypes.OmitKeepDiscriminatedUnion}['$props'], keyof __VLS_BuiltInPublicProps>`; - yield endOfLine; + yield `type __VLS_OwnProps = `; + yield `${ctx.localTypes.OmitKeepDiscriminatedUnion}['$props'], keyof __VLS_BuiltInPublicProps>`; + yield endOfLine; + } if (scriptSetupRanges.defineProp.length) { yield `const __VLS_defaults = {${newLine}`; for (const defineProp of scriptSetupRanges.defineProp) { - if (defineProp.defaultValue) { - const [propName, localName] = getPropAndLocalName(scriptSetup, defineProp); - - if (defineProp.name || defineProp.isModel) { - yield propName!; - } - else if (defineProp.localName) { - yield localName!; - } - else { - continue; - } - yield `: `; - yield getRangeName(scriptSetup, defineProp.defaultValue); - yield `,${newLine}`; + if (!defineProp.defaultValue) { + continue; + } + + const [propName, localName] = getPropAndLocalName(scriptSetup, defineProp); + + if (defineProp.name || defineProp.isModel) { + yield `'${propName}'`; + } + else if (defineProp.localName) { + yield localName!; + } + else { + continue; } + + yield `: `; + yield getRangeName(scriptSetup, defineProp.defaultValue); + yield `,${newLine}`; } yield `}${endOfLine}`; } yield `type __VLS_PublicProps = `; - if (scriptSetupRanges.slots.define && options.vueCompilerOptions.jsxSlots) { + if (scriptSetupRanges.defineSlots && options.vueCompilerOptions.jsxSlots) { if (ctx.generatedPropsType) { yield ` & `; } @@ -417,12 +480,10 @@ function* generateComponentProps( yield propName!; } else if (defineProp.name) { - // renaming support yield generateSfcBlockSection(scriptSetup, defineProp.name.start, defineProp.name.end, codeFeatures.navigation); } else if (defineProp.localName) { - definePropMirrors.set(localName!, options.getGeneratedLength()); - yield localName!; + yield generateSfcBlockSection(scriptSetup, defineProp.localName.start, defineProp.localName.end, codeFeatures.navigation); } else { continue; @@ -435,18 +496,14 @@ function* generateComponentProps( yield `,${newLine}`; if (defineProp.modifierType) { - let propModifierName = 'modelModifiers'; - if (defineProp.name) { - propModifierName = `${getRangeName(scriptSetup, defineProp.name, true)}Modifiers`; - } + const modifierName = `${defineProp.name ? propName : 'model'}Modifiers`; const modifierType = getRangeName(scriptSetup, defineProp.modifierType); - definePropMirrors.set(propModifierName, options.getGeneratedLength()); - yield `${propModifierName}?: Record<${modifierType}, true>,${endOfLine}`; + yield `'${modifierName}'?: Partial>,${newLine}`; } } yield `}`; } - if (scriptSetupRanges.props.define?.typeArg) { + if (scriptSetupRanges.defineProps?.typeArg) { if (ctx.generatedPropsType) { yield ` & `; } @@ -468,7 +525,7 @@ function* generateModelEmit( yield `type __VLS_ModelEmit = {${newLine}`; for (const defineModel of defineModels) { const [propName, localName] = getPropAndLocalName(scriptSetup, defineModel); - yield `'update:${propName}': [value:`; + yield `'update:${propName}': [value: `; yield* generateDefinePropType(scriptSetup, propName, localName, defineModel); yield `]${endOfLine}`; } @@ -515,14 +572,12 @@ function getPropAndLocalName( if (defineProp.name) { propName = propName!.replace(/['"]+/g, ''); } - return [propName, localName]; + return [propName, localName] as const; } function getRangeName( scriptSetup: NonNullable, - range: TextRange, - unwrap = false + range: TextRange ) { - const offset = unwrap ? 1 : 0; - return scriptSetup.content.substring(range.start + offset, range.end - offset); -} \ No newline at end of file + return scriptSetup.content.slice(range.start, range.end); +} diff --git a/packages/language-core/lib/codegen/script/src.ts b/packages/language-core/lib/codegen/script/src.ts index 44eab3c0eb..350a4b44bf 100644 --- a/packages/language-core/lib/codegen/script/src.ts +++ b/packages/language-core/lib/codegen/script/src.ts @@ -1,5 +1,5 @@ import type { Code, Sfc } from '../../types'; -import { endOfLine } from '../common'; +import { endOfLine } from '../utils'; import { codeFeatures } from './index'; export function* generateSrc( @@ -7,13 +7,13 @@ export function* generateSrc( src: string ): Generator { if (src.endsWith('.d.ts')) { - src = src.substring(0, src.length - '.d.ts'.length); + src = src.slice(0, -'.d.ts'.length); } else if (src.endsWith('.ts')) { - src = src.substring(0, src.length - '.ts'.length); + src = src.slice(0, -'.ts'.length); } else if (src.endsWith('.tsx')) { - src = src.substring(0, src.length - '.tsx'.length) + '.jsx'; + src = src.slice(0, -'.tsx'.length) + '.jsx'; } if (!src.endsWith('.js') && !src.endsWith('.jsx')) { diff --git a/packages/language-core/lib/codegen/script/styleModulesType.ts b/packages/language-core/lib/codegen/script/styleModulesType.ts index f23a7addfa..493ad36ccd 100644 --- a/packages/language-core/lib/codegen/script/styleModulesType.ts +++ b/packages/language-core/lib/codegen/script/styleModulesType.ts @@ -1,15 +1,15 @@ import type { Code } from '../../types'; +import { endOfLine, newLine } from '../utils'; import type { ScriptCodegenContext } from './context'; import { ScriptCodegenOptions, codeFeatures } from './index'; import { generateCssClassProperty } from './template'; -import { endOfLine, newLine } from '../common'; export function* generateStyleModulesType( options: ScriptCodegenOptions, ctx: ScriptCodegenContext ): Generator { const styles = options.sfc.styles.map((style, i) => [style, i] as const).filter(([style]) => style.module); - if (!styles.length && !options.scriptSetupRanges?.cssModules.length) { + if (!styles.length && !options.scriptSetupRanges?.useCssModule.length) { return; } yield `type __VLS_StyleModules = {${newLine}`; diff --git a/packages/language-core/lib/codegen/script/template.ts b/packages/language-core/lib/codegen/script/template.ts index 9e119e18b0..982e37a909 100644 --- a/packages/language-core/lib/codegen/script/template.ts +++ b/packages/language-core/lib/codegen/script/template.ts @@ -1,14 +1,30 @@ import * as path from 'path-browserify'; -import type * as ts from 'typescript'; import type { Code } from '../../types'; import { getSlotsPropertyName, hyphenateTag } from '../../utils/shared'; -import { endOfLine, newLine } from '../common'; import { TemplateCodegenContext, createTemplateCodegenContext } from '../template/context'; -import { forEachInterpolationSegment } from '../template/interpolation'; -import { generateStyleScopedClasses } from '../template/styleScopedClasses'; +import { generateInterpolation } from '../template/interpolation'; +import { generateStyleScopedClassReferences } from '../template/styleScopedClasses'; +import { endOfLine, newLine } from '../utils'; import type { ScriptCodegenContext } from './context'; import { codeFeatures, type ScriptCodegenOptions } from './index'; +export function* generateTemplate( + options: ScriptCodegenOptions, + ctx: ScriptCodegenContext +): Generator { + ctx.generatedTemplate = true; + + const templateCodegenCtx = createTemplateCodegenContext({ + scriptSetupBindingNames: new Set(), + edited: options.edited, + }); + yield* generateTemplateCtx(options); + yield* generateTemplateComponents(options); + yield* generateTemplateDirectives(options); + yield* generateTemplateBody(options, templateCodegenCtx); + return templateCodegenCtx; +} + function* generateTemplateCtx(options: ScriptCodegenOptions): Generator { const exps = []; @@ -38,107 +54,122 @@ function* generateTemplateCtx(options: ScriptCodegenOptions): Generator { } function* generateTemplateComponents(options: ScriptCodegenOptions): Generator { - const exps: Code[] = []; + const types: Code[] = []; if (options.sfc.script && options.scriptRanges?.exportDefault?.componentsOption) { const { componentsOption } = options.scriptRanges.exportDefault; - exps.push([ - options.sfc.script.content.substring(componentsOption.start, componentsOption.end), + yield `const __VLS_componentsOption = ` + yield [ + options.sfc.script.content.slice(componentsOption.start, componentsOption.end), 'script', componentsOption.start, codeFeatures.navigation, - ]); + ]; + yield endOfLine; + types.push(`typeof __VLS_componentsOption`); } let nameType: Code | undefined; if (options.sfc.script && options.scriptRanges?.exportDefault?.nameOption) { const { nameOption } = options.scriptRanges.exportDefault; - nameType = options.sfc.script.content.substring(nameOption.start, nameOption.end); + nameType = options.sfc.script.content.slice(nameOption.start, nameOption.end); } else if (options.sfc.scriptSetup) { const baseName = path.basename(options.fileName); - nameType = `'${options.scriptSetupRanges?.options.name ?? baseName.substring(0, baseName.lastIndexOf('.'))}'`; + nameType = `'${options.scriptSetupRanges?.defineOptions?.name ?? baseName.slice(0, baseName.lastIndexOf('.'))}'`; } if (nameType) { - exps.push(`{} as { - [K in ${nameType}]: typeof __VLS_self - & (new () => { - ${getSlotsPropertyName(options.vueCompilerOptions.target)}: typeof ${options.scriptSetupRanges?.slots?.name ?? '__VLS_slots'} - }) - }`); + types.push( + `{ [K in ${nameType}]: typeof __VLS_self & (new () => { ` + + getSlotsPropertyName(options.vueCompilerOptions.target) + + `: typeof ${options.scriptSetupRanges?.defineSlots?.name ?? `__VLS_slots`} }) }` + ); } - exps.push(`{} as NonNullable`); - exps.push(`__VLS_ctx`); + types.push(`typeof __VLS_ctx`); - yield `const __VLS_localComponents = {${newLine}`; - for (const type of exps) { - yield `...`; + yield `type __VLS_LocalComponents =`; + for (const type of types) { + yield ` & `; yield type; - yield `,${newLine}`; } - yield `}${endOfLine}`; + yield endOfLine; - yield `let __VLS_components!: typeof __VLS_localComponents & __VLS_GlobalComponents${endOfLine}`; + yield `let __VLS_components!: __VLS_LocalComponents & __VLS_GlobalComponents${endOfLine}`; } export function* generateTemplateDirectives(options: ScriptCodegenOptions): Generator { - const exps: Code[] = []; + const types: Code[] = []; if (options.sfc.script && options.scriptRanges?.exportDefault?.directivesOption) { const { directivesOption } = options.scriptRanges.exportDefault; - exps.push([ - options.sfc.script.content.substring(directivesOption.start, directivesOption.end), + yield `const __VLS_directivesOption = `; + yield [ + options.sfc.script.content.slice(directivesOption.start, directivesOption.end), 'script', directivesOption.start, codeFeatures.navigation, - ]); + ]; + yield endOfLine; + types.push(`typeof __VLS_directivesOption`); } - exps.push(`{} as NonNullable`); - exps.push(`__VLS_ctx`); + types.push(`typeof __VLS_ctx`); - yield `const __VLS_localDirectives = {${newLine}`; - for (const type of exps) { - yield `...`; + yield `type __VLS_LocalDirectives =`; + for (const type of types) { + yield ` & `; yield type; - yield `,${newLine}`; } - yield `}${endOfLine}`; + yield endOfLine; - yield `let __VLS_directives!: typeof __VLS_localDirectives & __VLS_GlobalDirectives${endOfLine}`; + yield `let __VLS_directives!: __VLS_LocalDirectives & __VLS_GlobalDirectives${endOfLine}`; } -export function* generateTemplate( +function* generateTemplateBody( options: ScriptCodegenOptions, - ctx: ScriptCodegenContext -): Generator { - ctx.generatedTemplate = true; + templateCodegenCtx: TemplateCodegenContext +): Generator { + yield* generateStyleScopedClasses(options, templateCodegenCtx); + yield* generateStyleScopedClassReferences(templateCodegenCtx, true); + yield* generateCssVars(options, templateCodegenCtx); - const templateCodegenCtx = createTemplateCodegenContext({ - scriptSetupBindingNames: new Set(), - edited: options.edited, - }); - yield* generateTemplateCtx(options); - yield* generateTemplateComponents(options); - yield* generateTemplateDirectives(options); - yield* generateTemplateBody(options, templateCodegenCtx); - return templateCodegenCtx; + if (options.templateCodegen) { + for (const code of options.templateCodegen.codes) { + yield code; + } + } + else { + yield `// no template${newLine}`; + if (!options.scriptSetupRanges?.defineSlots) { + yield `const __VLS_slots = {}${endOfLine}`; + } + yield `const __VLS_inheritedAttrs = {}${endOfLine}`; + yield `const $refs = {}${endOfLine}`; + yield `const $el = {} as any${endOfLine}`; + } + + yield `return {${newLine}`; + yield ` attrs: {} as Partial,${newLine}`; + yield ` slots: ${options.scriptSetupRanges?.defineSlots?.name ?? '__VLS_slots'},${newLine}`; + yield ` refs: $refs,${newLine}`; + yield ` rootEl: $el,${newLine}`; + yield `}${endOfLine}`; } -function* generateTemplateBody( +function* generateStyleScopedClasses( options: ScriptCodegenOptions, - templateCodegenCtx: TemplateCodegenContext + ctx: TemplateCodegenContext ): Generator { const firstClasses = new Set(); - yield `let __VLS_styleScopedClasses!: {}`; + yield `type __VLS_StyleScopedClasses = {}`; for (let i = 0; i < options.sfc.styles.length; i++) { const style = options.sfc.styles[i]; const option = options.vueCompilerOptions.experimentalResolveStyleCssClasses; if (option === 'always' || (option === 'scoped' && style.scoped)) { for (const className of style.classNames) { if (firstClasses.has(className.text)) { - templateCodegenCtx.scopedClasses.push({ + ctx.scopedClasses.push({ source: 'style_' + i, className: className.text.slice(1), offset: className.offset + 1 @@ -157,30 +188,6 @@ function* generateTemplateBody( } } yield endOfLine; - yield* generateStyleScopedClasses(templateCodegenCtx, true); - yield* generateCssVars(options, templateCodegenCtx); - - if (options.templateCodegen) { - for (const code of options.templateCodegen.codes) { - yield code; - } - } - else { - yield `// no template${newLine}`; - if (!options.scriptSetupRanges?.slots.define) { - yield `const __VLS_slots = {}${endOfLine}`; - } - yield `const __VLS_inheritedAttrs = {}${endOfLine}`; - yield `const $refs = {}${endOfLine}`; - yield `const $el = {} as any${endOfLine}`; - } - - yield `return {${newLine}`; - yield ` attrs: {} as Partial,${newLine}`; - yield ` slots: ${options.scriptSetupRanges?.slots.name ?? '__VLS_slots'},${newLine}`; - yield ` refs: $refs,${newLine}`; - yield ` rootEl: $el,${newLine}`; - yield `}${endOfLine}`; } export function* generateCssClassProperty( @@ -199,7 +206,7 @@ export function* generateCssClassProperty( ]; yield `'`; yield [ - classNameWithDot.substring(1), + classNameWithDot.slice(1), 'style_' + styleIndex, offset + 1, codeFeatures.navigation, @@ -222,29 +229,14 @@ function* generateCssVars(options: ScriptCodegenOptions, ctx: TemplateCodegenCon yield `// CSS variable injection ${newLine}`; for (const style of options.sfc.styles) { for (const cssBind of style.cssVars) { - for (const [segment, offset, onlyError] of forEachInterpolationSegment( - options.ts, - undefined, - undefined, + yield* generateInterpolation( + options, ctx, + style.name, + codeFeatures.all, cssBind.text, - cssBind.offset, - options.ts.createSourceFile('/a.txt', cssBind.text, 99 satisfies ts.ScriptTarget.ESNext) - )) { - if (offset === undefined) { - yield segment; - } - else { - yield [ - segment, - style.name, - cssBind.offset + offset, - onlyError - ? codeFeatures.navigation - : codeFeatures.all, - ]; - } - } + cssBind.offset + ); yield endOfLine; } } @@ -264,7 +256,7 @@ export function getTemplateUsageVars(options: ScriptCodegenOptions, ctx: ScriptC } } for (const component of components) { - if (component.indexOf('.') >= 0) { + if (component.includes('.')) { usageVars.add(component.split('.')[0]); } } diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index c5abab54e1..662ee6cff2 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -1,8 +1,8 @@ import type * as CompilerDOM from '@vue/compiler-dom'; import type { Code, VueCodeInformation } from '../../types'; -import { endOfLine, newLine, wrapWith } from '../common'; -import type { TemplateCodegenOptions } from './index'; import { InlayHintInfo } from '../inlayHints'; +import { endOfLine, newLine, wrapWith } from '../utils'; +import type { TemplateCodegenOptions } from './index'; const _codeFeatures = { all: { @@ -38,6 +38,11 @@ const _codeFeatures = { navigation: true, completion: { isAdditional: true }, } as VueCodeInformation, + withoutNavigation: { + verification: true, + completion: true, + semantic: true, + } as VueCodeInformation, withoutHighlight: { semantic: { shouldHighlight: () => false }, verification: true, @@ -63,6 +68,10 @@ export function createTemplateCodegenContext(options: Pick();; const blockConditions: string[] = []; - const usedComponentCtxVars = new Set(); const scopedClasses: { source: string; className: string; @@ -116,6 +124,8 @@ export function createTemplateCodegenContext(options: Pick(); const templateRefs = new Map(); return { @@ -123,15 +133,21 @@ export function createTemplateCodegenContext(options: Pick { - const startTagOffset = node.loc.start.offset + options.template.content.substring(node.loc.start.offset).indexOf(node.tag); - const endTagOffset = !node.isSelfClosing && options.template.lang === 'html' ? node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) : undefined; - const tagOffsets = - endTagOffset !== undefined && endTagOffset > startTagOffset - ? [startTagOffset, endTagOffset] - : [startTagOffset]; - const propsFailedExps: { - node: CompilerDOM.SimpleExpressionNode; - prefix: string; - suffix: string; - }[] = []; + const tagOffsets = [node.loc.start.offset + options.template.content.slice(node.loc.start.offset).indexOf(node.tag)]; + if (!node.isSelfClosing && options.template.lang === 'html') { + const endTagOffset = node.loc.start.offset + node.loc.source.lastIndexOf(node.tag); + if (endTagOffset > tagOffsets[0]) { + tagOffsets.push(endTagOffset); + } + } + const failedPropExps: FailedPropExpression[] = []; const possibleOriginalNames = getPossibleOriginalComponentNames(node.tag, true); const matchImportName = possibleOriginalNames.find(name => options.scriptSetupImportComponentNames.has(name)); const var_originalComponent = matchImportName ?? ctx.getInternalVariable(); @@ -47,10 +43,16 @@ export function* generateComponent( const var_defineComponentCtx = ctx.getInternalVariable(); const isComponentTag = node.tag.toLowerCase() === 'component'; + ctx.currentComponent = { + node, + ctxVar: var_defineComponentCtx, + used: false + }; + let props = node.props; let dynamicTagInfo: { tag: string; - offsets: [number, number | undefined]; + offsets: number[]; astHolder: CompilerDOM.SourceLocation; } | undefined; @@ -67,7 +69,7 @@ export function* generateComponent( } dynamicTagInfo = { tag: prop.exp.content, - offsets: [prop.exp.loc.start.offset, undefined], + offsets: [prop.exp.loc.start.offset], astHolder: prop.exp.loc, }; props = props.filter(p => p !== prop); @@ -79,7 +81,7 @@ export function* generateComponent( // namespace tag dynamicTagInfo = { tag: node.tag, - offsets: [startTagOffset, endTagOffset], + offsets: tagOffsets, astHolder: node.loc, }; } @@ -87,8 +89,9 @@ export function* generateComponent( if (matchImportName) { // hover, renaming / find references support yield `// @ts-ignore${newLine}`; // #2304 - yield `[`; + yield `/** @type { [`; for (const tagOffset of tagOffsets) { + yield `typeof `; if (var_originalComponent === node.tag) { yield [ var_originalComponent, @@ -98,8 +101,9 @@ export function* generateComponent( ]; } else { + const shouldCapitalize = matchImportName[0].toUpperCase() === matchImportName[0]; yield* generateCamelized( - capitalize(node.tag), + shouldCapitalize ? capitalize(node.tag) : node.tag, tagOffset, { ...ctx.codeFeatures.withoutHighlightAndCompletion, @@ -110,19 +114,20 @@ export function* generateComponent( } ); } - yield `,`; + yield `, `; } - yield `]${endOfLine}`; + yield `] } */${endOfLine}`; } else if (dynamicTagInfo) { yield `const ${var_originalComponent} = (`; yield* generateInterpolation( options, ctx, + 'template', + ctx.codeFeatures.all, dynamicTagInfo.tag, - dynamicTagInfo.astHolder, dynamicTagInfo.offsets[0], - ctx.codeFeatures.all, + dynamicTagInfo.astHolder, '(', ')' ); @@ -131,13 +136,14 @@ export function* generateComponent( yield* generateInterpolation( options, ctx, - dynamicTagInfo.tag, - dynamicTagInfo.astHolder, - dynamicTagInfo.offsets[1], + 'template', { ...ctx.codeFeatures.all, completion: false, }, + dynamicTagInfo.tag, + dynamicTagInfo.offsets[1], + dynamicTagInfo.astHolder, '(', ')' ); @@ -145,15 +151,15 @@ export function* generateComponent( yield `)${endOfLine}`; } else if (!isComponentTag) { - yield `const ${var_originalComponent} = __VLS_resolvedLocalAndGlobalComponents.`; + yield `const ${var_originalComponent} = ({} as __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', __VLS_LocalComponents, `; + yield getPossibleOriginalComponentNames(node.tag, false) + .map(name => `'${name}'`) + .join(`, `); + yield `>).`; yield* generateCanonicalComponentName( node.tag, - startTagOffset, - { - // with hover support - ...ctx.codeFeatures.withoutHighlightAndCompletionAndNavigation, - ...ctx.codeFeatures.verification, - } + tagOffsets[0], + ctx.codeFeatures.withoutHighlightAndCompletionAndNavigation ); yield `${endOfLine}`; @@ -178,13 +184,13 @@ export function* generateComponent( yield `, `; } } - yield `] } */${newLine}`; + yield `] } */${endOfLine}`; // auto import support if (options.edited) { yield `// @ts-ignore${newLine}`; // #2304 yield* generateCamelized( capitalize(node.tag), - startTagOffset, + tagOffsets[0], { completion: { isAdditional: true, @@ -201,43 +207,44 @@ export function* generateComponent( } yield `// @ts-ignore${newLine}`; - yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({`; - yield* generateElementProps(options, ctx, node, props, false); + yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({${newLine}`; + yield* generateElementProps(options, ctx, node, props, options.vueCompilerOptions.strictTemplates, false); yield `}))${endOfLine}`; - yield `const ${var_componentInstance} = ${var_functionalComponent}(`; + yield `const `; yield* wrapWith( - startTagOffset, - startTagOffset + node.tag.length, + node.loc.start.offset, + node.loc.end.offset, + { + verification: { + shouldReport(_source, code) { + return String(code) !== '6133'; + }, + } + }, + var_componentInstance + ); + yield ` = ${var_functionalComponent}`; + yield* generateComponentGeneric(ctx); + yield `(`; + yield* wrapWith( + tagOffsets[0], + tagOffsets[0] + node.tag.length, ctx.codeFeatures.verification, - `{`, - ...generateElementProps(options, ctx, node, props, true, propsFailedExps), + `{${newLine}`, + ...generateElementProps(options, ctx, node, props, options.vueCompilerOptions.strictTemplates, true, failedPropExps), `}` ); yield `, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}))${endOfLine}`; - currentComponent = node; - - for (const failedExp of propsFailedExps) { - yield* generateInterpolation( - options, - ctx, - failedExp.node.loc.source, - failedExp.node.loc, - failedExp.node.loc.start.offset, - ctx.codeFeatures.all, - failedExp.prefix, - failedExp.suffix - ); - yield endOfLine; - } + yield* generateFailedPropExps(options, ctx, failedPropExps); const [refName, offset] = yield* generateVScope(options, ctx, node, props); const isRootNode = node === ctx.singleRootNode; if (refName || isRootNode) { const varName = ctx.getInternalVariable(); - ctx.usedComponentCtxVars.add(var_defineComponentCtx); + ctx.currentComponent.used = true; yield `var ${varName} = {} as (Parameters>[0] | null)`; if (node.codegenNode?.type === CompilerDOM.NodeTypes.VNODE_CALL @@ -256,9 +263,9 @@ export function* generateComponent( } } - const usedComponentEventsVar = yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEmit, var_componentEvents); + const usedComponentEventsVar = yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEvents); if (usedComponentEventsVar) { - ctx.usedComponentCtxVars.add(var_defineComponentCtx); + ctx.currentComponent.used = true; yield `let ${var_componentEmit}!: typeof ${var_defineComponentCtx}.emit${endOfLine}`; yield `let ${var_componentEvents}!: __VLS_NormalizeEmits${endOfLine}`; } @@ -277,13 +284,13 @@ export function* generateComponent( const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; if (slotDir) { - yield* generateComponentSlot(options, ctx, node, slotDir, currentComponent, var_defineComponentCtx); + yield* generateComponentSlot(options, ctx, node, slotDir); } else { - yield* generateElementChildren(options, ctx, node, currentComponent, var_defineComponentCtx); + yield* generateElementChildren(options, ctx, node); } - if (ctx.usedComponentCtxVars.has(var_defineComponentCtx)) { + if (ctx.currentComponent.used) { yield `var ${var_defineComponentCtx}!: __VLS_PickFunctionalComponentCtx${endOfLine}`; } } @@ -292,19 +299,13 @@ export function* generateElement( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.ElementNode, - currentComponent: CompilerDOM.ElementNode | undefined, - componentCtxVar: string | undefined, isVForChild: boolean ): Generator { - const startTagOffset = node.loc.start.offset + options.template.content.substring(node.loc.start.offset).indexOf(node.tag); + const startTagOffset = node.loc.start.offset + options.template.content.slice(node.loc.start.offset).indexOf(node.tag); const endTagOffset = !node.isSelfClosing && options.template.lang === 'html' ? node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) : undefined; - const propsFailedExps: { - node: CompilerDOM.SimpleExpressionNode; - prefix: string; - suffix: string; - }[] = []; + const failedPropExps: FailedPropExpression[] = []; yield `__VLS_elementAsFunction(__VLS_intrinsicElements`; yield* generatePropertyAccess( @@ -329,25 +330,13 @@ export function* generateElement( startTagOffset, startTagOffset + node.tag.length, ctx.codeFeatures.verification, - `{`, - ...generateElementProps(options, ctx, node, node.props, true, propsFailedExps), + `{${newLine}`, + ...generateElementProps(options, ctx, node, node.props, options.vueCompilerOptions.strictTemplates, true, failedPropExps), `}` ); yield `)${endOfLine}`; - for (const failedExp of propsFailedExps) { - yield* generateInterpolation( - options, - ctx, - failedExp.node.loc.source, - failedExp.node.loc, - failedExp.node.loc.start.offset, - ctx.codeFeatures.all, - failedExp.prefix, - failedExp.suffix - ); - yield endOfLine; - } + yield* generateFailedPropExps(options, ctx, failedPropExps); const [refName, offset] = yield* generateVScope(options, ctx, node, node.props); if (refName) { @@ -362,11 +351,11 @@ export function* generateElement( } const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; - if (slotDir && componentCtxVar) { - yield* generateComponentSlot(options, ctx, node, slotDir, currentComponent, componentCtxVar); + if (slotDir && ctx.currentComponent) { + yield* generateComponentSlot(options, ctx, node, slotDir); } else { - yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar); + yield* generateElementChildren(options, ctx, node); } if ( @@ -380,6 +369,27 @@ export function* generateElement( } } +function* generateFailedPropExps( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext, + failedPropExps: FailedPropExpression[] +): Generator { + for (const failedExp of failedPropExps) { + yield* generateInterpolation( + options, + ctx, + 'template', + ctx.codeFeatures.all, + failedExp.node.loc.source, + failedExp.node.loc.start.offset, + failedExp.node.loc, + failedExp.prefix, + failedExp.suffix + ); + yield endOfLine; + } +} + function* generateVScope( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, @@ -419,13 +429,13 @@ function* generateVScope( return [refName, offset]; } -export function getCanonicalComponentName(tagText: string) { +function getCanonicalComponentName(tagText: string) { return variableNameRegex.test(tagText) ? tagText : capitalize(camelize(tagText.replace(colonReg, '-'))); } -export function getPossibleOriginalComponentNames(tagText: string, deduplicate: boolean) { +function getPossibleOriginalComponentNames(tagText: string, deduplicate: boolean) { const name1 = capitalize(camelize(tagText)); const name2 = camelize(tagText); const name3 = tagText; @@ -452,18 +462,38 @@ function* generateCanonicalComponentName(tagText: string, offset: number, featur } } +function* generateComponentGeneric( + ctx: TemplateCodegenContext +): Generator { + if (ctx.lastGenericComment) { + const { content, offset } = ctx.lastGenericComment; + yield* wrapWith( + offset, + offset + content.length, + ctx.codeFeatures.verification, + `<`, + [ + content, + 'template', + offset, + ctx.codeFeatures.all + ], + `>` + ); + } + ctx.lastGenericComment = undefined; +} + function* generateComponentSlot( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.ElementNode, - slotDir: CompilerDOM.DirectiveNode, - currentComponent: CompilerDOM.ElementNode | undefined, - componentCtxVar: string + slotDir: CompilerDOM.DirectiveNode ): Generator { yield `{${newLine}`; - ctx.usedComponentCtxVars.add(componentCtxVar); - if (currentComponent) { - ctx.hasSlotElements.add(currentComponent); + if (ctx.currentComponent) { + ctx.currentComponent.used = true; + ctx.hasSlotElements.add(ctx.currentComponent.node); } const slotBlockVars: string[] = []; yield `const {`; @@ -478,24 +508,16 @@ function* generateComponentSlot( false, true ); - yield ': __VLS_thisSlot'; } else { - yield `default: `; yield* wrapWith( slotDir.loc.start.offset, - slotDir.loc.start.offset + ( - slotDir.loc.source.startsWith('#') - ? '#'.length - : slotDir.loc.source.startsWith('v-slot:') - ? 'v-slot:'.length - : 0 - ), + slotDir.loc.start.offset + (slotDir.rawName?.length ?? 0), ctx.codeFeatures.withoutHighlightAndCompletion, - `__VLS_thisSlot` + `default` ); } - yield `} = __VLS_nonNullable(${componentCtxVar}.slots)${endOfLine}`; + yield `: __VLS_thisSlot } = ${ctx.currentComponent!.ctxVar}.slots!${endOfLine}`; if (slotDir?.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { const slotAst = createTsAst(options.ts, slotDir, `(${slotDir.exp.content}) => {}`); @@ -530,7 +552,7 @@ function* generateComponentSlot( let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, currentComponent, prev, componentCtxVar); + yield* generateTemplateChild(options, ctx, childNode, prev); prev = childNode; } @@ -542,7 +564,7 @@ function* generateComponentSlot( isStatic = slotDir.arg.isStatic; } if (isStatic && slotDir && !slotDir.arg) { - yield `__VLS_nonNullable(${componentCtxVar}.slots)['`; + yield `${ctx.currentComponent!.ctxVar}.slots!['`; yield [ '', 'template', @@ -575,7 +597,7 @@ function* generateReferencesForElements( const [content, startOffset] = normalizeAttributeValue(prop.value); yield `// @ts-ignore navigation for \`const ${content} = ref()\`${newLine}`; - yield `__VLS_ctx`; + yield `/** @type { typeof __VLS_ctx`; yield* generatePropertyAccess( options, ctx, @@ -584,9 +606,9 @@ function* generateReferencesForElements( ctx.codeFeatures.navigation, prop.value.loc ); - yield endOfLine; + yield ` } */${endOfLine}`; - if (variableNameRegex.test(content)) { + if (variableNameRegex.test(content) && !options.templateRefNames.has(content)) { ctx.accessExternalVariable(content, startOffset); } @@ -645,7 +667,7 @@ function* generateReferencesForScopedCssClasses( const startOffset = prop.exp.loc.start.offset - 3; const { ts } = options; - const ast = ts.createSourceFile('', content, 99 satisfies typeof ts.ScriptTarget.Latest); + const ast = ts.createSourceFile('', content, 99 satisfies ts.ScriptTarget.Latest); const literals: ts.StringLiteralLike[] = []; ts.forEachChild(ast, node => { @@ -672,11 +694,16 @@ function* generateReferencesForScopedCssClasses( }); for (const literal of literals) { - const classes = collectClasses( - literal.text, - literal.end - literal.text.length - 1 + startOffset - ); - ctx.scopedClasses.push(...classes); + if (literal.text) { + const classes = collectClasses( + literal.text, + literal.end - literal.text.length - 1 + startOffset + ); + ctx.scopedClasses.push(...classes); + } + else { + ctx.emptyClassOffsets.push(literal.end - 1 + startOffset); + } } function walkArrayLiteral(node: ts.ArrayLiteralExpression) { diff --git a/packages/language-core/lib/codegen/template/elementChildren.ts b/packages/language-core/lib/codegen/template/elementChildren.ts index a184a2c798..410df8ffae 100644 --- a/packages/language-core/lib/codegen/template/elementChildren.ts +++ b/packages/language-core/lib/codegen/template/elementChildren.ts @@ -1,6 +1,6 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type { Code } from '../../types'; -import { endOfLine, wrapWith } from '../common'; +import { endOfLine, wrapWith } from '../utils'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; import { generateTemplateChild } from './templateChild'; @@ -8,28 +8,26 @@ import { generateTemplateChild } from './templateChild'; export function* generateElementChildren( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, - node: CompilerDOM.ElementNode, - currentComponent: CompilerDOM.ElementNode | undefined, - componentCtxVar: string | undefined + node: CompilerDOM.ElementNode ): Generator { yield* ctx.resetDirectiveComments('end of element children start'); let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, currentComponent, prev, componentCtxVar); + yield* generateTemplateChild(options, ctx, childNode, prev); prev = childNode; } yield* ctx.generateAutoImportCompletion(); // fix https://github.com/vuejs/language-tools/issues/932 if ( - componentCtxVar + ctx.currentComponent && !ctx.hasSlotElements.has(node) && node.children.length && node.tagType !== CompilerDOM.ElementTypes.ELEMENT && node.tagType !== CompilerDOM.ElementTypes.TEMPLATE ) { - ctx.usedComponentCtxVars.add(componentCtxVar); - yield `__VLS_nonNullable(${componentCtxVar}.slots).`; + ctx.currentComponent.used = true; + yield `${ctx.currentComponent.ctxVar}.slots!.`; yield* wrapWith( node.children[0].loc.start.offset, node.children[node.children.length - 1].loc.end.offset, diff --git a/packages/language-core/lib/codegen/template/elementDirectives.ts b/packages/language-core/lib/codegen/template/elementDirectives.ts index 3c67bce054..973abd9a6c 100644 --- a/packages/language-core/lib/codegen/template/elementDirectives.ts +++ b/packages/language-core/lib/codegen/template/elementDirectives.ts @@ -2,13 +2,13 @@ import * as CompilerDOM from '@vue/compiler-dom'; import { camelize } from '@vue/shared'; import type { Code } from '../../types'; import { hyphenateAttr } from '../../utils/shared'; -import { endOfLine, wrapWith } from '../common'; -import { generateCamelized } from './camelized'; +import { endOfLine, wrapWith } from '../utils'; +import { generateCamelized } from '../utils/camelized'; +import { generateStringLiteralKey } from '../utils/stringLiteralKey'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; import { generateObjectProperty } from './objectProperty'; -import { generateStringLiteralKey } from './stringLiteralKey'; export function* generateElementDirectives( options: TemplateCodegenOptions, @@ -39,7 +39,7 @@ export function* generateElementDirectives( ...generateArg(options, ctx, prop), ...generateModifiers(options, ctx, prop), ...generateValue(options, ctx, prop), - `}, null!, null!)` + ` }, null!, null!)` ); yield endOfLine; } @@ -90,52 +90,64 @@ function* generateArg( startOffset, startOffset + arg.content.length, ctx.codeFeatures.verification, - 'arg' + `arg` ); - yield ': '; + yield `: `; if (arg.isStatic) { yield* generateStringLiteralKey( arg.content, startOffset, - ctx.codeFeatures.withoutHighlight + ctx.codeFeatures.all ); } else { yield* generateInterpolation( options, ctx, + 'template', + ctx.codeFeatures.all, arg.content, - arg.loc, startOffset, - ctx.codeFeatures.all, - '(', - ')' + arg.loc, + `(`, + `)` ); } - yield ', '; + yield `, `; } -function* generateModifiers( +export function* generateModifiers( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, - prop: CompilerDOM.DirectiveNode + prop: CompilerDOM.DirectiveNode, + propertyName: string = 'modifiers' ): Generator { - if (options.vueCompilerOptions.target < 3.5) { + const { modifiers } = prop; + if (!modifiers.length) { return; } - yield 'modifiers: { '; - for (const mod of prop.modifiers) { + const startOffset = modifiers[0].loc.start.offset - 1; + const endOffset = modifiers.at(-1)!.loc.end.offset; + + yield* wrapWith( + startOffset, + endOffset, + ctx.codeFeatures.verification, + propertyName + ); + yield `: { `; + for (const mod of modifiers) { yield* generateObjectProperty( options, ctx, mod.content, mod.loc.start.offset, - ctx.codeFeatures.withoutHighlight + ctx.codeFeatures.withoutNavigation ); - yield ': true, '; + yield `: true, `; } - yield '}, '; + yield `}, `; } function* generateValue( @@ -143,30 +155,32 @@ function* generateValue( ctx: TemplateCodegenContext, prop: CompilerDOM.DirectiveNode ): Generator { - if (prop.exp?.type !== CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { + const { exp } = prop; + if (exp?.type !== CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { return; } yield* wrapWith( - prop.exp.loc.start.offset, - prop.exp.loc.end.offset, + exp.loc.start.offset, + exp.loc.end.offset, ctx.codeFeatures.verification, - 'value' + `value` ); - yield ': '; + yield `: `; yield* wrapWith( - prop.exp.loc.start.offset, - prop.exp.loc.end.offset, + exp.loc.start.offset, + exp.loc.end.offset, ctx.codeFeatures.verification, ...generateInterpolation( options, ctx, - prop.exp.content, - prop.exp.loc, - prop.exp.loc.start.offset, + 'template', ctx.codeFeatures.all, - '(', - ')' + exp.content, + exp.loc.start.offset, + exp.loc, + `(`, + `)` ) ); } diff --git a/packages/language-core/lib/codegen/template/elementEvents.ts b/packages/language-core/lib/codegen/template/elementEvents.ts index 57f57c4c74..e6ec49a56a 100644 --- a/packages/language-core/lib/codegen/template/elementEvents.ts +++ b/packages/language-core/lib/codegen/template/elementEvents.ts @@ -1,10 +1,9 @@ import * as CompilerDOM from '@vue/compiler-dom'; import { camelize, capitalize } from '@vue/shared'; import type * as ts from 'typescript'; -import type { Code, VueCodeInformation } from '../../types'; -import { hyphenateAttr } from '../../utils/shared'; -import { combineLastMapping, createTsAst, endOfLine, newLine, variableNameRegex, wrapWith } from '../common'; -import { generateCamelized } from './camelized'; +import type { Code } from '../../types'; +import { combineLastMapping, createTsAst, endOfLine, newLine, variableNameRegex, wrapWith } from '../utils'; +import { generateCamelized } from '../utils/camelized'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; @@ -15,9 +14,8 @@ export function* generateElementEvents( node: CompilerDOM.ElementNode, componentVar: string, componentInstanceVar: string, - emitVar: string, eventsVar: string -): Generator { +): Generator { let usedComponentEventsVar = false; let propsVar: string | undefined; for (const prop of node.props) { @@ -33,32 +31,18 @@ export function* generateElementEvents( propsVar = ctx.getInternalVariable(); yield `let ${propsVar}!: __VLS_FunctionalComponentProps${endOfLine}`; } - const originalPropName = camelize('on-' + prop.arg.loc.source); - const originalPropNameObjectKey = variableNameRegex.test(originalPropName) - ? originalPropName - : `'${originalPropName}'`; - yield `const ${ctx.getInternalVariable()}: `; - if (!options.vueCompilerOptions.strictTemplates) { - yield `Record & `; - } - yield `(${newLine}`; - yield `__VLS_IsFunction extends true${newLine}`; - yield `? typeof ${propsVar}${newLine}`; - yield `: __VLS_IsFunction extends true${newLine}`; - yield `? {${newLine}`; - yield `/**__VLS_emit,${emitVar},${prop.arg.loc.source}*/${newLine}`; - yield `${originalPropNameObjectKey}?: typeof ${eventsVar}['${prop.arg.loc.source}']${newLine}`; - yield `}${newLine}`; - if (prop.arg.loc.source !== camelize(prop.arg.loc.source)) { - yield `: __VLS_IsFunction extends true${newLine}`; - yield `? {${newLine}`; - yield `/**__VLS_emit,${emitVar},${camelize(prop.arg.loc.source)}*/${newLine}`; - yield `${originalPropNameObjectKey}?: typeof ${eventsVar}['${camelize(prop.arg.loc.source)}']${newLine}`; - yield `}${newLine}`; + let source = prop.arg.loc.source; + let start = prop.arg.loc.start.offset; + let propPrefix = 'on'; + let emitPrefix = ''; + if (source.startsWith('vue:')) { + source = source.slice('vue:'.length); + start = start + 'vue:'.length; + propPrefix = 'onVnode'; + emitPrefix = 'vnode-'; } - yield `: typeof ${propsVar}${newLine}`; - yield `) = {${newLine}`; - yield* generateEventArg(ctx, prop.arg, true); + yield `const ${ctx.getInternalVariable()}: __VLS_NormalizeComponentEvent = {${newLine}`; + yield* generateEventArg(ctx, source, start, propPrefix); yield `: `; yield* generateEventExpression(options, ctx, prop); yield `}${endOfLine}`; @@ -67,54 +51,36 @@ export function* generateElementEvents( return usedComponentEventsVar; } -const eventArgFeatures: VueCodeInformation = { - navigation: { - // @click-outside -> onClickOutside - resolveRenameNewName(newName) { - return camelize('on-' + newName); - }, - // onClickOutside -> @click-outside - resolveRenameEditText(newName) { - const hName = hyphenateAttr(newName); - if (hyphenateAttr(newName).startsWith('on-')) { - return camelize(hName.slice('on-'.length)); - } - return newName; - }, - }, -}; - export function* generateEventArg( ctx: TemplateCodegenContext, - arg: CompilerDOM.SimpleExpressionNode, - enableHover: boolean + name: string, + start: number, + directive = 'on' ): Generator { - const features = enableHover - ? { - ...ctx.codeFeatures.withoutHighlightAndCompletion, - ...eventArgFeatures, - } - : eventArgFeatures; - if (variableNameRegex.test(camelize(arg.loc.source))) { - yield ['', 'template', arg.loc.start.offset, features]; - yield `on`; + const features = { + ...ctx.codeFeatures.withoutHighlightAndCompletion, + ...ctx.codeFeatures.navigationWithoutRename, + }; + if (variableNameRegex.test(camelize(name))) { + yield ['', 'template', start, features]; + yield directive; yield* generateCamelized( - capitalize(arg.loc.source), - arg.loc.start.offset, + capitalize(name), + start, combineLastMapping ); } else { yield* wrapWith( - arg.loc.start.offset, - arg.loc.end.offset, + start, + start + name.length, features, `'`, - ['', 'template', arg.loc.start.offset, combineLastMapping], - 'on', + ['', 'template', start, combineLastMapping], + directive, ...generateCamelized( - capitalize(arg.loc.source), - arg.loc.start.offset, + capitalize(name), + start, combineLastMapping ), `'` @@ -148,9 +114,7 @@ export function* generateEventExpression( yield* generateInterpolation( options, ctx, - prop.exp.content, - prop.exp.loc, - prop.exp.loc.start.offset, + 'template', offset => { if (_isCompoundExpression && isFirstMapping) { isFirstMapping = false; @@ -169,6 +133,9 @@ export function* generateEventExpression( } return ctx.codeFeatures.all; }, + prop.exp.content, + prop.exp.loc.start.offset, + prop.exp.loc, prefix, suffix ); @@ -210,7 +177,7 @@ export function isCompoundExpression(ts: typeof import('typescript'), ast: ts.So return result; } -function isPropertyAccessOrId(ts: typeof import('typescript'), node: ts.Node): boolean { +function isPropertyAccessOrId(ts: typeof import('typescript'), node: ts.Node) { if (ts.isIdentifier(node)) { return true; } diff --git a/packages/language-core/lib/codegen/template/elementProps.ts b/packages/language-core/lib/codegen/template/elementProps.ts index f6ca405cc8..c55084e82b 100644 --- a/packages/language-core/lib/codegen/template/elementProps.ts +++ b/packages/language-core/lib/codegen/template/elementProps.ts @@ -4,26 +4,31 @@ import { minimatch } from 'minimatch'; import { toString } from 'muggle-string'; import type { Code, VueCodeInformation, VueCompilerOptions } from '../../types'; import { hyphenateAttr, hyphenateTag } from '../../utils/shared'; -import { conditionWrapWith, variableNameRegex, wrapWith } from '../common'; -import { generateCamelized } from './camelized'; +import { createVBindShorthandInlayHintInfo } from '../inlayHints'; +import { newLine, variableNameRegex, wrapWith } from '../utils'; +import { generateCamelized } from '../utils/camelized'; +import { generateUnicode } from '../utils/unicode'; import type { TemplateCodegenContext } from './context'; +import { generateModifiers } from './elementDirectives'; import { generateEventArg, generateEventExpression } from './elementEvents'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; import { generateObjectProperty } from './objectProperty'; -import { createVBindShorthandInlayHintInfo } from '../inlayHints'; + +export interface FailedPropExpression { + node: CompilerDOM.SimpleExpressionNode; + prefix: string; + suffix: string; +} export function* generateElementProps( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.ElementNode, props: CompilerDOM.ElementNode['props'], + strictPropsCheck: boolean, enableCodeFeatures: boolean, - propsFailedExps?: { - node: CompilerDOM.SimpleExpressionNode; - prefix: string; - suffix: string; - }[] + failedPropExps?: FailedPropExpression[] ): Generator { const isComponent = node.tagType === CompilerDOM.ElementTypes.COMPONENT; @@ -39,14 +44,15 @@ export function* generateElementProps( ) { if (!isComponent) { yield `...{ `; - yield* generateEventArg(ctx, prop.arg, true); + yield* generateEventArg(ctx, prop.arg.loc.source, prop.arg.loc.start.offset); yield `: `; yield* generateEventExpression(options, ctx, prop); - yield `}, `; + yield `},`; } else { - yield `...{ '${camelize('on-' + prop.arg.loc.source)}': {} as any }, `; + yield `...{ '${camelize('on-' + prop.arg.loc.source)}': {} as any },`; } + yield newLine; } else if ( prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION @@ -54,14 +60,14 @@ export function* generateElementProps( && prop.arg.loc.source.startsWith('[') && prop.arg.loc.source.endsWith(']') ) { - propsFailedExps?.push({ node: prop.arg, prefix: '(', suffix: ')' }); - propsFailedExps?.push({ node: prop.exp, prefix: '() => {', suffix: '}' }); + failedPropExps?.push({ node: prop.arg, prefix: '(', suffix: ')' }); + failedPropExps?.push({ node: prop.exp, prefix: '() => {', suffix: '}' }); } else if ( !prop.arg && prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION ) { - propsFailedExps?.push({ node: prop.exp, prefix: '(', suffix: ')' }); + failedPropExps?.push({ node: prop.exp, prefix: '(', suffix: ')' }); } } } @@ -83,7 +89,7 @@ export function* generateElementProps( : prop.arg.loc.source; } else { - propName = getModelValuePropName(node, options.vueCompilerOptions.target, options.vueCompilerOptions); + propName = getModelPropName(node, options.vueCompilerOptions); } if ( @@ -91,25 +97,25 @@ export function* generateElementProps( || options.vueCompilerOptions.dataAttributes.some(pattern => minimatch(propName!, pattern)) ) { if (prop.exp && prop.exp.constType !== CompilerDOM.ConstantTypes.CAN_STRINGIFY) { - propsFailedExps?.push({ node: prop.exp, prefix: '(', suffix: ')' }); + failedPropExps?.push({ node: prop.exp, prefix: '(', suffix: ')' }); } continue; } - if (prop.modifiers.some(m => m.content === 'prop' || m.content === 'attr')) { - propName = propName.substring(1); + if ( + prop.name === 'bind' + && prop.modifiers.some(m => m.content === 'prop' || m.content === 'attr') + ) { + propName = propName.slice(1); } const shouldSpread = propName === 'style' || propName === 'class'; - const shouldCamelize = isComponent - && (!prop.arg || (prop.arg.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && prop.arg.isStatic)) // isStatic - && hyphenateAttr(propName) === propName - && !options.vueCompilerOptions.htmlAttributes.some(pattern => minimatch(propName, pattern)); + const shouldCamelize = isComponent && getShouldCamelize(options, prop, propName); + const codeInfo = getPropsCodeInfo(ctx, strictPropsCheck, shouldCamelize); if (shouldSpread) { yield `...{ `; } - const codeInfo = ctx.codeFeatures.withoutHighlightAndCompletion; const codes = wrapWith( prop.loc.start.offset, prop.loc.end.offset, @@ -121,28 +127,8 @@ export function* generateElementProps( ctx, propName, prop.arg.loc.start.offset, - { - ...codeInfo, - verification: options.vueCompilerOptions.strictTemplates - ? codeInfo.verification - : { - shouldReport(_source, code) { - if (String(code) === '2353' || String(code) === '2561') { - return false; - } - return typeof codeInfo.verification === 'object' - ? codeInfo.verification.shouldReport?.(_source, code) ?? true - : true; - }, - }, - navigation: codeInfo.navigation - ? { - resolveRenameNewName: camelize, - resolveRenameEditText: shouldCamelize ? hyphenateAttr : undefined, - } - : false, - }, - (prop.loc as any).name_2 ?? ((prop.loc as any).name_2 = {}), + codeInfo, + (prop.loc as any).name_2 ??= {}, shouldCamelize ) : wrapWith( @@ -164,21 +150,42 @@ export function* generateElementProps( ), `)` ); - if (!enableCodeFeatures) { - yield toString([...codes]); + if (enableCodeFeatures) { + yield* codes; } else { - yield* codes; + yield toString([...codes]); } if (shouldSpread) { yield ` }`; } - yield `, `; + yield `,${newLine}`; + + if (prop.name === 'model' && prop.modifiers.length) { + const propertyName = prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION + ? !prop.arg.isStatic + ? `[__VLS_tryAsConstant(\`$\{${prop.arg.content}\}Modifiers\`)]` + : camelize(propName) + `Modifiers` + : `modelModifiers`; + const codes = generateModifiers( + options, + ctx, + prop, + propertyName + ); + if (enableCodeFeatures) { + yield* codes; + } + else { + yield toString([...codes]); + } + yield newLine; + } } else if (prop.type === CompilerDOM.NodeTypes.ATTRIBUTE) { if ( options.vueCompilerOptions.dataAttributes.some(pattern => minimatch(prop.name, pattern)) - // Vue 2 Transition doesn't support "persisted" property but `@vue/compiler-dom always adds it (#3881) + // Vue 2 Transition doesn't support "persisted" property but `@vue/compiler-dom` always adds it (#3881) || ( options.vueCompilerOptions.target < 3 && prop.name === 'persisted' @@ -189,41 +196,13 @@ export function* generateElementProps( } const shouldSpread = prop.name === 'style' || prop.name === 'class'; - const shouldCamelize = isComponent - && hyphenateAttr(prop.name) === prop.name - && !options.vueCompilerOptions.htmlAttributes.some(pattern => minimatch(prop.name, pattern)); + const shouldCamelize = isComponent && getShouldCamelize(options, prop, prop.name); + const codeInfo = getPropsCodeInfo(ctx, strictPropsCheck, true); if (shouldSpread) { yield `...{ `; } - const codeInfo = shouldCamelize - ? { - ...ctx.codeFeatures.withoutHighlightAndCompletion, - navigation: ctx.codeFeatures.withoutHighlightAndCompletion.navigation - ? { - resolveRenameNewName: camelize, - resolveRenameEditText: hyphenateAttr, - } - : false, - } - : { - ...ctx.codeFeatures.withoutHighlightAndCompletion, - }; - if (!options.vueCompilerOptions.strictTemplates) { - const verification = codeInfo.verification; - codeInfo.verification = { - shouldReport(_source, code) { - if (String(code) === '2353' || String(code) === '2561') { - return false; - } - return typeof verification === 'object' - ? verification.shouldReport?.(_source, code) ?? true - : true; - }, - }; - } - const codes = conditionWrapWith( - enableCodeFeatures, + const codes = wrapWith( prop.loc.start.offset, prop.loc.end.offset, ctx.codeFeatures.verification, @@ -233,27 +212,27 @@ export function* generateElementProps( prop.name, prop.loc.start.offset, codeInfo, - (prop.loc as any).name_1 ?? ((prop.loc as any).name_1 = {}), + (prop.loc as any).name_1 ??= {}, shouldCamelize ), `: (`, ...( prop.value - ? generateAttrValue(prop.value, ctx.codeFeatures.all) + ? generateAttrValue(prop.value, ctx.codeFeatures.withoutNavigation) : [`true`] ), `)` ); - if (!enableCodeFeatures) { - yield toString([...codes]); + if (enableCodeFeatures) { + yield* codes; } else { - yield* codes; + yield toString([...codes]); } if (shouldSpread) { yield ` }`; } - yield `, `; + yield `,${newLine}`; } else if ( prop.type === CompilerDOM.NodeTypes.DIRECTIVE @@ -261,34 +240,35 @@ export function* generateElementProps( && !prop.arg && prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION ) { - const codes = conditionWrapWith( - enableCodeFeatures, - prop.exp.loc.start.offset, - prop.exp.loc.end.offset, - ctx.codeFeatures.verification, - `...`, - ...generateInterpolation( - options, - ctx, - prop.exp.content, - prop.exp.loc, - prop.exp.loc.start.offset, - ctx.codeFeatures.all, - '(', - ')' - ) - ); - if (!enableCodeFeatures) { - yield toString([...codes]); + if (prop.exp.loc.source === '$attrs') { + if (enableCodeFeatures) { + ctx.bindingAttrLocs.push(prop.exp.loc); + } } else { - yield* codes; + const codes = wrapWith( + prop.exp.loc.start.offset, + prop.exp.loc.end.offset, + ctx.codeFeatures.verification, + `...`, + ...generatePropExp( + options, + ctx, + prop, + prop.exp, + ctx.codeFeatures.all, + false, + enableCodeFeatures + ) + ); + if (enableCodeFeatures) { + yield* codes; + } + else { + yield toString([...codes]); + } + yield `,${newLine}`; } - yield `, `; - } - else { - // comment this line to avoid affecting comments in prop expressions - // tsCodeGen.addText("/* " + [prop.type, prop.name, prop.arg?.loc.source, prop.exp?.loc.source, prop.loc.source].join(", ") + " */ "); } } } @@ -313,26 +293,45 @@ function* generatePropExp( yield* generateInterpolation( options, ctx, + 'template', + features, exp.loc.source, - exp.loc, exp.loc.start.offset, - features, + exp.loc, '(', ')' ); - } else { + } + else { const propVariableName = camelize(exp.loc.source); if (variableNameRegex.test(propVariableName)) { - if (!ctx.hasLocalVariable(propVariableName)) { - ctx.accessExternalVariable(propVariableName, exp.loc.start.offset); - yield `__VLS_ctx.`; - } - yield* generateCamelized( + const isDestructuredProp = options.destructuredPropNames?.has(propVariableName) ?? false; + const isTemplateRef = options.templateRefNames?.has(propVariableName) ?? false; + + const codes = generateCamelized( exp.loc.source, exp.loc.start.offset, features ); + + if (ctx.hasLocalVariable(propVariableName) || isDestructuredProp) { + yield* codes; + } + else { + ctx.accessExternalVariable(propVariableName, exp.loc.start.offset); + + if (isTemplateRef) { + yield `__VLS_unref(`; + yield* codes; + yield `)`; + } + else { + yield `__VLS_ctx.`; + yield* codes; + } + } + if (enableCodeFeatures) { ctx.inlayHints.push(createVBindShorthandInlayHintInfo(prop.loc, propVariableName)); } @@ -344,61 +343,83 @@ function* generatePropExp( } } -function* generateAttrValue(attrNode: CompilerDOM.TextNode, features: VueCodeInformation): Generator { - const char = attrNode.loc.source.startsWith("'") ? "'" : '"'; - yield char; +function* generateAttrValue( + attrNode: CompilerDOM.TextNode, + features: VueCodeInformation +): Generator { + const quote = attrNode.loc.source.startsWith("'") ? "'" : '"'; + yield quote; let start = attrNode.loc.start.offset; - let end = attrNode.loc.end.offset; let content = attrNode.loc.source; if ( (content.startsWith('"') && content.endsWith('"')) || (content.startsWith("'") && content.endsWith("'")) ) { start++; - end--; content = content.slice(1, -1); } - if (needToUnicode(content)) { - yield* wrapWith( - start, - end, - features, - toUnicode(content) - ); - } - else { - yield [content, 'template', start, features]; - } - yield char; + yield* generateUnicode(content, start, features); + yield quote; } -function needToUnicode(str: string) { - return str.includes('\\') || str.includes('\n'); +function getShouldCamelize( + options: TemplateCodegenOptions, + prop: CompilerDOM.AttributeNode | CompilerDOM.DirectiveNode, + propName: string +) { + return ( + prop.type !== CompilerDOM.NodeTypes.DIRECTIVE + || !prop.arg + || (prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && prop.arg.isStatic) + ) + && hyphenateAttr(propName) === propName + && !options.vueCompilerOptions.htmlAttributes.some(pattern => minimatch(propName, pattern)); } -function toUnicode(str: string) { - return str.split('').map(value => { - const temp = value.charCodeAt(0).toString(16).padStart(4, '0'); - if (temp.length > 2) { - return '\\u' + temp; - } - return value; - }).join(''); +function getPropsCodeInfo( + ctx: TemplateCodegenContext, + strictPropsCheck: boolean, + shouldCamelize: boolean +): VueCodeInformation { + const codeInfo = ctx.codeFeatures.withoutHighlightAndCompletion; + return { + ...codeInfo, + navigation: codeInfo.navigation + ? { + resolveRenameNewName: camelize, + resolveRenameEditText: shouldCamelize ? hyphenateAttr : undefined, + } + : false, + verification: strictPropsCheck + ? codeInfo.verification + : { + shouldReport(_source, code) { + if (String(code) === '2353' || String(code) === '2561') { + return false; + } + return typeof codeInfo.verification === 'object' + ? codeInfo.verification.shouldReport?.(_source, code) ?? true + : true; + }, + } + }; } -function getModelValuePropName(node: CompilerDOM.ElementNode, vueVersion: number, vueCompilerOptions: VueCompilerOptions) { +function getModelPropName(node: CompilerDOM.ElementNode, vueCompilerOptions: VueCompilerOptions) { for (const modelName in vueCompilerOptions.experimentalModelPropName) { const tags = vueCompilerOptions.experimentalModelPropName[modelName]; for (const tag in tags) { if (node.tag === tag || node.tag === hyphenateTag(tag)) { - const v = tags[tag]; - if (typeof v === 'object') { - const arr = Array.isArray(v) ? v : [v]; + const val = tags[tag]; + if (typeof val === 'object') { + const arr = Array.isArray(val) ? val : [val]; for (const attrs of arr) { let failed = false; for (const attr in attrs) { - const attrNode = node.props.find(prop => prop.type === CompilerDOM.NodeTypes.ATTRIBUTE && prop.name === attr) as CompilerDOM.AttributeNode | undefined; + const attrNode = node.props.find( + prop => prop.type === CompilerDOM.NodeTypes.ATTRIBUTE && prop.name === attr + ) as CompilerDOM.AttributeNode | undefined; if (!attrNode || attrNode.value?.content !== attrs[attr]) { failed = true; break; @@ -426,5 +447,5 @@ function getModelValuePropName(node: CompilerDOM.ElementNode, vueVersion: number } } - return vueVersion < 3 ? 'value' : 'modelValue'; + return vueCompilerOptions.target < 3 ? 'value' : 'modelValue'; } diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index feac4f497a..63bcdfa5d6 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -1,13 +1,13 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type * as ts from 'typescript'; import type { Code, Sfc, VueCompilerOptions } from '../../types'; -import { endOfLine, newLine, wrapWith } from '../common'; +import { getSlotsPropertyName } from '../../utils/shared'; +import { endOfLine, newLine, wrapWith } from '../utils'; +import { generateStringLiteralKey } from '../utils/stringLiteralKey'; import { TemplateCodegenContext, createTemplateCodegenContext } from './context'; -import { getCanonicalComponentName, getPossibleOriginalComponentNames } from './element'; import { generateObjectProperty } from './objectProperty'; -import { generateStringLiteralKey } from './stringLiteralKey'; +import { generateStyleScopedClassReferences } from './styleScopedClasses'; import { generateTemplateChild, getVForNode } from './templateChild'; -import { generateStyleScopedClasses } from './styleScopedClasses'; export interface TemplateCodegenOptions { ts: typeof ts; @@ -34,68 +34,82 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator { - for (const { expVar, varName } of ctx.dynamicSlots) { - ctx.hasSlot = true; - yield `Partial, (_: typeof ${varName}) => any>> &${newLine}`; - } - yield `{${newLine}`; - for (const slot of ctx.slots) { - ctx.hasSlot = true; - if (slot.name && slot.loc !== undefined) { - yield* generateObjectProperty( - options, - ctx, - slot.name, - slot.loc, - ctx.codeFeatures.withoutHighlightAndCompletion, - slot.nodeLoc - ); +function* generateSlots(options: TemplateCodegenOptions, ctx: TemplateCodegenContext): Generator { + if (!options.hasDefineSlots) { + yield `var __VLS_slots!: `; + for (const { expVar, varName } of ctx.dynamicSlots) { + ctx.hasSlot = true; + yield `Partial, (_: typeof ${varName}) => any>> &${newLine}`; } - else { - yield* wrapWith( - slot.tagRange[0], - slot.tagRange[1], - ctx.codeFeatures.withoutHighlightAndCompletion, - `default` - ); + yield `{${newLine}`; + for (const slot of ctx.slots) { + ctx.hasSlot = true; + if (slot.name && slot.loc !== undefined) { + yield* generateObjectProperty( + options, + ctx, + slot.name, + slot.loc, + ctx.codeFeatures.withoutHighlightAndCompletion, + slot.nodeLoc + ); + } + else { + yield* wrapWith( + slot.tagRange[0], + slot.tagRange[1], + ctx.codeFeatures.withoutHighlightAndCompletion, + `default` + ); + } + yield `?(_: typeof ${slot.varName}): any,${newLine}`; } - yield `?(_: typeof ${slot.varName}): any,${newLine}`; + yield `}${endOfLine}`; } - yield `}`; + const name = getSlotsPropertyName(options.vueCompilerOptions.target); + yield `var ${name}!: typeof ${options.slotsAssignName ?? '__VLS_slots'}${endOfLine}`; } function* generateInheritedAttrs(ctx: TemplateCodegenContext): Generator { - yield 'var __VLS_inheritedAttrs!: {}'; + yield 'let __VLS_inheritedAttrs!: {}'; for (const varName of ctx.inheritedAttrVars) { yield ` & typeof ${varName}`; } yield endOfLine; + yield `var $attrs!: Partial & Record${endOfLine}`; + + if (ctx.bindingAttrLocs.length) { + yield `[`; + for (const loc of ctx.bindingAttrLocs) { + yield [ + loc.source, + 'template', + loc.start.offset, + ctx.codeFeatures.all + ]; + yield `,`; + } + yield `]${endOfLine}`; + } } function* generateRefs(ctx: TemplateCodegenContext): Generator { @@ -121,32 +135,6 @@ function* generateRootEl(ctx: TemplateCodegenContext): Generator { } } -function* generatePreResolveComponents(options: TemplateCodegenOptions): Generator { - yield `let __VLS_resolvedLocalAndGlobalComponents!: Required<{}`; - if (options.template.ast) { - const components = new Set(); - for (const node of forEachElementNode(options.template.ast)) { - if ( - node.tagType === CompilerDOM.ElementTypes.COMPONENT - && node.tag.toLowerCase() !== 'component' - && !node.tag.includes('.') // namespace tag - ) { - if (components.has(node.tag)) { - continue; - } - components.add(node.tag); - yield newLine; - yield ` & __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', typeof __VLS_localComponents, `; - yield getPossibleOriginalComponentNames(node.tag, false) - .map(name => `"${name}"`) - .join(', '); - yield `>`; - } - } - } - yield `>${endOfLine}`; -} - export function* forEachElementNode(node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode): Generator { if (node.type === CompilerDOM.NodeTypes.ROOT) { for (const child of node.children) { diff --git a/packages/language-core/lib/codegen/template/interpolation.ts b/packages/language-core/lib/codegen/template/interpolation.ts index 8880aa879c..f66fb18eea 100644 --- a/packages/language-core/lib/codegen/template/interpolation.ts +++ b/packages/language-core/lib/codegen/template/interpolation.ts @@ -1,20 +1,24 @@ -import { isGloballyWhitelisted } from '@vue/shared'; +import { isGloballyAllowed } from '@vue/shared'; import type * as ts from 'typescript'; import { getNodeText, getStartEnd } from '../../parsers/scriptSetupRanges'; import type { Code, VueCodeInformation } from '../../types'; -import { collectVars, createTsAst } from '../common'; +import { collectVars, createTsAst } from '../utils'; import type { TemplateCodegenContext } from './context'; -import type { TemplateCodegenOptions } from './index'; export function* generateInterpolation( - options: TemplateCodegenOptions, + options: { + ts: typeof ts, + destructuredPropNames: Set | undefined, + templateRefNames: Set | undefined + }, ctx: TemplateCodegenContext, + source: string, + data: VueCodeInformation | ((offset: number) => VueCodeInformation) | undefined, _code: string, - astHolder: any, start: number | undefined, - data: VueCodeInformation | ((offset: number) => VueCodeInformation) | undefined, - prefix: string, - suffix: string + astHolder: any = {}, + prefix: string = '', + suffix: string = '' ): Generator { const code = prefix + _code + suffix; const ast = createTsAst(options.ts, astHolder, code); @@ -35,12 +39,12 @@ export function* generateInterpolation( let addSuffix = ''; const overLength = offset + section.length - _code.length; if (overLength > 0) { - addSuffix = section.substring(section.length - overLength); - section = section.substring(0, section.length - overLength); + addSuffix = section.slice(section.length - overLength); + section = section.slice(0, -overLength); } if (offset < 0) { - yield section.substring(0, -offset); - section = section.substring(-offset); + yield section.slice(0, -offset); + section = section.slice(-offset); offset = 0; } const shouldSkip = section.length === 0 && (type === 'startText' || type === 'endText'); @@ -51,7 +55,7 @@ export function* generateInterpolation( ) { yield [ section, - 'template', + source, start + offset, type === 'errorMappingOnly' ? ctx.codeFeatures.verification @@ -67,7 +71,7 @@ export function* generateInterpolation( } } -export function* forEachInterpolationSegment( +function* forEachInterpolationSegment( ts: typeof import('typescript'), destructuredPropNames: Set | undefined, templateRefNames: Set | undefined, @@ -87,7 +91,7 @@ export function* forEachInterpolationSegment( if ( ctx.hasLocalVariable(text) || // https://github.com/vuejs/core/blob/245230e135152900189f13a4281302de45fdcfaa/packages/compiler-core/src/transforms/transformExpression.ts#L342-L352 - isGloballyWhitelisted(text) || + isGloballyAllowed(text) || text === 'require' || text.startsWith('__VLS_') ) { @@ -117,11 +121,11 @@ export function* forEachInterpolationSegment( if (ctxVars.length) { if (ctxVars[0].isShorthand) { - yield [code.substring(0, ctxVars[0].offset + ctxVars[0].text.length), 0]; + yield [code.slice(0, ctxVars[0].offset + ctxVars[0].text.length), 0]; yield [': ', undefined]; } else if (ctxVars[0].offset > 0) { - yield [code.substring(0, ctxVars[0].offset), 0, 'startText']; + yield [code.slice(0, ctxVars[0].offset), 0, 'startText']; } for (let i = 0; i < ctxVars.length - 1; i++) { @@ -131,18 +135,18 @@ export function* forEachInterpolationSegment( yield* generateVar(code, destructuredPropNames, templateRefNames, curVar, nextVar); if (nextVar.isShorthand) { - yield [code.substring(curVar.offset + curVar.text.length, nextVar.offset + nextVar.text.length), curVar.offset + curVar.text.length]; + yield [code.slice(curVar.offset + curVar.text.length, nextVar.offset + nextVar.text.length), curVar.offset + curVar.text.length]; yield [': ', undefined]; } else { - yield [code.substring(curVar.offset + curVar.text.length, nextVar.offset), curVar.offset + curVar.text.length]; + yield [code.slice(curVar.offset + curVar.text.length, nextVar.offset), curVar.offset + curVar.text.length]; } } const lastVar = ctxVars.at(-1)!; yield* generateVar(code, destructuredPropNames, templateRefNames, lastVar); if (lastVar.offset + lastVar.text.length < code.length) { - yield [code.substring(lastVar.offset + lastVar.text.length), lastVar.offset + lastVar.text.length, 'endText']; + yield [code.slice(lastVar.offset + lastVar.text.length), lastVar.offset + lastVar.text.length, 'endText']; } } else { @@ -173,14 +177,14 @@ function* generateVar( const isTemplateRef = templateRefNames?.has(curVar.text) ?? false; if (isTemplateRef) { yield [`__VLS_unref(`, undefined]; - yield [code.substring(curVar.offset, curVar.offset + curVar.text.length), curVar.offset]; + yield [code.slice(curVar.offset, curVar.offset + curVar.text.length), curVar.offset]; yield [`)`, undefined]; } else { if (!isDestructuredProp) { yield [`__VLS_ctx.`, undefined]; } - yield [code.substring(curVar.offset, curVar.offset + curVar.text.length), curVar.offset]; + yield [code.slice(curVar.offset, curVar.offset + curVar.text.length), curVar.offset]; } } diff --git a/packages/language-core/lib/codegen/template/objectProperty.ts b/packages/language-core/lib/codegen/template/objectProperty.ts index 812943c8da..78df1a85c6 100644 --- a/packages/language-core/lib/codegen/template/objectProperty.ts +++ b/packages/language-core/lib/codegen/template/objectProperty.ts @@ -1,11 +1,11 @@ import { camelize } from '@vue/shared'; import type { Code, VueCodeInformation } from '../../types'; -import { combineLastMapping, variableNameRegex, wrapWith } from '../common'; -import { generateCamelized } from './camelized'; +import { combineLastMapping, variableNameRegex, wrapWith } from '../utils'; +import { generateCamelized } from '../utils/camelized'; +import { generateStringLiteralKey } from '../utils/stringLiteralKey'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; -import { generateStringLiteralKey } from './stringLiteralKey'; export function* generateObjectProperty( options: TemplateCodegenOptions, @@ -22,16 +22,25 @@ export function* generateObjectProperty( yield* generateInterpolation( options, ctx, + 'template', + features, code.slice(1, -1), - astHolder, offset + 1, - features, + astHolder, `[__VLS_tryAsConstant(`, `)]` ); } else { - yield* generateInterpolation(options, ctx, code, astHolder, offset, features, '', ''); + yield* generateInterpolation( + options, + ctx, + 'template', + features, + code, + offset, + astHolder + ); } } else if (shouldCamelize) { @@ -43,9 +52,9 @@ export function* generateObjectProperty( offset, offset + code.length, features, - `"`, + `'`, ...generateCamelized(code, offset, combineLastMapping), - `"` + `'` ); } } diff --git a/packages/language-core/lib/codegen/template/propertyAccess.ts b/packages/language-core/lib/codegen/template/propertyAccess.ts index d763ae41e6..0733d38b01 100644 --- a/packages/language-core/lib/codegen/template/propertyAccess.ts +++ b/packages/language-core/lib/codegen/template/propertyAccess.ts @@ -1,9 +1,9 @@ import type { Code, VueCodeInformation } from '../../types'; -import { variableNameRegex } from '../common'; +import { variableNameRegex } from '../utils'; +import { generateStringLiteralKey } from '../utils/stringLiteralKey'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; -import { generateStringLiteralKey } from './stringLiteralKey'; export function* generatePropertyAccess( options: TemplateCodegenOptions, @@ -23,12 +23,11 @@ export function* generatePropertyAccess( yield* generateInterpolation( options, ctx, + 'template', + features, code, - astHolder, offset, - features, - '', - '' + astHolder ); } else { diff --git a/packages/language-core/lib/codegen/template/slotOutlet.ts b/packages/language-core/lib/codegen/template/slotOutlet.ts index 021d51faf7..a8b07fb40c 100644 --- a/packages/language-core/lib/codegen/template/slotOutlet.ts +++ b/packages/language-core/lib/codegen/template/slotOutlet.ts @@ -1,21 +1,19 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type { Code } from '../../types'; -import { endOfLine, newLine, wrapWith } from '../common'; +import { createVBindShorthandInlayHintInfo } from '../inlayHints'; +import { endOfLine, newLine, wrapWith } from '../utils'; import type { TemplateCodegenContext } from './context'; import { generateElementChildren } from './elementChildren'; import { generateElementProps } from './elementProps'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; -import { createVBindShorthandInlayHintInfo } from '../inlayHints'; export function* generateSlotOutlet( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, - node: CompilerDOM.SlotOutletNode, - currentComponent: CompilerDOM.ElementNode | undefined, - componentCtxVar: string | undefined + node: CompilerDOM.SlotOutletNode ): Generator { - const startTagOffset = node.loc.start.offset + options.template.content.substring(node.loc.start.offset).indexOf(node.tag); + const startTagOffset = node.loc.start.offset + options.template.content.slice(node.loc.start.offset).indexOf(node.tag); const varSlot = ctx.getInternalVariable(); const nameProp = node.props.find(prop => { if (prop.type === CompilerDOM.NodeTypes.ATTRIBUTE) { @@ -55,14 +53,14 @@ export function* generateSlotOutlet( startTagOffset + node.tag.length, ctx.codeFeatures.verification, `{${newLine}`, - ...generateElementProps(options, ctx, node, node.props.filter(prop => prop !== nameProp), true), + ...generateElementProps(options, ctx, node, node.props.filter(prop => prop !== nameProp), true, true), `}` ); yield `)${endOfLine}`; } else { yield `var ${varSlot} = {${newLine}`; - yield* generateElementProps(options, ctx, node, node.props.filter(prop => prop !== nameProp), true); + yield* generateElementProps(options, ctx, node, node.props.filter(prop => prop !== nameProp), options.vueCompilerOptions.strictTemplates, true); yield `}${endOfLine}`; if ( @@ -90,10 +88,11 @@ export function* generateSlotOutlet( yield* generateInterpolation( options, ctx, + 'template', + ctx.codeFeatures.all, nameProp.exp.content, - nameProp.exp, nameProp.exp.loc.start.offset, - ctx.codeFeatures.all, + nameProp.exp, '(', ')' ); @@ -113,5 +112,5 @@ export function* generateSlotOutlet( } } yield* ctx.generateAutoImportCompletion(); - yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar); + yield* generateElementChildren(options, ctx, node); } diff --git a/packages/language-core/lib/codegen/template/styleScopedClasses.ts b/packages/language-core/lib/codegen/template/styleScopedClasses.ts index fe1777866b..d7f8cb5bd8 100644 --- a/packages/language-core/lib/codegen/template/styleScopedClasses.ts +++ b/packages/language-core/lib/codegen/template/styleScopedClasses.ts @@ -1,23 +1,27 @@ import type { Code } from '../../types'; +import { endOfLine } from '../utils'; import type { TemplateCodegenContext } from './context'; -import { endOfLine, newLine } from '../common'; -export function* generateStyleScopedClasses( +export function* generateStyleScopedClassReferences( ctx: TemplateCodegenContext, withDot = false ): Generator { + if (!ctx.emptyClassOffsets.length && !ctx.scopedClasses.length) { + return; + } + + yield `[`; for (const offset of ctx.emptyClassOffsets) { - yield `__VLS_styleScopedClasses['`; + yield `'`; yield [ '', 'template', offset, ctx.codeFeatures.additionalCompletion, ]; - yield `']${endOfLine}`; + yield `', `; } for (const { source, className, offset } of ctx.scopedClasses) { - yield `__VLS_styleScopedClasses[`; yield [ '', source, @@ -35,9 +39,9 @@ export function* generateStyleScopedClasses( offset + className.length, ctx.codeFeatures.navigationWithoutRename, ]; - yield `]${endOfLine}`; + yield `, `; } - yield newLine; + yield `] as (keyof __VLS_StyleScopedClasses)[]${endOfLine}`; function* escapeString(source: string, className: string, offset: number, escapeTargets: string[]): Generator { let count = 0; diff --git a/packages/language-core/lib/codegen/template/templateChild.ts b/packages/language-core/lib/codegen/template/templateChild.ts index c7df01b522..90b810dee9 100644 --- a/packages/language-core/lib/codegen/template/templateChild.ts +++ b/packages/language-core/lib/codegen/template/templateChild.ts @@ -1,6 +1,6 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type { Code } from '../../types'; -import { endOfLine, newLine } from '../common'; +import { endOfLine, newLine } from '../utils'; import type { TemplateCodegenContext } from './context'; import { generateComponent, generateElement } from './element'; import type { TemplateCodegenOptions } from './index'; @@ -29,23 +29,31 @@ export function* generateTemplateChild( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode | CompilerDOM.SimpleExpressionNode, - currentComponent: CompilerDOM.ElementNode | undefined, prevNode: CompilerDOM.TemplateChildNode | undefined, - componentCtxVar: string | undefined, isVForChild: boolean = false ): Generator { if (prevNode?.type === CompilerDOM.NodeTypes.COMMENT) { const commentText = prevNode.content.trim().split(' ')[0]; - if (commentText.match(/^@vue-skip\b[\s\S]*/)) { + if (/^@vue-skip\b[\s\S]*/.test(commentText)) { yield `// @vue-skip${newLine}`; return; } - else if (commentText.match(/^@vue-ignore\b[\s\S]*/)) { + else if (/^@vue-ignore\b[\s\S]*/.test(commentText)) { yield* ctx.ignoreError(); } - else if (commentText.match(/^@vue-expect-error\b[\s\S]*/)) { + else if (/^@vue-expect-error\b[\s\S]*/.test(commentText)) { yield* ctx.expectError(prevNode); } + else { + const match = prevNode.loc.source.match(/^\n\n\n```\n\nPozor na to, že pro importy pomocí `src` platí stejná pravidla pro zadávání cest jako pro požadavky na webpack moduly, což znamená:\n\n- Relativní cesty musí začínat s `./`\n- Můžete importovat zdroje z npm závislostí:\n\n```vue\n\n\n```\n\nDejte pozor, že integrace s různými pre-procesory se může lišit podle zvolené sady softwarových nástrojů. Pro příklady se podívejte do příslušné dokumentace:\n\n- [Vite](https://vitejs.dev/guide/features.html#css-pre-processors)\n- [Vue CLI](https://cli.vuejs.org/guide/css.html#pre-processors)\n- [webpack + vue-loader](https://vue-loader.vuejs.org/guide/pre-processors.html#using-pre-processors)\n" + "value": "Bloky mohou pomocí atributu `lang` deklarovat programovací jazyk, v němž má proběhnout pre-processing. Nejběžnější případ je použití TypeScriptu pro blok `\n```\n\n`lang` lze použít na jakýkoli blok – například můžeme použít `\n```\n\nDejte pozor, že integrace s různými pre-procesory se může lišit podle zvolené sady softwarových nástrojů. Pro příklady se podívejte do příslušné dokumentace:\n\n- [Vite](https://vitejs.dev/guide/features.html#css-pre-processors)\n- [Vue CLI](https://cli.vuejs.org/guide/css.html#pre-processors)\n- [webpack + vue-loader](https://vue-loader.vuejs.org/guide/pre-processors.html#using-pre-processors)" }, "values": [ { @@ -79,120 +26,14 @@ "name": "pug" } ], - "references": [ - { - "name": "en", - "url": "https://vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "zh-cn", - "url": "https://cn.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "zh-hk", - "url": "https://zh-hk.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "ja", - "url": "https://ja.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "ua", - "url": "https://ua.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "fr", - "url": "https://fr.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "ko", - "url": "https://ko.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "pt", - "url": "https://pt.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "bn", - "url": "https://bn.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "it", - "url": "https://it.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "cs", - "url": "https://cs.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "ru", - "url": "https://ru.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "fa", - "url": "https://fa.vuejs.org/api/sfc-spec.html#pre-processors" - } - ] + "references": "api/sfc-spec.html#pre-processors" } ], "description": { "kind": "markdown", "value": "\n- Každý soubor `*.vue` může obsahovat maximálně jeden blok `