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/CHANGELOG.md b/CHANGELOG.md index 7e27573f7a..c3ecffed71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,7 +97,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/extensions/vscode/README.md b/extensions/vscode/README.md index f4fb4dee9d..6dbe71267a 100644 --- a/extensions/vscode/README.md +++ b/extensions/vscode/README.md @@ -277,6 +277,7 @@ Finally you need to make VS Code recognize your new extension and automatically | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | ----------------------------------- | | `vue.trace.server` | Traces the communication between VS Code and the language server. | `string` | `"off"` | | `vue.server.hybridMode` | Vue language server only handles CSS and HTML language support, and tsserver takes over TS language support via TS plugin. | `boolean,string` | `"auto"` | +| `vue.server.compatibleExtensions` | Set compatible extensions to skip automatic detection of Hybrid Mode. | `array` | `[]` | | `vue.server.includeLanguages` | | `array` | `["vue"]` | | `vue.server.maxOldSpaceSize` | Set --max-old-space-size option on server process. If you have problem on frequently "Request textDocument/** failed." error, try setting higher memory(MB) on it. | `number,null` | `null` | | `vue.doctor.status` | Show known problems in status bar. | `boolean` | `true` | diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 73732413d9..510653134d 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -274,6 +274,14 @@ ], "description": "Vue language server only handles CSS and HTML language support, and tsserver takes over TS language support via TS plugin." }, + "vue.server.compatibleExtensions": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "Set compatible extensions to skip automatic detection of Hybrid Mode." + }, "vue.server.includeLanguages": { "type": "array", "items": { @@ -557,14 +565,14 @@ "devDependencies": { "@types/semver": "^7.5.3", "@types/vscode": "^1.82.0", - "@volar/vscode": "~2.4.9", + "@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": "latest", "esbuild-visualizer": "latest", - "reactive-vscode": "0.2.7", + "reactive-vscode": "^0.2.9", "semver": "^7.5.4", "vscode-ext-gen": "^0.5.0", "vscode-tmlanguage-snapshot": "latest" diff --git a/extensions/vscode/src/compatibility.ts b/extensions/vscode/src/compatibility.ts index a0abe9aaac..45aa9dd1e9 100644 --- a/extensions/vscode/src/compatibility.ts +++ b/extensions/vscode/src/compatibility.ts @@ -1,6 +1,29 @@ 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 8db6272f9a..db637d9dd8 100644 --- a/extensions/vscode/src/features/doctor.ts +++ b/extensions/vscode/src/features/doctor.ts @@ -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'), }); diff --git a/extensions/vscode/src/hybridMode.ts b/extensions/vscode/src/hybridMode.ts index 38fc895891..606b8b2775 100644 --- a/extensions/vscode/src/hybridMode.ts +++ b/extensions/vscode/src/hybridMode.ts @@ -1,6 +1,6 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; -import { computed, executeCommand, ref, useAllExtensions, useVscodeContext, watchEffect } from "reactive-vscode"; +import { computed, executeCommand, useAllExtensions, useVscodeContext, watchEffect } from 'reactive-vscode'; import * as semver from 'semver'; import * as vscode from 'vscode'; import { incompatibleExtensions, unknownExtensions } from './compatibility'; @@ -8,31 +8,51 @@ 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..33fda238cd 100644 --- a/extensions/vscode/src/insiders.ts +++ b/extensions/vscode/src/insiders.ts @@ -1,10 +1,10 @@ -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..."; + const item = vscode.languages.createLanguageStatusItem('vue-insider', 'vue'); + item.text = 'Checking for Updates...'; item.busy = true; let succeed = false; @@ -12,8 +12,8 @@ export function useInsidersStatusItem(context: vscode.ExtensionContext) { async function fetchJson() { 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); @@ -26,7 +26,7 @@ export function useInsidersStatusItem(context: vscode.ExtensionContext) { item.busy = false; if (!succeed) { - item.text = "Failed to Fetch Versions"; + item.text = 'Failed to Fetch Versions'; item.severity = vscode.LanguageStatusSeverity.Error; } } @@ -44,40 +44,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 +88,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 +123,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 e7948f40d9..01b2c7ff16 100644 --- a/extensions/vscode/src/languageClient.ts +++ b/extensions/vscode/src/languageClient.ts @@ -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/nodeClientMain.ts b/extensions/vscode/src/nodeClientMain.ts index 56960d104d..a828f15028 100644 --- a/extensions/vscode/src/nodeClientMain.ts +++ b/extensions/vscode/src/nodeClientMain.ts @@ -152,7 +152,8 @@ try { 'for(const e of n.contributes.typescriptServerPlugins', 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)', 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/lib/base.ts b/packages/component-meta/lib/base.ts index 6bde6ab32d..c65a7cf43d 100644 --- a/packages/component-meta/lib/base.ts +++ b/packages/component-meta/lib/base.ts @@ -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 @@ -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.slice(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.slice(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 3ddb629c9e..c7ed0cab44 100644 --- a/packages/component-meta/package.json +++ b/packages/component-meta/package.json @@ -13,7 +13,7 @@ "directory": "packages/component-meta" }, "dependencies": { - "@volar/typescript": "~2.4.9", + "@volar/typescript": "~2.4.11", "@vue/language-core": "2.1.10", "path-browserify": "^1.0.1", "vue-component-type-helpers": "2.1.10" diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index 9908372d43..d34f4e924a 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 diff --git a/packages/language-core/lib/codegen/script/component.ts b/packages/language-core/lib/codegen/script/component.ts index d76ea81197..6b8b12d021 100644 --- a/packages/language-core/lib/codegen/script/component.ts +++ b/packages/language-core/lib/codegen/script/component.ts @@ -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 && scriptSetupRanges.useTemplateRef.length) { 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 085697af3b..331c21d823 100644 --- a/packages/language-core/lib/codegen/script/componentSelf.ts +++ b/packages/language-core/lib/codegen/script/componentSelf.ts @@ -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.slice(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 84434470fd..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.slice(range.start, range.end)) ?? [], - ...options.scriptSetupRanges?.bindings.map(range => options.sfc.scriptSetup!.content.slice(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 d805ec1ef3..9c975f325d 100644 --- a/packages/language-core/lib/codegen/script/index.ts +++ b/packages/language-core/lib/codegen/script/index.ts @@ -84,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 bc3117e60d..0d7fbeffeb 100644 --- a/packages/language-core/lib/codegen/script/scriptSetup.ts +++ b/packages/language-core/lib/codegen/script/scriptSetup.ts @@ -17,7 +17,6 @@ export function* generateScriptSetupImports( 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,20 +53,20 @@ 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}` + + ` 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}` @@ -79,34 +76,16 @@ export function* generateScriptSetup( } 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,136 +93,167 @@ 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) { - 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 - ]); - } + // 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, codeFeatures.verification], + `])` + ], + callExp.end, + callExp.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 ]); } } @@ -260,15 +270,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, scriptSetup); - 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 +294,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 +309,51 @@ 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, + scriptSetup: NonNullable +): 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}`; + } + } +} + 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 +363,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 +394,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 (scriptSetupRanges.props.define?.arg) { - yield `props: `; - yield generateSfcBlockSection(scriptSetup, scriptSetupRanges.props.define.arg.start, scriptSetupRanges.props.define.arg.end, codeFeatures.navigation); - yield `,${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}`; + } - 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 +476,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 +492,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}?: Partial>,${endOfLine}`; + yield `'${modifierName}'?: Partial>,${newLine}`; } } yield `}`; } - if (scriptSetupRanges.props.define?.typeArg) { + if (scriptSetupRanges.defineProps?.typeArg) { if (ctx.generatedPropsType) { yield ` & `; } @@ -515,14 +568,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.slice(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/styleModulesType.ts b/packages/language-core/lib/codegen/script/styleModulesType.ts index 4e4c30a75a..493ad36ccd 100644 --- a/packages/language-core/lib/codegen/script/styleModulesType.ts +++ b/packages/language-core/lib/codegen/script/styleModulesType.ts @@ -9,7 +9,7 @@ export function* generateStyleModulesType( 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 b9524dbd66..982e37a909 100644 --- a/packages/language-core/lib/codegen/script/template.ts +++ b/packages/language-core/lib/codegen/script/template.ts @@ -3,11 +3,28 @@ import type { Code } from '../../types'; import { getSlotsPropertyName, hyphenateTag } from '../../utils/shared'; import { TemplateCodegenContext, createTemplateCodegenContext } from '../template/context'; import { generateInterpolation } from '../template/interpolation'; -import { generateStyleScopedClasses } from '../template/styleScopedClasses'; +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 = []; @@ -37,16 +54,19 @@ 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([ + 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; @@ -56,87 +76,100 @@ function* generateTemplateComponents(options: ScriptCodegenOptions): Generator { ` + types.push( + `{ [K in ${nameType}]: typeof __VLS_self & (new () => { ` + getSlotsPropertyName(options.vueCompilerOptions.target) - + ` : typeof ${options.scriptSetupRanges?.slots.name ?? `__VLS_slots`} }) }` + + `: 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([ + 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 @@ -155,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( @@ -247,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 e204d791f3..bf219f05b7 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -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, diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index ec9c86def8..6776fe8ddb 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -200,7 +200,7 @@ export function* generateComponent( } yield `// @ts-ignore${newLine}`; - yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({`; + yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({${newLine}`; yield* generateElementProps(options, ctx, node, props, options.vueCompilerOptions.strictTemplates, false); yield `}))${endOfLine}`; @@ -211,7 +211,7 @@ export function* generateComponent( startTagOffset, startTagOffset + node.tag.length, ctx.codeFeatures.verification, - `{`, + `{${newLine}`, ...generateElementProps(options, ctx, node, props, options.vueCompilerOptions.strictTemplates, true, failedPropExps), `}` ); @@ -245,7 +245,7 @@ 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); yield `let ${var_componentEmit}!: typeof ${var_defineComponentCtx}.emit${endOfLine}`; @@ -314,7 +314,7 @@ export function* generateElement( startTagOffset, startTagOffset + node.tag.length, ctx.codeFeatures.verification, - `{`, + `{${newLine}`, ...generateElementProps(options, ctx, node, node.props, options.vueCompilerOptions.strictTemplates, true, failedPropExps), `}` ); @@ -602,7 +602,7 @@ function* generateReferencesForElements( ); yield ` } */${endOfLine}`; - if (variableNameRegex.test(content)) { + if (variableNameRegex.test(content) && !options.templateRefNames.has(content)) { ctx.accessExternalVariable(content, startOffset); } diff --git a/packages/language-core/lib/codegen/template/elementDirectives.ts b/packages/language-core/lib/codegen/template/elementDirectives.ts index 831bfe901a..973abd9a6c 100644 --- a/packages/language-core/lib/codegen/template/elementDirectives.ts +++ b/packages/language-core/lib/codegen/template/elementDirectives.ts @@ -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,14 +90,14 @@ 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 { @@ -109,34 +109,45 @@ function* generateArg( arg.content, startOffset, 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( @@ -144,31 +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, 'template', ctx.codeFeatures.all, - prop.exp.content, - prop.exp.loc.start.offset, - prop.exp.loc, - '(', - ')' + 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 498330f2a5..e6ec49a56a 100644 --- a/packages/language-core/lib/codegen/template/elementEvents.ts +++ b/packages/language-core/lib/codegen/template/elementEvents.ts @@ -1,8 +1,7 @@ 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 type { Code } from '../../types'; import { combineLastMapping, createTsAst, endOfLine, newLine, variableNameRegex, wrapWith } from '../utils'; import { generateCamelized } from '../utils/camelized'; import type { TemplateCodegenContext } from './context'; @@ -15,7 +14,6 @@ export function* generateElementEvents( node: CompilerDOM.ElementNode, componentVar: string, componentInstanceVar: string, - emitVar: string, eventsVar: string ): Generator { let usedComponentEventsVar = false; @@ -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 & `; + 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 `(${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}`; - } - 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 ), `'` diff --git a/packages/language-core/lib/codegen/template/elementProps.ts b/packages/language-core/lib/codegen/template/elementProps.ts index 74512d60f8..1557aad18c 100644 --- a/packages/language-core/lib/codegen/template/elementProps.ts +++ b/packages/language-core/lib/codegen/template/elementProps.ts @@ -5,10 +5,11 @@ import { toString } from 'muggle-string'; import type { Code, VueCodeInformation, VueCompilerOptions } from '../../types'; import { hyphenateAttr, hyphenateTag } from '../../utils/shared'; import { createVBindShorthandInlayHintInfo } from '../inlayHints'; -import { conditionWrapWith, variableNameRegex, wrapWith } from '../utils'; +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'; @@ -43,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 @@ -87,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 ( @@ -100,20 +102,20 @@ export function* generateElementProps( continue; } - if (prop.modifiers.some(m => m.content === 'prop' || m.content === 'attr')) { + 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 = getPropsCodeInfo(ctx, strictPropsCheck, shouldCamelize); const codes = wrapWith( prop.loc.start.offset, prop.loc.end.offset, @@ -148,16 +150,37 @@ 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 ( @@ -173,16 +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 = getPropsCodeInfo(ctx, strictPropsCheck, true); - const codes = conditionWrapWith( - enableCodeFeatures, + const codes = wrapWith( prop.loc.start.offset, prop.loc.end.offset, ctx.codeFeatures.verification, @@ -198,21 +218,21 @@ export function* generateElementProps( `: (`, ...( 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 @@ -220,64 +240,32 @@ export function* generateElementProps( && !prop.arg && prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION ) { - const codes = conditionWrapWith( - enableCodeFeatures, + const codes = wrapWith( prop.exp.loc.start.offset, prop.exp.loc.end.offset, ctx.codeFeatures.verification, `...`, - ...generateInterpolation( + ...generatePropExp( options, ctx, - 'template', + prop, + prop.exp, ctx.codeFeatures.all, - prop.exp.content, - prop.exp.loc.start.offset, - prop.exp.loc, - '(', - ')' + false, + enableCodeFeatures ) ); - if (!enableCodeFeatures) { - yield toString([...codes]); + if (enableCodeFeatures) { + yield* codes; } else { - yield* codes; + yield toString([...codes]); } - yield `, `; + yield `,${newLine}`; } } } -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* generatePropExp( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, @@ -330,7 +318,10 @@ function* generatePropExp( } } -function* generateAttrValue(attrNode: CompilerDOM.TextNode, features: VueCodeInformation): Generator { +function* generateAttrValue( + attrNode: CompilerDOM.TextNode, + features: VueCodeInformation +): Generator { const quote = attrNode.loc.source.startsWith("'") ? "'" : '"'; yield quote; let start = attrNode.loc.start.offset; @@ -346,19 +337,64 @@ function* generateAttrValue(attrNode: CompilerDOM.TextNode, features: VueCodeInf yield quote; } -function getModelValuePropName(node: CompilerDOM.ElementNode, vueVersion: number, vueCompilerOptions: VueCompilerOptions) { +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 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 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; @@ -386,5 +422,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 a0776541a3..4fd82c74da 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -1,12 +1,13 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type * as ts from 'typescript'; import type { Code, Sfc, VueCompilerOptions } from '../../types'; +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 { generateStyleScopedClasses } from './styleScopedClasses'; +import { generateStyleScopedClassReferences } from './styleScopedClasses'; import { generateTemplateChild, getVForNode } from './templateChild'; export interface TemplateCodegenOptions { @@ -34,8 +35,11 @@ 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}`; } function* generateRefs(ctx: TemplateCodegenContext): Generator { @@ -136,7 +140,7 @@ function* generatePreResolveComponents(options: TemplateCodegenOptions): Generat } components.add(node.tag); yield newLine; - yield ` & __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', typeof __VLS_localComponents, `; + yield ` & __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', __VLS_LocalComponents, `; yield getPossibleOriginalComponentNames(node.tag, false) .map(name => `'${name}'`) .join(', '); diff --git a/packages/language-core/lib/codegen/template/styleScopedClasses.ts b/packages/language-core/lib/codegen/template/styleScopedClasses.ts index 86a1f5ce4e..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, newLine } from '../utils'; +import { endOfLine } from '../utils'; import type { TemplateCodegenContext } from './context'; -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/utils/index.ts b/packages/language-core/lib/codegen/utils/index.ts index e404e73c9a..e2f28894da 100644 --- a/packages/language-core/lib/codegen/utils/index.ts +++ b/packages/language-core/lib/codegen/utils/index.ts @@ -7,23 +7,6 @@ export const endOfLine = `;${newLine}`; export const combineLastMapping: VueCodeInformation = { __combineLastMapping: true }; export const variableNameRegex = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/; -export function* conditionWrapWith( - condition: boolean, - startOffset: number, - endOffset: number, - features: VueCodeInformation, - ...wrapCodes: Code[] -): Generator { - if (condition) { - yield* wrapWith(startOffset, endOffset, features, ...wrapCodes); - } - else { - for (const wrapCode of wrapCodes) { - yield wrapCode; - } - } -} - export function* wrapWith( startOffset: number, endOffset: number, diff --git a/packages/language-core/lib/parsers/scriptSetupRanges.ts b/packages/language-core/lib/parsers/scriptSetupRanges.ts index 433674d2f1..55a835ee39 100644 --- a/packages/language-core/lib/parsers/scriptSetupRanges.ts +++ b/packages/language-core/lib/parsers/scriptSetupRanges.ts @@ -4,6 +4,62 @@ import type { TextRange, VueCompilerOptions } from '../types'; const tsCheckReg = /^\/\/\s*@ts-(?:no)?check($|\s)/; +type CallExpressionRange = { + callExp: TextRange; + exp: TextRange; + arg?: TextRange; + typeArg?: TextRange; +}; + +type DefineProp = { + localName?: TextRange; + name?: TextRange; + type?: TextRange; + modifierType?: TextRange; + runtimeType?: TextRange; + defaultValue?: TextRange; + required?: boolean; + isModel?: boolean; +} + +type DefineProps = CallExpressionRange & { + name?: string; + destructured?: Set; + destructuredRest?: string; + statement: TextRange; +} + +type WithDefaults = Pick; + +type DefineEmits = CallExpressionRange & { + name?: string; + hasUnionTypeArg?: boolean; + statement: TextRange; +} + +type DefineSlots = CallExpressionRange & { + name?: string; + isObjectBindingPattern?: boolean; + statement: TextRange; +} + +type DefineExpose = CallExpressionRange; + +type DefineOptions = { + name?: string; + inheritAttrs?: string; +} + +type UseAttrs = CallExpressionRange; + +type UseCssModule = CallExpressionRange; + +type UseSlots = CallExpressionRange; + +type UseTemplateRef = CallExpressionRange & { + name?: string; +} + export interface ScriptSetupRanges extends ReturnType { } export function parseScriptSetupRanges( @@ -11,64 +67,20 @@ export function parseScriptSetupRanges( ast: ts.SourceFile, vueCompilerOptions: VueCompilerOptions ) { - - let foundNonImportExportNode = false; - let importSectionEndOffset = 0; - - const props: { - name?: string; - destructured?: Set; - destructuredRest?: string; - define?: ReturnType & { - statement: TextRange; - }; - withDefaults?: TextRange & { - arg?: TextRange; - }; - } = {}; - const slots: { - name?: string; - isObjectBindingPattern?: boolean; - define?: ReturnType & { - statement: TextRange; - }; - } = {}; - const emits: { - name?: string; - define?: ReturnType & { - statement: TextRange; - hasUnionTypeArg?: boolean; - }; - } = {}; - const expose: { - name?: string; - define?: ReturnType; - } = {}; - const options: { - name?: string; - inheritAttrs?: string; - } = {}; - const cssModules: { - define: ReturnType; - }[] = []; - const templateRefs: { - name?: string; - define: ReturnType; - }[] = []; + const defineProp: DefineProp[] = []; + let defineProps: DefineProps | undefined; + let withDefaults: WithDefaults | undefined; + let defineEmits: DefineEmits | undefined; + let defineSlots: DefineSlots | undefined; + let defineExpose: DefineExpose | undefined; + let defineOptions: DefineOptions | undefined; + const useAttrs: UseAttrs[] = []; + const useCssModule: UseCssModule[] = []; + const useSlots: UseSlots[] = []; + const useTemplateRef: UseTemplateRef[] = []; const definePropProposalA = vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition'); const definePropProposalB = vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition'); - const defineProp: { - localName: TextRange | undefined; - name: TextRange | undefined; - type: TextRange | undefined; - modifierType?: TextRange | undefined; - runtimeType: TextRange | undefined; - defaultValue: TextRange | undefined; - required: boolean; - isModel?: boolean; - }[] = []; const text = ast.text; - const importComponentNames = new Set(); const leadingCommentRanges = ts.getLeadingCommentRanges(text, 0)?.reverse() ?? []; const leadingCommentEndOffset = leadingCommentRanges.find( @@ -76,9 +88,13 @@ export function parseScriptSetupRanges( )?.end ?? 0; let bindings = parseBindingRanges(ts, ast); + let foundNonImportExportNode = false; + let importSectionEndOffset = 0; ts.forEachChild(ast, node => { - const isTypeExport = (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); + const isTypeExport = + (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) + && node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); if ( !foundNonImportExportNode && !ts.isImportDeclaration(node) @@ -97,22 +113,11 @@ export function parseScriptSetupRanges( } foundNonImportExportNode = true; } - - if ( - ts.isImportDeclaration(node) - && node.importClause?.name - && !node.importClause.isTypeOnly - ) { - const moduleName = getNodeText(ts, node.moduleSpecifier, ast).slice(1, -1); - if (vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))) { - importComponentNames.add(getNodeText(ts, node.importClause.name, ast)); - } - } }); - ts.forEachChild(ast, child => visitNode(child, [ast])); + ts.forEachChild(ast, node => visitNode(node, [ast])); - const templateRefNames = new Set(templateRefs.map(ref => ref.name)); - bindings = bindings.filter(range => { + const templateRefNames = new Set(useTemplateRef.map(ref => ref.name)); + bindings = bindings.filter(({ range }) => { const name = text.slice(range.start, range.end); return !templateRefNames.has(name); }); @@ -121,37 +126,26 @@ export function parseScriptSetupRanges( leadingCommentEndOffset, importSectionEndOffset, bindings, - importComponentNames, - props, - slots, - emits, - expose, - options, - cssModules, defineProp, - templateRefs, + defineProps, + withDefaults, + defineEmits, + defineSlots, + defineExpose, + defineOptions, + useAttrs, + useCssModule, + useSlots, + useTemplateRef, }; - function _getStartEnd(node: ts.Node) { - return getStartEnd(ts, node, ast); - } - - function parseDefineFunction(node: ts.CallExpression) { - return { - ..._getStartEnd(node), - exp: _getStartEnd(node.expression), - arg: node.arguments.length ? _getStartEnd(node.arguments[0]) : undefined, - typeArg: node.typeArguments?.length ? _getStartEnd(node.typeArguments[0]) : undefined, - }; - } - function visitNode(node: ts.Node, parents: ts.Node[]) { const parent = parents[parents.length - 1]; if ( ts.isCallExpression(node) && ts.isIdentifier(node.expression) ) { - const callText = getNodeText(ts, node.expression, ast); + const callText = _getNodeText(node.expression); if (vueCompilerOptions.macros.defineModel.includes(callText)) { let localName: TextRange | undefined; let propName: TextRange | undefined; @@ -169,7 +163,7 @@ export function parseScriptSetupRanges( options = node.arguments[1]; } else if (node.arguments.length >= 1) { - if (ts.isStringLiteral(node.arguments[0])) { + if (ts.isStringLiteralLike(node.arguments[0])) { propName = _getStartEnd(node.arguments[0]); } else { @@ -185,7 +179,7 @@ export function parseScriptSetupRanges( if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) { continue; } - const text = getNodeText(ts, property.name, ast); + const text = _getNodeText(property.name); if (text === 'type') { runtimeType = _getStartEnd(property.initializer); } @@ -236,7 +230,7 @@ export function parseScriptSetupRanges( if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) { continue; } - const text = getNodeText(ts, property.name, ast); + const text = _getNodeText(property.name); if (text === 'type') { runtimeType = _getStartEnd(property.initializer); } @@ -267,7 +261,7 @@ export function parseScriptSetupRanges( if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) { continue; } - const text = getNodeText(ts, property.name, ast); + const text = _getNodeText(property.name); if (text === 'type') { runtimeType = _getStartEnd(property.initializer); } @@ -284,209 +278,269 @@ export function parseScriptSetupRanges( required, }); } - else if (vueCompilerOptions.macros.defineSlots.includes(callText)) { - slots.define = { - ...parseDefineFunction(node), + else if (vueCompilerOptions.macros.defineProps.includes(callText)) { + defineProps = { + ...parseCallExpression(node), statement: getStatementRange(ts, parents, node, ast) }; if (ts.isVariableDeclaration(parent)) { - if (ts.isIdentifier(parent.name)) { - slots.name = getNodeText(ts, parent.name, ast); + if (ts.isObjectBindingPattern(parent.name)) { + defineProps.destructured = new Set(); + const identifiers = collectIdentifiers(ts, parent.name, []); + for (const [id, isRest] of identifiers) { + const name = _getNodeText(id); + if (isRest) { + defineProps.destructuredRest = name; + } + else { + defineProps.destructured.add(name); + } + } } else { - slots.isObjectBindingPattern = ts.isObjectBindingPattern(parent.name); + defineProps.name = _getNodeText(parent.name); + } + } + else if ( + ts.isCallExpression(parent) + && vueCompilerOptions.macros.withDefaults.includes(_getNodeText(parent.expression)) + ) { + const grand = parents.at(-2); + if (grand && ts.isVariableDeclaration(grand)) { + defineProps.name = _getNodeText(grand.name); } } } + else if (vueCompilerOptions.macros.withDefaults.includes(callText)) { + const [, arg] = node.arguments; + withDefaults = { + callExp: _getStartEnd(node), + exp: _getStartEnd(node.expression), + arg: arg ? _getStartEnd(arg) : undefined + }; + } else if (vueCompilerOptions.macros.defineEmits.includes(callText)) { - emits.define = { - ...parseDefineFunction(node), + defineEmits = { + ...parseCallExpression(node), statement: getStatementRange(ts, parents, node, ast) }; if (ts.isVariableDeclaration(parent)) { - emits.name = getNodeText(ts, parent.name, ast); + defineEmits.name = _getNodeText(parent.name); } - if (node.typeArguments?.length && ts.isTypeLiteralNode(node.typeArguments[0]) && node.typeArguments[0].members.at(0)) { + if (node.typeArguments?.length && ts.isTypeLiteralNode(node.typeArguments[0])) { for (const member of node.typeArguments[0].members) { - if (ts.isCallSignatureDeclaration(member) && member.parameters[0].type && ts.isUnionTypeNode(member.parameters[0].type)) { - emits.define.hasUnionTypeArg = true; - return; + if (ts.isCallSignatureDeclaration(member)) { + const type = member.parameters[0]?.type; + if (type && ts.isUnionTypeNode(type)) { + defineEmits.hasUnionTypeArg = true; + break; + } } } } } - else if (vueCompilerOptions.macros.defineExpose.includes(callText)) { - expose.define = parseDefineFunction(node); - } - else if (vueCompilerOptions.macros.defineProps.includes(callText)) { + else if (vueCompilerOptions.macros.defineSlots.includes(callText)) { + defineSlots = { + ...parseCallExpression(node), + statement: getStatementRange(ts, parents, node, ast) + }; if (ts.isVariableDeclaration(parent)) { - if (ts.isObjectBindingPattern(parent.name)) { - props.destructured = new Set(); - const identifiers = collectIdentifiers(ts, parent.name, []); - for (const [id, isRest] of identifiers) { - const name = getNodeText(ts, id, ast); - if (isRest) { - props.destructuredRest = name; - } - else { - props.destructured.add(name); - } - } + if (ts.isIdentifier(parent.name)) { + defineSlots.name = _getNodeText(parent.name); } else { - props.name = getNodeText(ts, parent.name, ast); + defineSlots.isObjectBindingPattern = ts.isObjectBindingPattern(parent.name); } } - - props.define = { - ...parseDefineFunction(node), - statement: getStatementRange(ts, parents, node, ast), - }; - - if (node.arguments.length) { - props.define.arg = _getStartEnd(node.arguments[0]); - } - if (node.typeArguments?.length) { - props.define.typeArg = _getStartEnd(node.typeArguments[0]); - } } - else if (vueCompilerOptions.macros.withDefaults.includes(callText)) { - props.withDefaults = _getStartEnd(node); - if (node.arguments.length >= 2) { - const arg = node.arguments[1]; - props.withDefaults.arg = _getStartEnd(arg); - } - if (ts.isVariableDeclaration(parent)) { - props.name = getNodeText(ts, parent.name, ast); - } + else if (vueCompilerOptions.macros.defineExpose.includes(callText)) { + defineExpose = parseCallExpression(node); } - else if (vueCompilerOptions.macros.defineOptions.includes(callText)) { - if (node.arguments.length && ts.isObjectLiteralExpression(node.arguments[0])) { - const obj = node.arguments[0]; - ts.forEachChild(obj, node => { - if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) { - const name = getNodeText(ts, node.name, ast); - if (name === 'inheritAttrs') { - options.inheritAttrs = getNodeText(ts, node.initializer, ast); - } + else if ( + vueCompilerOptions.macros.defineOptions.includes(callText) + && node.arguments.length + && ts.isObjectLiteralExpression(node.arguments[0]) + ) { + defineOptions = {}; + const obj = node.arguments[0]; + for (const prop of obj.properties) { + if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) { + const name = _getNodeText(prop.name); + if (name === 'inheritAttrs') { + defineOptions.inheritAttrs = _getNodeText(prop.initializer); } - }); - for (const prop of node.arguments[0].properties) { - if (ts.isPropertyAssignment(prop) && getNodeText(ts, prop.name, ast) === 'name' && ts.isStringLiteral(prop.initializer)) { - options.name = prop.initializer.text; + else if (name === 'name' && ts.isStringLiteral(prop.initializer)) { + defineOptions.name = prop.initializer.text; } } } - } else if (vueCompilerOptions.composibles.useTemplateRef.includes(callText) && node.arguments.length && !node.typeArguments?.length) { - const define = parseDefineFunction(node); - let name; - if (ts.isVariableDeclaration(parent)) { - name = getNodeText(ts, parent.name, ast); - } - templateRefs.push({ - name, - define - }); } - else if (vueCompilerOptions.composibles.useCssModule.includes(callText)) { - const define = parseDefineFunction(node); - cssModules.push({ - define + else if (vueCompilerOptions.composables.useAttrs.includes(callText)) { + useAttrs.push(parseCallExpression(node)); + } + else if (vueCompilerOptions.composables.useCssModule.includes(callText)) { + useCssModule.push(parseCallExpression(node)); + } + else if (vueCompilerOptions.composables.useSlots.includes(callText)) { + useSlots.push(parseCallExpression(node)); + } + else if ( + vueCompilerOptions.composables.useTemplateRef.includes(callText) + && !node.typeArguments?.length + ) { + useTemplateRef.push({ + name: ts.isVariableDeclaration(parent) ? _getNodeText(parent.name) : undefined, + ...parseCallExpression(node) }); } } + ts.forEachChild(node, child => { parents.push(node); visitNode(child, parents); parents.pop(); }); } + + function parseCallExpression(node: ts.CallExpression) { + return { + callExp: _getStartEnd(node), + exp: _getStartEnd(node.expression), + arg: node.arguments.length ? _getStartEnd(node.arguments[0]) : undefined, + typeArg: node.typeArguments?.length ? _getStartEnd(node.typeArguments[0]) : undefined, + }; + } + + function _getStartEnd(node: ts.Node) { + return getStartEnd(ts, node, ast); + } + + function _getNodeText(node: ts.Node) { + return getNodeText(ts, node, ast); + } } -export function parseBindingRanges(ts: typeof import('typescript'), sourceFile: ts.SourceFile) { - const bindings: TextRange[] = []; - ts.forEachChild(sourceFile, node => { +export function parseBindingRanges(ts: typeof import('typescript'), ast: ts.SourceFile) { + const bindings: { + range: TextRange; + moduleName?: string; + isDefaultImport?: boolean; + isNamespace?: boolean; + }[] = []; + + ts.forEachChild(ast, node => { if (ts.isVariableStatement(node)) { - for (const node_2 of node.declarationList.declarations) { - const vars = _findBindingVars(node_2.name); - for (const _var of vars) { - bindings.push(_var); - } + for (const decl of node.declarationList.declarations) { + const vars = _findBindingVars(decl.name); + bindings.push(...vars.map(range => ({ range }))); } } else if (ts.isFunctionDeclaration(node)) { if (node.name && ts.isIdentifier(node.name)) { - bindings.push(_getStartEnd(node.name)); + bindings.push({ + range: _getStartEnd(node.name) + }); } } else if (ts.isClassDeclaration(node)) { if (node.name) { - bindings.push(_getStartEnd(node.name)); + bindings.push({ + range: _getStartEnd(node.name) + }); } } else if (ts.isEnumDeclaration(node)) { - bindings.push(_getStartEnd(node.name)); + bindings.push({ + range: _getStartEnd(node.name) + }); } if (ts.isImportDeclaration(node)) { + const moduleName = _getNodeText(node.moduleSpecifier).slice(1, -1); + if (node.importClause && !node.importClause.isTypeOnly) { - if (node.importClause.name) { - bindings.push(_getStartEnd(node.importClause.name)); + const { name, namedBindings } = node.importClause; + + if (name) { + bindings.push({ + range: _getStartEnd(name), + moduleName, + isDefaultImport: true + }); } - if (node.importClause.namedBindings) { - if (ts.isNamedImports(node.importClause.namedBindings)) { - for (const element of node.importClause.namedBindings.elements) { + if (namedBindings) { + if (ts.isNamedImports(namedBindings)) { + for (const element of namedBindings.elements) { if (element.isTypeOnly) { continue; } - bindings.push(_getStartEnd(element.name)); + bindings.push({ + range: _getStartEnd(element.name), + moduleName, + isDefaultImport: element.propertyName?.text === 'default' + }); } } - else if (ts.isNamespaceImport(node.importClause.namedBindings)) { - bindings.push(_getStartEnd(node.importClause.namedBindings.name)); + else { + bindings.push({ + range: _getStartEnd(namedBindings.name), + moduleName, + isNamespace: true + }); } } } } }); + return bindings; + function _getStartEnd(node: ts.Node) { - return getStartEnd(ts, node, sourceFile); + return getStartEnd(ts, node, ast); + } + + function _getNodeText(node: ts.Node) { + return getNodeText(ts, node, ast); } + function _findBindingVars(left: ts.BindingName) { - return findBindingVars(ts, left, sourceFile); + return findBindingVars(ts, left, ast); } } -export function findBindingVars(ts: typeof import('typescript'), left: ts.BindingName, sourceFile: ts.SourceFile) { +export function findBindingVars( + ts: typeof import('typescript'), + left: ts.BindingName, + ast: ts.SourceFile +) { const vars: TextRange[] = []; worker(left); return vars; - function worker(_node: ts.Node) { - if (ts.isIdentifier(_node)) { - vars.push(getStartEnd(ts, _node, sourceFile)); + function worker(node: ts.Node) { + if (ts.isIdentifier(node)) { + vars.push(getStartEnd(ts, node, ast)); } // { ? } = ... // [ ? ] = ... - else if (ts.isObjectBindingPattern(_node) || ts.isArrayBindingPattern(_node)) { - for (const property of _node.elements) { + else if (ts.isObjectBindingPattern(node) || ts.isArrayBindingPattern(node)) { + for (const property of node.elements) { if (ts.isBindingElement(property)) { worker(property.name); } } } // { foo: ? } = ... - else if (ts.isPropertyAssignment(_node)) { - worker(_node.initializer); + else if (ts.isPropertyAssignment(node)) { + worker(node.initializer); } // { foo } = ... - else if (ts.isShorthandPropertyAssignment(_node)) { - vars.push(getStartEnd(ts, _node.name, sourceFile)); + else if (ts.isShorthandPropertyAssignment(node)) { + vars.push(getStartEnd(ts, node.name, ast)); } // { ...? } = ... // [ ...? ] = ... - else if (ts.isSpreadAssignment(_node) || ts.isSpreadElement(_node)) { - worker(_node.expression); + else if (ts.isSpreadAssignment(node) || ts.isSpreadElement(node)) { + worker(node.expression); } } } @@ -494,10 +548,10 @@ export function findBindingVars(ts: typeof import('typescript'), left: ts.Bindin export function getStartEnd( ts: typeof import('typescript'), node: ts.Node, - sourceFile: ts.SourceFile + ast: ts.SourceFile ): TextRange { return { - start: (ts as any).getTokenPosOfNode(node, sourceFile) as number, + start: (ts as any).getTokenPosOfNode(node, ast) as number, end: node.end, }; } @@ -505,24 +559,24 @@ export function getStartEnd( export function getNodeText( ts: typeof import('typescript'), node: ts.Node, - sourceFile: ts.SourceFile + ast: ts.SourceFile ) { - const { start, end } = getStartEnd(ts, node, sourceFile); - return sourceFile.text.slice(start, end); + const { start, end } = getStartEnd(ts, node, ast); + return ast.text.slice(start, end); } function getStatementRange( ts: typeof import('typescript'), parents: ts.Node[], node: ts.Node, - sourceFile: ts.SourceFile + ast: ts.SourceFile ) { let statementRange: TextRange | undefined; for (let i = parents.length - 1; i >= 0; i--) { if (ts.isStatement(parents[i])) { const statement = parents[i]; ts.forEachChild(statement, child => { - const range = getStartEnd(ts, child, sourceFile); + const range = getStartEnd(ts, child, ast); statementRange ??= range; statementRange.end = range.end; }); @@ -530,7 +584,7 @@ function getStatementRange( } } if (!statementRange) { - statementRange = getStartEnd(ts, node, sourceFile); + statementRange = getStartEnd(ts, node, ast); } return statementRange; -} \ No newline at end of file +} diff --git a/packages/language-core/lib/plugins/file-html.ts b/packages/language-core/lib/plugins/file-html.ts index 2bb52b7d9e..4c548dcf40 100644 --- a/packages/language-core/lib/plugins/file-html.ts +++ b/packages/language-core/lib/plugins/file-html.ts @@ -66,8 +66,8 @@ const plugin: VueLanguagePlugin = ({ vueCompilerOptions }) => { }); } // ignore `\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)" + "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 `