From 78fcfdd42827f95ccfb8e32e2a340a855c3843c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Sat, 19 Jul 2025 22:16:22 +0800 Subject: [PATCH 01/85] fix(language-core): avoid clearing global types path when local compiler options is present --- packages/language-core/lib/types.ts | 2 +- packages/language-core/lib/utils/ts.ts | 50 +++++++++++++------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/packages/language-core/lib/types.ts b/packages/language-core/lib/types.ts index 999625b4e9..7bffaac10a 100644 --- a/packages/language-core/lib/types.ts +++ b/packages/language-core/lib/types.ts @@ -26,7 +26,7 @@ export type Code = Segment; export interface VueCompilerOptions { target: number; lib: string; - globalTypesPath: (fileName: string) => string | undefined; + globalTypesPath: (fileName: string) => string | void; extensions: string[]; vitePressExtensions: string[]; petiteVueExtensions: string[]; diff --git a/packages/language-core/lib/utils/ts.ts b/packages/language-core/lib/utils/ts.ts index aaec7af426..5c26a694fb 100644 --- a/packages/language-core/lib/utils/ts.ts +++ b/packages/language-core/lib/utils/ts.ts @@ -1,4 +1,4 @@ -import { camelize } from '@vue/shared'; +import { camelize, NOOP as noop } from '@vue/shared'; import { posix as path } from 'path-browserify'; import type * as ts from 'typescript'; import { generateGlobalTypes, getGlobalTypesFileName } from '../codegen/globalTypes'; @@ -232,30 +232,32 @@ export class CompilerOptionsResolver { ), }; - if (this.fileExists && this.globalTypesPath === undefined) { - const fileDirToGlobalTypesPath = new Map(); - resolvedOptions.globalTypesPath = fileName => { - const fileDir = path.dirname(fileName); - if (fileDirToGlobalTypesPath.has(fileDir)) { - return fileDirToGlobalTypesPath.get(fileDir); - } + if (resolvedOptions.globalTypesPath === noop) { + if (this.fileExists && this.globalTypesPath === undefined) { + const fileDirToGlobalTypesPath = new Map(); + resolvedOptions.globalTypesPath = fileName => { + const fileDir = path.dirname(fileName); + if (fileDirToGlobalTypesPath.has(fileDir)) { + return fileDirToGlobalTypesPath.get(fileDir); + } - const root = this.findNodeModulesRoot(fileDir, resolvedOptions.lib); - const result = root - ? path.join( - root, - 'node_modules', - '.vue-global-types', - getGlobalTypesFileName(resolvedOptions), - ) - : undefined; + const root = this.findNodeModulesRoot(fileDir, resolvedOptions.lib); + const result = root + ? path.join( + root, + 'node_modules', + '.vue-global-types', + getGlobalTypesFileName(resolvedOptions), + ) + : undefined; - fileDirToGlobalTypesPath.set(fileDir, result); - return result; - }; - } - else { - resolvedOptions.globalTypesPath = () => this.globalTypesPath; + fileDirToGlobalTypesPath.set(fileDir, result); + return result; + }; + } + else { + resolvedOptions.globalTypesPath = () => this.globalTypesPath; + } } return resolvedOptions; @@ -303,7 +305,7 @@ export function getDefaultCompilerOptions(target = 99, lib = 'vue', strictTempla return { target, lib, - globalTypesPath: () => undefined, + globalTypesPath: noop, extensions: ['.vue'], vitePressExtensions: [], petiteVueExtensions: [], From 26fa1044a7cf6eaad8737212954e3f06b29380fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Sat, 19 Jul 2025 10:34:09 -0700 Subject: [PATCH 02/85] refactor(language-core): use `shouldReport` to handle unknown events and components checking (#5537) --- .../language-core/lib/codegen/codeFeatures.ts | 20 ++++++ .../language-core/lib/codegen/globalTypes.ts | 64 ++++++++----------- .../lib/codegen/template/element.ts | 16 ++--- .../lib/codegen/template/elementEvents.ts | 19 ++++-- .../lib/codegen/template/elementProps.ts | 15 ++--- 5 files changed, 72 insertions(+), 62 deletions(-) diff --git a/packages/language-core/lib/codegen/codeFeatures.ts b/packages/language-core/lib/codegen/codeFeatures.ts index 0baa2d992c..c7143649c7 100644 --- a/packages/language-core/lib/codegen/codeFeatures.ts +++ b/packages/language-core/lib/codegen/codeFeatures.ts @@ -72,6 +72,26 @@ const raw = { navigation: true, completion: true, }, + doNotReportTs2339AndTs2551: { + verification: { + // https://typescript.tv/errors/#ts2339 + // https://typescript.tv/errors/#ts2551 + shouldReport: (_source, code) => String(code) !== '2339' && String(code) !== '2551', + }, + }, + doNotReportTs2353AndTs2561: { + verification: { + // https://typescript.tv/errors/#ts2353 + // https://typescript.tv/errors/#ts2561 + shouldReport: (_source, code) => String(code) !== '2353' && String(code) !== '2561', + }, + }, + doNotReportTs6133: { + verification: { + // https://typescript.tv/errors/#ts6133 + shouldReport: (_source, code) => String(code) !== '6133', + }, + }, } satisfies Record; export const codeFeatures = raw as { diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index c03bd3bf49..5851bb1899 100644 --- a/packages/language-core/lib/codegen/globalTypes.ts +++ b/packages/language-core/lib/codegen/globalTypes.ts @@ -1,29 +1,17 @@ import type { VueCompilerOptions } from '../types'; import { getSlotsPropertyName } from '../utils/shared'; -export function getGlobalTypesFileName({ - lib, - target, - checkUnknownProps, - checkUnknownEvents, - checkUnknownComponents, -}: VueCompilerOptions) { +export function getGlobalTypesFileName(options: VueCompilerOptions) { return [ - lib, - target, - checkUnknownProps, - checkUnknownEvents, - checkUnknownComponents, + options.lib, + options.target, + options.checkUnknownProps, ].map(v => (typeof v === 'boolean' ? Number(v) : v)).join('_') + '.d.ts'; } -export function generateGlobalTypes({ - lib, - target, - checkUnknownProps, - checkUnknownEvents, - checkUnknownComponents, -}: VueCompilerOptions) { +export function generateGlobalTypes(options: VueCompilerOptions) { + const { lib, target, checkUnknownProps } = options; + const fnPropsType = `(T extends { $props: infer Props } ? Props : {})${ checkUnknownProps ? '' : ' & Record' }`; @@ -69,7 +57,7 @@ export function generateGlobalTypes({ 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] } : - ${checkUnknownComponents ? '{}' : '{ [K in N0]: unknown }'}; + {}; type __VLS_FunctionalComponentCtx = __VLS_PickNotAny<'__ctx' extends keyof __VLS_PickNotAny ? K extends { __ctx?: infer Ctx } ? NonNullable : never : any , T extends (props: any, ctx: infer Ctx) => any ? Ctx : any @@ -80,12 +68,12 @@ export function generateGlobalTypes({ : {}; type __VLS_FunctionalComponent = (props: ${fnPropsType}, ctx?: any) => __VLS_Element & { __ctx?: { - attrs?: any, - slots?: T extends { ${getSlotsPropertyName(target)}: infer Slots } ? Slots : Record, - emit?: T extends { $emit: infer Emit } ? Emit : {}, - props?: ${fnPropsType}, - expose?: (exposed: T) => void, - } + attrs?: any; + slots?: T extends { ${getSlotsPropertyName(target)}: infer Slots } ? Slots : Record; + emit?: T extends { $emit: infer Emit } ? Emit : {}; + props?: ${fnPropsType}; + expose?: (exposed: T) => void; + }; }; type __VLS_IsFunction = K extends keyof T ? __VLS_IsAny extends false @@ -94,15 +82,19 @@ export function generateGlobalTypes({ : true : false : false; - type __VLS_NormalizeComponentEvent = ( - __VLS_IsFunction extends true - ? Props - : __VLS_IsFunction extends true - ? { [K in onEvent]?: Emits[Event] } - : __VLS_IsFunction extends true - ? { [K in onEvent]?: Emits[CamelizedEvent] } - : Props - )${checkUnknownEvents ? '' : ' & Record'}; + type __VLS_NormalizeComponentEvent< + Props, + Emits, + onEvent extends keyof Props, + Event extends keyof Emits, + CamelizedEvent extends keyof Emits, + > = __VLS_IsFunction extends true + ? Props + : __VLS_IsFunction extends true + ? { [K in onEvent]?: Emits[Event] } + : __VLS_IsFunction extends true + ? { [K in onEvent]?: Emits[CamelizedEvent] } + : Props; // 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 @@ -174,7 +166,7 @@ export function generateGlobalTypes({ : __VLS_FunctionalComponent<{}>; function __VLS_functionalComponentArgsRest any>(t: T): 2 extends Parameters['length'] ? [any] : []; function __VLS_asFunctionalElement(tag: T, endTag?: T): (attrs: T${ - checkUnknownComponents ? '' : ' & Record' + checkUnknownProps ? '' : ' & Record' }) => void; function __VLS_asFunctionalSlot(slot: S): S extends () => infer R ? (props: {}) => R : NonNullable; function __VLS_tryAsConstant(t: T): T; diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index f5254adbe2..55e28e1b0e 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -152,7 +152,12 @@ export function* generateComponent( yield* generateCanonicalComponentName( node.tag, tagOffsets[0], - ctx.codeFeatures.withoutHighlightAndCompletionAndNavigation, + ctx.resolveCodeFeatures({ + ...codeFeatures.semanticWithoutHighlight, + ...options.vueCompilerOptions.checkUnknownComponents + ? codeFeatures.verification + : codeFeatures.doNotReportTs2339AndTs2551, + }), ); yield `${endOfLine}`; @@ -207,14 +212,7 @@ export function* generateComponent( yield* wrapWith( node.loc.start.offset, node.loc.end.offset, - ctx.resolveCodeFeatures({ - verification: { - shouldReport(_source, code) { - // https://typescript.tv/errors/#ts6133 - return String(code) !== '6133'; - }, - }, - }), + ctx.codeFeatures.doNotReportTs6133, componentVNodeVar, ); yield ` = ${componentFunctionalVar}`; diff --git a/packages/language-core/lib/codegen/template/elementEvents.ts b/packages/language-core/lib/codegen/template/elementEvents.ts index 95cef6fadd..132fbe65a7 100644 --- a/packages/language-core/lib/codegen/template/elementEvents.ts +++ b/packages/language-core/lib/codegen/template/elementEvents.ts @@ -2,6 +2,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 { codeFeatures } from '../codeFeatures'; import { combineLastMapping, createTsAst, endOfLine, identifierRegex, newLine } from '../utils'; import { generateCamelized } from '../utils/camelized'; import { wrapWith } from '../utils/wrapWith'; @@ -61,12 +62,12 @@ export function* generateElementEvents( yield `const ${ctx.getInternalVariable()}: __VLS_NormalizeComponentEvent = (${newLine}`; if (prop.name === 'on') { yield `{ `; - yield* generateEventArg(ctx, source, start!, emitPrefix.slice(0, -1), ctx.codeFeatures.navigation); + yield* generateEventArg(options, ctx, source, start!, emitPrefix.slice(0, -1), ctx.codeFeatures.navigation); yield `: {} as any } as typeof ${emitsVar},${newLine}`; } yield `{ `; if (prop.name === 'on') { - yield* generateEventArg(ctx, source, start!, propPrefix.slice(0, -1)); + yield* generateEventArg(options, ctx, source, start!, propPrefix.slice(0, -1)); yield `: `; yield* generateEventExpression(options, ctx, prop); } @@ -80,15 +81,21 @@ export function* generateElementEvents( } export function* generateEventArg( + options: TemplateCodegenOptions, ctx: TemplateCodegenContext, name: string, start: number, directive = 'on', - features: VueCodeInformation = { - ...ctx.codeFeatures.withoutHighlightAndCompletion, - ...ctx.codeFeatures.navigationWithoutRename, - }, + features?: VueCodeInformation, ): Generator { + features ??= ctx.resolveCodeFeatures({ + ...codeFeatures.semanticWithoutHighlight, + ...codeFeatures.navigationWithoutRename, + ...options.vueCompilerOptions.checkUnknownEvents + ? codeFeatures.verification + : codeFeatures.doNotReportTs2353AndTs2561, + }); + if (directive.length) { name = capitalize(name); } diff --git a/packages/language-core/lib/codegen/template/elementProps.ts b/packages/language-core/lib/codegen/template/elementProps.ts index 62dec6978d..57dc63eb40 100644 --- a/packages/language-core/lib/codegen/template/elementProps.ts +++ b/packages/language-core/lib/codegen/template/elementProps.ts @@ -46,7 +46,7 @@ export function* generateElementProps( ) { if (!isComponent) { yield `...{ `; - yield* generateEventArg(ctx, prop.arg.loc.source, prop.arg.loc.start.offset); + yield* generateEventArg(options, ctx, prop.arg.loc.source, prop.arg.loc.start.offset); yield `: `; yield* generateEventExpression(options, ctx, prop); yield `},`; @@ -378,16 +378,9 @@ function getPropsCodeInfo( ): VueCodeInformation { return ctx.resolveCodeFeatures({ ...codeFeatures.withoutHighlightAndCompletion, - verification: strictPropsCheck || { - shouldReport(_source, code) { - // https://typescript.tv/errors/#ts2353 - // https://typescript.tv/errors/#ts2561 - if (String(code) === '2353' || String(code) === '2561') { - return false; - } - return true; - }, - }, + ...strictPropsCheck + ? codeFeatures.verification + : codeFeatures.doNotReportTs2353AndTs2561, }); } From b930d3a58ce600633263c1755be6e782a629958f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Mon, 21 Jul 2025 21:46:26 +0800 Subject: [PATCH 03/85] refactor(language-service): correct name and simplify logic of some plugins (#5521) --- .../lib/plugins/vue-root-tags.ts | 11 +- packages/language-service/index.ts | 10 +- packages/language-service/lib/plugins/css.ts | 16 +-- .../lib/plugins/typescript-semantic-tokens.ts | 2 +- .../lib/plugins/vue-autoinsert-dotvalue.ts | 25 ++-- .../lib/plugins/vue-compiler-dom-errors.ts | 4 - .../plugins/vue-component-semantic-tokens.ts | 2 +- .../lib/plugins/vue-document-links.ts | 131 ------------------ .../lib/plugins/vue-extract-file.ts | 2 +- .../lib/plugins/vue-global-types-error.ts | 2 +- .../lib/plugins/vue-inlayhints.ts | 22 +-- .../lib/plugins/vue-missing-props-hints.ts | 7 +- .../lib/plugins/vue-scoped-class-links.ts | 77 ++++++++++ .../lib/plugins/vue-template-ref-links.ts | 72 ++++++++++ .../lib/plugins/vue-template.ts | 60 ++++---- 15 files changed, 220 insertions(+), 223 deletions(-) delete mode 100644 packages/language-service/lib/plugins/vue-document-links.ts create mode 100644 packages/language-service/lib/plugins/vue-scoped-class-links.ts create mode 100644 packages/language-service/lib/plugins/vue-template-ref-links.ts diff --git a/packages/language-core/lib/plugins/vue-root-tags.ts b/packages/language-core/lib/plugins/vue-root-tags.ts index 6a010b1d76..cee5cedd2e 100644 --- a/packages/language-core/lib/plugins/vue-root-tags.ts +++ b/packages/language-core/lib/plugins/vue-root-tags.ts @@ -18,9 +18,9 @@ const plugin: VueLanguagePlugin = () => { embeddedFile.content.push([sfc.content, undefined, 0, allCodeFeatures]); for ( const block of [ + sfc.template, sfc.script, sfc.scriptSetup, - sfc.template, ...sfc.styles, ...sfc.customBlocks, ] @@ -28,14 +28,7 @@ const plugin: VueLanguagePlugin = () => { if (!block) { continue; } - let content = block.content; - if (content.endsWith('\r\n')) { - content = content.slice(0, -2); - } - else if (content.endsWith('\n')) { - content = content.slice(0, -1); - } - const offset = content.lastIndexOf('\n') + 1; + const offset = block.content.lastIndexOf('\n', block.content.lastIndexOf('\n') - 1) + 1; // fix folding range end position failed to mapping replaceSourceRange( embeddedFile.content, diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index aa1a7856b2..bbdfb44909 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -15,20 +15,21 @@ import { create as createTypeScriptSyntacticPlugin } from 'volar-service-typescr import { create as createCssPlugin } from './lib/plugins/css'; import { create as createTypescriptSemanticTokensPlugin } from './lib/plugins/typescript-semantic-tokens'; import { create as createVueAutoDotValuePlugin } from './lib/plugins/vue-autoinsert-dotvalue'; -import { create as createVueAutoAddSpacePlugin } from './lib/plugins/vue-autoinsert-space'; +import { create as createVueAutoSpacePlugin } from './lib/plugins/vue-autoinsert-space'; import { create as createVueCompilerDomErrorsPlugin } from './lib/plugins/vue-compiler-dom-errors'; import { create as createVueComponentSemanticTokensPlugin } from './lib/plugins/vue-component-semantic-tokens'; import { create as createVueDirectiveCommentsPlugin } from './lib/plugins/vue-directive-comments'; import { create as createVueDocumentDropPlugin } from './lib/plugins/vue-document-drop'; import { create as createVueDocumentHighlightsPlugin } from './lib/plugins/vue-document-highlights'; -import { create as createVueDocumentLinksPlugin } from './lib/plugins/vue-document-links'; import { create as createVueExtractFilePlugin } from './lib/plugins/vue-extract-file'; import { create as createVueGlobalTypesErrorPlugin } from './lib/plugins/vue-global-types-error'; import { create as createVueInlayHintsPlugin } from './lib/plugins/vue-inlayhints'; import { create as createVueMissingPropsHintsPlugin } from './lib/plugins/vue-missing-props-hints'; +import { create as createVueScopedClassLinksPlugin } from './lib/plugins/vue-scoped-class-links'; import { create as createVueSfcPlugin } from './lib/plugins/vue-sfc'; import { create as createVueSuggestDefineAssignmentPlugin } from './lib/plugins/vue-suggest-define-assignment'; import { create as createVueTemplatePlugin } from './lib/plugins/vue-template'; +import { create as createVueTemplateRefLinksPlugin } from './lib/plugins/vue-template-ref-links'; import { create as createVueTwoslashQueriesPlugin } from './lib/plugins/vue-twoslash-queries'; declare module '@volar/language-service' { @@ -55,21 +56,22 @@ export function createVueLanguageServicePlugins( createTypeScriptDocCommentTemplatePlugin(ts), createTypescriptSemanticTokensPlugin(getTsPluginClient), createTypeScriptSyntacticPlugin(ts), - createVueAutoAddSpacePlugin(), + createVueAutoSpacePlugin(), createVueAutoDotValuePlugin(ts, getTsPluginClient), createVueCompilerDomErrorsPlugin(), createVueComponentSemanticTokensPlugin(getTsPluginClient), createVueDocumentDropPlugin(ts, getTsPluginClient), - createVueDocumentLinksPlugin(), createVueDirectiveCommentsPlugin(), createVueExtractFilePlugin(ts, getTsPluginClient), createVueGlobalTypesErrorPlugin(), createVueInlayHintsPlugin(ts), createVueMissingPropsHintsPlugin(getTsPluginClient), + createVueScopedClassLinksPlugin(), createVueSfcPlugin(), createVueSuggestDefineAssignmentPlugin(), createVueTemplatePlugin('html', getTsPluginClient), createVueTemplatePlugin('pug', getTsPluginClient), + createVueTemplateRefLinksPlugin(), createVueTwoslashQueriesPlugin(getTsPluginClient), createEmmetPlugin({ mappedLanguages: { diff --git a/packages/language-service/lib/plugins/css.ts b/packages/language-service/lib/plugins/css.ts index a9ae2246be..0df678bd71 100644 --- a/packages/language-service/lib/plugins/css.ts +++ b/packages/language-service/lib/plugins/css.ts @@ -1,5 +1,5 @@ import type { LanguageServicePlugin, TextDocument, VirtualCode } from '@volar/language-service'; -import { VueVirtualCode } from '@vue/language-core'; +import { isRenameEnabled, VueVirtualCode } from '@vue/language-core'; import { create as baseCreate, type Provide } from 'volar-service-css'; import type * as css from 'vscode-css-languageservice'; import { URI } from 'vscode-uri'; @@ -20,9 +20,11 @@ export function create(): LanguageServicePlugin { async provideDiagnostics(document, token) { let diagnostics = await baseInstance.provideDiagnostics?.(document, token) ?? []; if (document.languageId === 'postcss') { - diagnostics = diagnostics.filter(diag => diag.code !== 'css-semicolonexpected'); - diagnostics = diagnostics.filter(diag => diag.code !== 'css-ruleorselectorexpected'); - diagnostics = diagnostics.filter(diag => diag.code !== 'unknownAtRules'); + diagnostics = diagnostics.filter(diag => + diag.code !== 'css-semicolonexpected' + && diag.code !== 'css-ruleorselectorexpected' + && diag.code !== 'unknownAtRules' + ); } return diagnostics; }, @@ -83,11 +85,7 @@ export function create(): LanguageServicePlugin { const offset = document.offsetAt(position) + block.startTagEnd; for (const { sourceOffsets, lengths, data } of script.mappings) { - if ( - !sourceOffsets.length - || !data.navigation - || typeof data.navigation === 'object' && !data.navigation.shouldRename - ) { + if (!sourceOffsets.length || !isRenameEnabled(data)) { continue; } diff --git a/packages/language-service/lib/plugins/typescript-semantic-tokens.ts b/packages/language-service/lib/plugins/typescript-semantic-tokens.ts index 20949218f6..298a3c67e7 100644 --- a/packages/language-service/lib/plugins/typescript-semantic-tokens.ts +++ b/packages/language-service/lib/plugins/typescript-semantic-tokens.ts @@ -9,7 +9,7 @@ export function create( ) => import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { return { - name: 'typescript-highlights', + name: 'typescript-semantic-tokens', capabilities: { semanticTokensProvider: { legend: { diff --git a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts index 56416b93bc..da79c6c3cd 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts @@ -21,6 +21,7 @@ export function create( create(context) { const tsPluginClient = getTsPluginClient?.(context); let currentReq = 0; + return { async provideAutoInsertSnippet(document, selection, change) { // selection must at end of change @@ -67,32 +68,26 @@ export function create( return; } - let sourceCodeOffset = document.offsetAt(selection); - let mapped = false; - for (const [, map] of context.language.maps.forEach(virtualCode)) { - for (const [sourceOffset] of map.toSourceLocation(sourceCodeOffset)) { - sourceCodeOffset = sourceOffset; - mapped = true; - break; - } - if (mapped) { - break; - } + let sourceOffset: number | undefined; + const map = context.language.maps.get(virtualCode, sourceScript); + for (const [offset] of map.toSourceLocation(document.offsetAt(selection))) { + sourceOffset = offset; + break; } - if (!mapped) { + if (sourceOffset === undefined) { return; } for (const { ast, startTagEnd, endTagStart } of blocks) { - if (sourceCodeOffset < startTagEnd || sourceCodeOffset > endTagStart) { + if (sourceOffset < startTagEnd || sourceOffset > endTagStart) { continue; } - if (isBlacklistNode(ts, ast, sourceCodeOffset - startTagEnd, false)) { + if (isBlacklistNode(ts, ast, sourceOffset - startTagEnd, false)) { return; } } - const props = await tsPluginClient?.getPropertiesAtLocation(root.fileName, sourceCodeOffset) ?? []; + const props = await tsPluginClient?.getPropertiesAtLocation(root.fileName, sourceOffset) ?? []; if (props.some(prop => prop === 'value')) { return '${1:.value}'; } diff --git a/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts b/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts index 4621071b14..c62d2f783c 100644 --- a/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts +++ b/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts @@ -21,10 +21,6 @@ export function create(): LanguageServicePlugin { const uri = URI.parse(document.uri); const decoded = context.decodeEmbeddedDocumentUri(uri); const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!virtualCode) { - return; - } const root = sourceScript?.generated?.root; if (!(root instanceof VueVirtualCode)) { diff --git a/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts b/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts index 60de24a5a1..d4110da8d2 100644 --- a/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts +++ b/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts @@ -9,7 +9,7 @@ export function create( ) => import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { return { - name: 'vue-component-highlights', + name: 'vue-component-semantic-tokens', capabilities: { semanticTokensProvider: { legend: { diff --git a/packages/language-service/lib/plugins/vue-document-links.ts b/packages/language-service/lib/plugins/vue-document-links.ts deleted file mode 100644 index 59aba65179..0000000000 --- a/packages/language-service/lib/plugins/vue-document-links.ts +++ /dev/null @@ -1,131 +0,0 @@ -import type { DocumentLink, LanguageServicePlugin } from '@volar/language-service'; -import { type Sfc, tsCodegen, VueVirtualCode } from '@vue/language-core'; -import { URI } from 'vscode-uri'; - -export function create(): LanguageServicePlugin { - return { - name: 'vue-document-links', - capabilities: { - documentLinkProvider: {}, - }, - create(context) { - return { - provideDocumentLinks(document) { - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || (virtualCode?.id !== 'template' && virtualCode?.id !== 'scriptsetup_raw')) { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { - return; - } - - const { sfc } = root; - const codegen = tsCodegen.get(sfc); - const result: DocumentLink[] = []; - - if (virtualCode.id === 'template') { - const scopedClasses = codegen?.getGeneratedTemplate()?.scopedClasses ?? []; - const styleClasses = new Map(); - const option = root.vueCompilerOptions.resolveStyleClassNames; - - for (let i = 0; i < sfc.styles.length; i++) { - const style = sfc.styles[i]; - if (option === true || (option === 'scoped' && style.scoped)) { - for (const className of style.classNames) { - if (!styleClasses.has(className.text.slice(1))) { - styleClasses.set(className.text.slice(1), []); - } - styleClasses.get(className.text.slice(1))!.push({ - index: i, - style, - classOffset: className.offset, - }); - } - } - } - - for (const { className, offset } of scopedClasses) { - const styles = styleClasses.get(className); - if (styles) { - for (const style of styles) { - const styleDocumentUri = context.encodeEmbeddedDocumentUri(decoded![0], 'style_' + style.index); - const styleVirtualCode = sourceScript.generated.embeddedCodes.get('style_' + style.index); - if (!styleVirtualCode) { - continue; - } - const styleDocument = context.documents.get( - styleDocumentUri, - styleVirtualCode.languageId, - styleVirtualCode.snapshot, - ); - const start = styleDocument.positionAt(style.classOffset); - const end = styleDocument.positionAt(style.classOffset + className.length + 1); - result.push({ - range: { - start: document.positionAt(offset), - end: document.positionAt(offset + className.length), - }, - target: context.encodeEmbeddedDocumentUri(decoded![0], 'style_' + style.index) - + `#L${start.line + 1},${start.character + 1}-L${end.line + 1},${end.character + 1}`, - }); - } - } - } - } - else if (virtualCode.id === 'scriptsetup_raw') { - if (!sfc.scriptSetup) { - return; - } - - const templateVirtualCode = sourceScript.generated.embeddedCodes.get('template'); - if (!templateVirtualCode) { - return; - } - const templateDocumentUri = context.encodeEmbeddedDocumentUri(decoded![0], 'template'); - const templateDocument = context.documents.get( - templateDocumentUri, - templateVirtualCode.languageId, - templateVirtualCode.snapshot, - ); - - const templateRefs = codegen?.getGeneratedTemplate()?.templateRefs; - const useTemplateRefs = codegen?.getScriptSetupRanges()?.useTemplateRef ?? []; - - for (const { arg } of useTemplateRefs) { - if (!arg) { - continue; - } - - const name = sfc.scriptSetup.content.slice(arg.start + 1, arg.end - 1); - - for (const { offset } of templateRefs?.get(name) ?? []) { - const start = templateDocument.positionAt(offset); - const end = templateDocument.positionAt(offset + name.length); - - result.push({ - range: { - start: document.positionAt(arg.start + 1), - end: document.positionAt(arg.end - 1), - }, - target: templateDocumentUri - + `#L${start.line + 1},${start.character + 1}-L${end.line + 1},${end.character + 1}`, - }); - } - } - } - - return result; - }, - }; - }, - }; -} diff --git a/packages/language-service/lib/plugins/vue-extract-file.ts b/packages/language-service/lib/plugins/vue-extract-file.ts index d54c7f4d97..fcac7a576c 100644 --- a/packages/language-service/lib/plugins/vue-extract-file.ts +++ b/packages/language-service/lib/plugins/vue-extract-file.ts @@ -235,7 +235,7 @@ export function create( const props = toExtract.filter(p => !p.model); const models = toExtract.filter(p => p.model); if (props.length) { - lines.push(`defineProps<{ \n\t${props.map(p => `${p.name}: ${p.type};`).join('\n\t')}\n}>()`); + lines.push(`defineProps<{\n\t${props.map(p => `${p.name}: ${p.type};`).join('\n\t')}\n}>()`); } for (const model of models) { lines.push(`const ${model.name} = defineModel<${model.type}>('${model.name}', { required: true })`); diff --git a/packages/language-service/lib/plugins/vue-global-types-error.ts b/packages/language-service/lib/plugins/vue-global-types-error.ts index d9200d5a94..6fbb1c1f59 100644 --- a/packages/language-service/lib/plugins/vue-global-types-error.ts +++ b/packages/language-service/lib/plugins/vue-global-types-error.ts @@ -4,7 +4,7 @@ import { URI } from 'vscode-uri'; export function create(): LanguageServicePlugin { return { - name: 'vue-compiler-dom-errors', + name: 'vue-global-types-error', capabilities: { diagnosticProvider: { interFileDependencies: false, diff --git a/packages/language-service/lib/plugins/vue-inlayhints.ts b/packages/language-service/lib/plugins/vue-inlayhints.ts index 4302f18052..19c5b63ecc 100644 --- a/packages/language-service/lib/plugins/vue-inlayhints.ts +++ b/packages/language-service/lib/plugins/vue-inlayhints.ts @@ -5,7 +5,7 @@ import { URI } from 'vscode-uri'; export function create(ts: typeof import('typescript')): LanguageServicePlugin { return { - name: 'vue-inlay-hints', + name: 'vue-inlayhints', capabilities: { inlayHintProvider: {}, }, @@ -16,7 +16,12 @@ export function create(ts: typeof import('typescript')): LanguageServicePlugin { const decoded = context.decodeEmbeddedDocumentUri(uri); const sourceScript = decoded && context.language.scripts.get(decoded[0]); const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!(virtualCode instanceof VueVirtualCode)) { + if (!sourceScript?.generated || virtualCode?.id !== 'main') { + return; + } + + const root = sourceScript.generated.root; + if (!(root instanceof VueVirtualCode)) { return; } @@ -26,15 +31,16 @@ export function create(ts: typeof import('typescript')): LanguageServicePlugin { } const result: InlayHint[] = []; + const { sfc } = root; - const codegen = tsCodegen.get(virtualCode.sfc); + const codegen = tsCodegen.get(sfc); const inlayHints = [ ...codegen?.getGeneratedTemplate()?.inlayHints ?? [], ...codegen?.getGeneratedScript()?.inlayHints ?? [], ]; const scriptSetupRanges = codegen?.getScriptSetupRanges(); - if (scriptSetupRanges?.defineProps?.destructured && virtualCode.sfc.scriptSetup?.ast) { + if (scriptSetupRanges?.defineProps?.destructured && sfc.scriptSetup?.ast) { const setting = 'vue.inlayHints.destructuredProps'; const enabled = await getSettingEnabled(setting); @@ -42,7 +48,7 @@ export function create(ts: typeof import('typescript')): LanguageServicePlugin { for ( const [prop, isShorthand] of findDestructuredProps( ts, - virtualCode.sfc.scriptSetup.ast, + sfc.scriptSetup.ast, scriptSetupRanges.defineProps.destructured.keys(), ) ) { @@ -61,9 +67,9 @@ export function create(ts: typeof import('typescript')): LanguageServicePlugin { } const blocks = [ - virtualCode.sfc.template, - virtualCode.sfc.script, - virtualCode.sfc.scriptSetup, + sfc.template, + sfc.script, + sfc.scriptSetup, ]; const start = document.offsetAt(range.start); const end = document.offsetAt(range.end); diff --git a/packages/language-service/lib/plugins/vue-missing-props-hints.ts b/packages/language-service/lib/plugins/vue-missing-props-hints.ts index 79d758d0b7..72a784e209 100644 --- a/packages/language-service/lib/plugins/vue-missing-props-hints.ts +++ b/packages/language-service/lib/plugins/vue-missing-props-hints.ts @@ -42,12 +42,11 @@ export function create( const uri = URI.parse(document.uri); const decoded = context.decodeEmbeddedDocumentUri(uri); const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!virtualCode) { + if (!sourceScript?.generated) { return; } - const root = sourceScript?.generated?.root; + const root = sourceScript.generated.root; if (!(root instanceof VueVirtualCode)) { return; } @@ -58,7 +57,7 @@ export function create( } const result: InlayHint[] = []; - const casing = await checkCasing(context, decoded[0]); + const casing = await checkCasing(context, decoded![0]); const components = await tsPluginClient?.getComponentNames(root.fileName) ?? []; const componentProps: Record = {}; diff --git a/packages/language-service/lib/plugins/vue-scoped-class-links.ts b/packages/language-service/lib/plugins/vue-scoped-class-links.ts new file mode 100644 index 0000000000..669d8c531a --- /dev/null +++ b/packages/language-service/lib/plugins/vue-scoped-class-links.ts @@ -0,0 +1,77 @@ +import type { LanguageServicePlugin } from '@volar/language-service'; +import { tsCodegen, VueVirtualCode } from '@vue/language-core'; +import { URI } from 'vscode-uri'; + +export function create(): LanguageServicePlugin { + return { + name: 'vue-scoped-class-links', + capabilities: { + documentLinkProvider: {}, + }, + create(context) { + return { + provideDocumentLinks(document) { + const uri = URI.parse(document.uri); + const decoded = context.decodeEmbeddedDocumentUri(uri); + const sourceScript = decoded && context.language.scripts.get(decoded[0]); + const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); + if (!sourceScript?.generated || virtualCode?.id !== 'template') { + return; + } + + const root = sourceScript.generated.root; + if (!(root instanceof VueVirtualCode)) { + return; + } + + const { sfc } = root; + const codegen = tsCodegen.get(sfc); + + const option = root.vueCompilerOptions.resolveStyleClassNames; + const scopedClasses = codegen?.getGeneratedTemplate()?.scopedClasses ?? []; + const styleClasses = new Map(); + + for (let i = 0; i < sfc.styles.length; i++) { + const style = sfc.styles[i]; + if (option !== true && !(option === 'scoped' && style.scoped)) { + continue; + } + + const styleDocumentUri = context.encodeEmbeddedDocumentUri(decoded![0], 'style_' + i); + const styleVirtualCode = sourceScript.generated.embeddedCodes.get('style_' + i); + if (!styleVirtualCode) { + continue; + } + const styleDocument = context.documents.get( + styleDocumentUri, + styleVirtualCode.languageId, + styleVirtualCode.snapshot, + ); + + for (const { text, offset } of style.classNames) { + const start = styleDocument.positionAt(offset); + const end = styleDocument.positionAt(offset + text.length); + const target = styleDocumentUri + + `#L${start.line + 1},${start.character + 1}-L${end.line + 1},${end.character + 1}`; + if (!styleClasses.has(text)) { + styleClasses.set(text, []); + } + styleClasses.get(text)!.push(target); + } + } + + return scopedClasses.flatMap(({ className, offset }) => { + const range = { + start: document.positionAt(offset), + end: document.positionAt(offset + className.length), + }; + return styleClasses.get('.' + className)?.map(target => ({ + range, + target, + })) ?? []; + }); + }, + }; + }, + }; +} diff --git a/packages/language-service/lib/plugins/vue-template-ref-links.ts b/packages/language-service/lib/plugins/vue-template-ref-links.ts new file mode 100644 index 0000000000..f2354ea43d --- /dev/null +++ b/packages/language-service/lib/plugins/vue-template-ref-links.ts @@ -0,0 +1,72 @@ +import type { LanguageServicePlugin } from '@volar/language-service'; +import { tsCodegen, VueVirtualCode } from '@vue/language-core'; +import { URI } from 'vscode-uri'; + +export function create(): LanguageServicePlugin { + return { + name: 'vue-template-ref-links', + capabilities: { + documentLinkProvider: {}, + }, + create(context) { + return { + provideDocumentLinks(document) { + const uri = URI.parse(document.uri); + const decoded = context.decodeEmbeddedDocumentUri(uri); + const sourceScript = decoded && context.language.scripts.get(decoded[0]); + const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); + if (!sourceScript?.generated || virtualCode?.id !== 'scriptsetup_raw') { + return; + } + + const root = sourceScript.generated.root; + if (!(root instanceof VueVirtualCode)) { + return; + } + + const { sfc } = root; + const codegen = tsCodegen.get(sfc); + + if (!sfc.scriptSetup) { + return; + } + + const templateVirtualCode = sourceScript.generated.embeddedCodes.get('template'); + if (!templateVirtualCode) { + return; + } + const templateDocumentUri = context.encodeEmbeddedDocumentUri(decoded![0], 'template'); + const templateDocument = context.documents.get( + templateDocumentUri, + templateVirtualCode.languageId, + templateVirtualCode.snapshot, + ); + + const templateRefs = codegen?.getGeneratedTemplate()?.templateRefs; + const useTemplateRefs = codegen?.getScriptSetupRanges()?.useTemplateRef ?? []; + + return useTemplateRefs.flatMap(({ arg }) => { + if (!arg) { + return []; + } + const name = sfc.scriptSetup!.content.slice(arg.start + 1, arg.end - 1); + const range = { + start: document.positionAt(arg.start + 1), + end: document.positionAt(arg.end - 1), + }; + + return templateRefs?.get(name)?.map(({ offset }) => { + const start = templateDocument.positionAt(offset); + const end = templateDocument.positionAt(offset + name.length); + return { + range, + target: templateDocumentUri + + `#L${start.line + 1},${start.character + 1}-L${end.line + 1},${end.character + 1}`, + }; + }) ?? []; + }); + }, + }; + }, + }; +} diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 49c9e0e3f4..089c9c472d 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -110,7 +110,9 @@ export function create( const vModel = builtInData.globalAttributes?.find(x => x.name === 'v-model'); if (vOn) { - const markdown = (typeof vOn.description === 'string' ? vOn.description : vOn.description?.value) ?? ''; + const markdown = typeof vOn.description === 'object' + ? vOn.description.value + : vOn.description ?? ''; const modifiers = markdown .split('\n- ')[4] .split('\n').slice(2, -1); @@ -121,7 +123,9 @@ export function create( } } if (vBind) { - const markdown = (typeof vBind.description === 'string' ? vBind.description : vBind.description?.value) ?? ''; + const markdown = typeof vBind.description === 'object' + ? vBind.description.value + : vBind.description ?? ''; const modifiers = markdown .split('\n- ')[4] .split('\n').slice(2, -1); @@ -135,7 +139,7 @@ export function create( for (const modifier of modelData.globalAttributes ?? []) { const description = typeof modifier.description === 'object' ? modifier.description.value - : modifier.description; + : modifier.description ?? ''; const references = modifier.references?.map(ref => `[${ref.name}](${ref.url})`).join(' | '); vModelModifiers[modifier.name] = description + '\n\n' + references; } @@ -162,52 +166,38 @@ export function create( return; } - let sync: (() => Promise) | undefined; - let currentVersion: number | undefined; - const uri = URI.parse(document.uri); const decoded = context.decodeEmbeddedDocumentUri(uri); const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const root = sourceScript?.generated?.root; - - if (root instanceof VueVirtualCode) { - // #4298: Precompute HTMLDocument before provideHtmlData to avoid parseHTMLDocument requesting component names from tsserver - baseServiceInstance.provideCompletionItems?.(document, position, completionContext, token); + if (!sourceScript?.generated) { + return; + } - sync = (await provideHtmlData(sourceScript!.id, root)).sync; - currentVersion = await sync(); + const root = sourceScript.generated.root; + if (!(root instanceof VueVirtualCode)) { + return; } - let htmlComplete = await baseServiceInstance.provideCompletionItems?.( - document, - position, - completionContext, - token, - ); + // #4298: Precompute HTMLDocument before provideHtmlData to avoid parseHTMLDocument requesting component names from tsserver + baseServiceInstance.provideCompletionItems?.(document, position, completionContext, token); + + let sync = (await provideHtmlData(sourceScript.id, root)).sync; + let currentVersion: number | undefined; + let completionList: CompletionList | null | undefined; + while (currentVersion !== (currentVersion = await sync?.())) { - htmlComplete = await baseServiceInstance.provideCompletionItems?.( + completionList = await baseServiceInstance.provideCompletionItems?.( document, position, completionContext, token, ); } - if (!htmlComplete) { - return; - } - if (sourceScript?.generated) { - const virtualCode = sourceScript.generated.embeddedCodes.get('template'); - if (virtualCode) { - const embeddedDocumentUri = context.encodeEmbeddedDocumentUri(sourceScript.id, virtualCode.id); - afterHtmlCompletion( - htmlComplete, - context.documents.get(embeddedDocumentUri, virtualCode.languageId, virtualCode.snapshot), - ); - } + if (completionList) { + transformCompletionList(completionList, document); + return completionList; } - - return htmlComplete; }, provideHover(document, position, token) { @@ -478,7 +468,7 @@ export function create( }; } - function afterHtmlCompletion(completionList: CompletionList, document: TextDocument) { + function transformCompletionList(completionList: CompletionList, document: TextDocument) { addDirectiveModifiers(); function addDirectiveModifiers() { From 2bb9ce881ede71cee9f7a35114e8c6565cd4318a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Tue, 22 Jul 2025 01:35:47 +0800 Subject: [PATCH 04/85] refactor(language-core): extract binding collectors --- packages/language-core/index.ts | 2 +- .../lib/codegen/template/interpolation.ts | 3 +- .../lib/codegen/template/vFor.ts | 3 +- .../lib/codegen/template/vSlot.ts | 3 +- .../language-core/lib/codegen/utils/index.ts | 41 ---------------- .../lib/parsers/scriptSetupRanges.ts | 49 ++----------------- .../lib/utils/collectBindings.ts | 45 +++++++++++++++++ .../language-server/lib/reactivityAnalyze.ts | 6 +-- .../lib/plugins/vue-inlayhints.ts | 6 +-- 9 files changed, 62 insertions(+), 96 deletions(-) create mode 100644 packages/language-core/lib/utils/collectBindings.ts diff --git a/packages/language-core/index.ts b/packages/language-core/index.ts index d3a6d53844..a09efc0f4b 100644 --- a/packages/language-core/index.ts +++ b/packages/language-core/index.ts @@ -1,10 +1,10 @@ export * from './lib/codegen/globalTypes'; export * from './lib/codegen/template'; -export * from './lib/codegen/utils'; export * from './lib/languagePlugin'; export * from './lib/parsers/scriptSetupRanges'; export * from './lib/plugins'; export * from './lib/types'; +export * from './lib/utils/collectBindings'; export * from './lib/utils/parseSfc'; export * from './lib/utils/shared'; export * from './lib/utils/ts'; diff --git a/packages/language-core/lib/codegen/template/interpolation.ts b/packages/language-core/lib/codegen/template/interpolation.ts index 064bb0b2d0..210f31834d 100644 --- a/packages/language-core/lib/codegen/template/interpolation.ts +++ b/packages/language-core/lib/codegen/template/interpolation.ts @@ -1,9 +1,10 @@ import { isGloballyAllowed, makeMap } from '@vue/shared'; import type * as ts from 'typescript'; import type { Code, VueCodeInformation } from '../../types'; +import { collectBindingNames } from '../../utils/collectBindings'; import { getNodeText, getStartEnd } from '../../utils/shared'; import type { ScriptCodegenOptions } from '../script'; -import { collectBindingNames, createTsAst, identifierRegex } from '../utils'; +import { createTsAst, identifierRegex } from '../utils'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; diff --git a/packages/language-core/lib/codegen/template/vFor.ts b/packages/language-core/lib/codegen/template/vFor.ts index 794602f42a..fce675dbda 100644 --- a/packages/language-core/lib/codegen/template/vFor.ts +++ b/packages/language-core/lib/codegen/template/vFor.ts @@ -1,6 +1,7 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type { Code } from '../../types'; -import { collectBindingNames, createTsAst, endOfLine, newLine } from '../utils'; +import { collectBindingNames } from '../../utils/collectBindings'; +import { createTsAst, endOfLine, newLine } from '../utils'; import type { TemplateCodegenContext } from './context'; import { generateElementChildren } from './elementChildren'; import type { TemplateCodegenOptions } from './index'; diff --git a/packages/language-core/lib/codegen/template/vSlot.ts b/packages/language-core/lib/codegen/template/vSlot.ts index 7651ed0190..4a3fa82a64 100644 --- a/packages/language-core/lib/codegen/template/vSlot.ts +++ b/packages/language-core/lib/codegen/template/vSlot.ts @@ -1,8 +1,9 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type * as ts from 'typescript'; import type { Code } from '../../types'; +import { collectBindingNames } from '../../utils/collectBindings'; import { getStartEnd } from '../../utils/shared'; -import { collectBindingNames, createTsAst, endOfLine, newLine } from '../utils'; +import { createTsAst, endOfLine, newLine } from '../utils'; import { wrapWith } from '../utils/wrapWith'; import type { TemplateCodegenContext } from './context'; import { generateElementChildren } from './elementChildren'; diff --git a/packages/language-core/lib/codegen/utils/index.ts b/packages/language-core/lib/codegen/utils/index.ts index 92b80bc630..6f7ab63253 100644 --- a/packages/language-core/lib/codegen/utils/index.ts +++ b/packages/language-core/lib/codegen/utils/index.ts @@ -1,53 +1,12 @@ import type * as CompilerDOM from '@vue/compiler-dom'; import type * as ts from 'typescript'; import type { Code, SfcBlock, VueCodeInformation } from '../../types'; -import { getNodeText } from '../../utils/shared'; export const newLine = `\n`; export const endOfLine = `;${newLine}`; export const combineLastMapping: VueCodeInformation = { __combineOffset: 1 }; export const identifierRegex = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/; -export function collectBindingNames( - ts: typeof import('typescript'), - node: ts.Node, - ast: ts.SourceFile, -) { - return collectIdentifiers(ts, node).map(({ id }) => getNodeText(ts, id, ast)); -} - -export function collectIdentifiers( - ts: typeof import('typescript'), - node: ts.Node, - results: { - id: ts.Identifier; - isRest: boolean; - initializer: ts.Expression | undefined; - }[] = [], - isRest = false, - initializer: ts.Expression | undefined = undefined, -) { - if (ts.isIdentifier(node)) { - results.push({ id: node, isRest, initializer }); - } - else if (ts.isObjectBindingPattern(node)) { - for (const el of node.elements) { - collectIdentifiers(ts, el.name, results, !!el.dotDotDotToken, el.initializer); - } - } - else if (ts.isArrayBindingPattern(node)) { - for (const el of node.elements) { - if (ts.isBindingElement(el)) { - collectIdentifiers(ts, el.name, results, !!el.dotDotDotToken); - } - } - } - else { - ts.forEachChild(node, node => collectIdentifiers(ts, node, results, false)); - } - return results; -} - export function normalizeAttributeValue(node: CompilerDOM.TextNode): [string, number] { let offset = node.loc.start.offset; let content = node.loc.source; diff --git a/packages/language-core/lib/parsers/scriptSetupRanges.ts b/packages/language-core/lib/parsers/scriptSetupRanges.ts index 5dabc257ed..886de3ff79 100644 --- a/packages/language-core/lib/parsers/scriptSetupRanges.ts +++ b/packages/language-core/lib/parsers/scriptSetupRanges.ts @@ -1,6 +1,6 @@ import type * as ts from 'typescript'; -import { collectIdentifiers } from '../codegen/utils'; import type { TextRange, VueCompilerOptions } from '../types'; +import { collectBindingIdentifiers, collectBindingRanges } from '../utils/collectBindings'; import { getNodeText, getStartEnd } from '../utils/shared'; const tsCheckReg = /^\/\/\s*@ts-(?:no)?check($|\s)/; @@ -236,7 +236,7 @@ export function parseScriptSetupRanges( }; if (ts.isVariableDeclaration(parent) && ts.isObjectBindingPattern(parent.name)) { defineProps.destructured = new Map(); - const identifiers = collectIdentifiers(ts, parent.name); + const identifiers = collectBindingIdentifiers(ts, parent.name); for (const { id, isRest, initializer } of identifiers) { const name = _getNodeText(id); if (isRest) { @@ -374,8 +374,8 @@ export function parseBindingRanges(ts: typeof import('typescript'), ast: ts.Sour ts.forEachChild(ast, node => { if (ts.isVariableStatement(node)) { for (const decl of node.declarationList.declarations) { - const vars = _findBindingVars(decl.name); - bindings.push(...vars.map(range => ({ range }))); + const ranges = collectBindingRanges(ts, decl.name, ast); + bindings.push(...ranges.map(range => ({ range }))); } } else if (ts.isFunctionDeclaration(node)) { @@ -445,47 +445,6 @@ export function parseBindingRanges(ts: typeof import('typescript'), ast: ts.Sour function _getNodeText(node: ts.Node) { return getNodeText(ts, node, ast); } - - function _findBindingVars(left: ts.BindingName) { - return findBindingVars(ts, left, ast); - } -} - -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, ast)); - } - // { ? } = ... - // [ ? ] = ... - 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); - } - // { foo } = ... - else if (ts.isShorthandPropertyAssignment(node)) { - vars.push(getStartEnd(ts, node.name, ast)); - } - // { ...? } = ... - // [ ...? ] = ... - else if (ts.isSpreadAssignment(node) || ts.isSpreadElement(node)) { - worker(node.expression); - } - } } function getStatementRange( diff --git a/packages/language-core/lib/utils/collectBindings.ts b/packages/language-core/lib/utils/collectBindings.ts new file mode 100644 index 0000000000..0aefcd6ce2 --- /dev/null +++ b/packages/language-core/lib/utils/collectBindings.ts @@ -0,0 +1,45 @@ +import type * as ts from 'typescript'; +import { getNodeText, getStartEnd } from './shared'; + +export function collectBindingNames( + ts: typeof import('typescript'), + node: ts.Node, + ast: ts.SourceFile, +) { + return collectBindingIdentifiers(ts, node).map(({ id }) => getNodeText(ts, id, ast)); +} + +export function collectBindingRanges( + ts: typeof import('typescript'), + node: ts.Node, + ast: ts.SourceFile, +) { + return collectBindingIdentifiers(ts, node).map(({ id }) => getStartEnd(ts, id, ast)); +} + +export function collectBindingIdentifiers( + ts: typeof import('typescript'), + node: ts.Node, + results: { + id: ts.Identifier; + isRest: boolean; + initializer: ts.Expression | undefined; + }[] = [], + isRest = false, + initializer: ts.Expression | undefined = undefined, +) { + if (ts.isIdentifier(node)) { + results.push({ id: node, isRest, initializer }); + } + else if (ts.isArrayBindingPattern(node) || ts.isObjectBindingPattern(node)) { + for (const el of node.elements) { + if (ts.isBindingElement(el)) { + collectBindingIdentifiers(ts, el.name, results, !!el.dotDotDotToken, el.initializer); + } + } + } + else { + ts.forEachChild(node, node => collectBindingIdentifiers(ts, node, results, false)); + } + return results; +} diff --git a/packages/language-server/lib/reactivityAnalyze.ts b/packages/language-server/lib/reactivityAnalyze.ts index a3b0001ed6..793c0930d1 100644 --- a/packages/language-server/lib/reactivityAnalyze.ts +++ b/packages/language-server/lib/reactivityAnalyze.ts @@ -1,4 +1,4 @@ -import { findBindingVars, hyphenateAttr, type TextRange } from '@vue/language-core'; +import { collectBindingRanges, hyphenateAttr, type TextRange } from '@vue/language-core'; import type * as ts from 'typescript'; const enum TrackKind { @@ -164,8 +164,8 @@ export function analyze( } function findSubscribers(refName: ts.BindingName, trackKinds: TrackKind[], visited = new Set()) { - return findBindingVars(ts, refName, sourceFile) - .map(binding => findSubscribersWorker(binding, trackKinds, visited)) + return collectBindingRanges(ts, refName, sourceFile) + .map(range => findSubscribersWorker(range, trackKinds, visited)) .flat(); } diff --git a/packages/language-service/lib/plugins/vue-inlayhints.ts b/packages/language-service/lib/plugins/vue-inlayhints.ts index 19c5b63ecc..24ce557efd 100644 --- a/packages/language-service/lib/plugins/vue-inlayhints.ts +++ b/packages/language-service/lib/plugins/vue-inlayhints.ts @@ -1,5 +1,5 @@ import type { InlayHint, InlayHintKind, LanguageServicePlugin } from '@volar/language-service'; -import { collectIdentifiers, tsCodegen, VueVirtualCode } from '@vue/language-core'; +import { collectBindingIdentifiers, tsCodegen, VueVirtualCode } from '@vue/language-core'; import type * as ts from 'typescript'; import { URI } from 'vscode-uri'; @@ -198,7 +198,7 @@ export function findDestructuredProps( && ts.isCallExpression(initializer) && initializer.expression.getText(ast) === 'defineProps'; - for (const { id } of collectIdentifiers(ts, name)) { + for (const { id } of collectBindingIdentifiers(ts, name)) { if (isDefineProps) { excludedIds.add(id); } @@ -215,7 +215,7 @@ export function findDestructuredProps( } for (const p of parameters) { - for (const { id } of collectIdentifiers(ts, p)) { + for (const { id } of collectBindingIdentifiers(ts, p)) { registerLocalBinding(id); } } From 7953154cbd48624fd9cb2a6420e7c4b053d57da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Wed, 23 Jul 2025 02:22:55 +0800 Subject: [PATCH 05/85] fix(language-core): do not evaluate `skipTemplateCodegen` when exposing `$slots` --- packages/language-core/lib/codegen/script/scriptSetup.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/language-core/lib/codegen/script/scriptSetup.ts b/packages/language-core/lib/codegen/script/scriptSetup.ts index 63588e136b..83af4ed7ff 100644 --- a/packages/language-core/lib/codegen/script/scriptSetup.ts +++ b/packages/language-core/lib/codegen/script/scriptSetup.ts @@ -313,12 +313,9 @@ function* generateSetupFunction( if (syntax) { if ( - !options.vueCompilerOptions.skipTemplateCodegen - && ( - scriptSetupRanges.defineSlots - || options.templateCodegen?.slots.length - || options.templateCodegen?.dynamicSlots.length - ) + scriptSetupRanges.defineSlots + || options.templateCodegen?.slots.length + || options.templateCodegen?.dynamicSlots.length ) { yield `const __VLS_component = `; yield* generateComponent(options, ctx, scriptSetup, scriptSetupRanges); From a6d75f74ab883357477b25f1e0385d96c9460e15 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 23 Jul 2025 09:00:38 +0800 Subject: [PATCH 06/85] refactor(language-service): remove unnecessary `ProjectContext` setup --- packages/language-server/index.ts | 2 +- packages/language-service/index.ts | 9 --------- .../lib/plugins/vue-document-drop.ts | 13 +++---------- .../lib/plugins/vue-missing-props-hints.ts | 6 +----- .../language-service/lib/plugins/vue-template.ts | 4 ---- 5 files changed, 5 insertions(+), 29 deletions(-) diff --git a/packages/language-server/index.ts b/packages/language-server/index.ts index 3b8d8e7a92..f23372f8ac 100644 --- a/packages/language-server/index.ts +++ b/packages/language-server/index.ts @@ -202,7 +202,7 @@ connection.onInitialize(params => { language, server.languageServicePlugins, createLanguageServiceEnvironment(server, [...server.workspaceFolders.all]), - { vue: { compilerOptions: commonLine.vueOptions } }, + {}, ); } }); diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index bbdfb44909..f1188ba217 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -4,7 +4,6 @@ export * from '@volar/language-service'; // for @vue/language-server usage export * from '@volar/language-service/lib/utils/featureWorkers'; -import type { VueCompilerOptions } from '@vue/language-core'; import type * as ts from 'typescript'; import { create as createEmmetPlugin } from 'volar-service-emmet'; @@ -32,14 +31,6 @@ import { create as createVueTemplatePlugin } from './lib/plugins/vue-template'; import { create as createVueTemplateRefLinksPlugin } from './lib/plugins/vue-template-ref-links'; import { create as createVueTwoslashQueriesPlugin } from './lib/plugins/vue-twoslash-queries'; -declare module '@volar/language-service' { - export interface ProjectContext { - vue?: { - compilerOptions: VueCompilerOptions; - }; - } -} - export function createVueLanguageServicePlugins( ts: typeof import('typescript'), tsPluginClient: diff --git a/packages/language-service/lib/plugins/vue-document-drop.ts b/packages/language-service/lib/plugins/vue-document-drop.ts index cf677cecee..1f8b3fc27a 100644 --- a/packages/language-service/lib/plugins/vue-document-drop.ts +++ b/packages/language-service/lib/plugins/vue-document-drop.ts @@ -24,14 +24,8 @@ export function create( documentDropEditsProvider: true, }, create(context) { - if (!context.project.vue) { - return {}; - } - - let casing = TagNameCasing.Pascal as TagNameCasing; // TODO - const tsPluginClient = getTsPluginClient?.(context); - const vueCompilerOptions = context.project.vue.compilerOptions; + let casing = TagNameCasing.Pascal as TagNameCasing; // TODO return { async provideDocumentDropEdits(document, _position, dataTransfer) { @@ -57,7 +51,7 @@ export function create( importUri = item.value as string; } } - if (!importUri || !vueCompilerOptions.extensions.some(ext => importUri.endsWith(ext))) { + if (!importUri || !root.vueCompilerOptions.extensions.some(ext => importUri.endsWith(ext))) { return; } @@ -76,8 +70,7 @@ export function create( code.id === (sfc.scriptSetup ? 'scriptsetup_raw' : 'script_raw') )!; const lastImportNode = getLastImportNode(ts, script.ast); - const incomingFileName = context.project.typescript?.uriConverter.asFileName(URI.parse(importUri)) - ?? URI.parse(importUri).fsPath.replace(/\\/g, '/'); + const incomingFileName = URI.parse(importUri).fsPath.replace(/\\/g, '/'); let importPath: string | undefined; diff --git a/packages/language-service/lib/plugins/vue-missing-props-hints.ts b/packages/language-service/lib/plugins/vue-missing-props-hints.ts index 72a784e209..39f10f3ebb 100644 --- a/packages/language-service/lib/plugins/vue-missing-props-hints.ts +++ b/packages/language-service/lib/plugins/vue-missing-props-hints.ts @@ -30,10 +30,6 @@ export function create( return; } - if (!context.project.vue) { - return; - } - const enabled = await context.env.getConfiguration?.('vue.inlayHints.missingProps') ?? false; if (!enabled) { return; @@ -126,7 +122,7 @@ export function create( attrText = attrText.slice('v-model:'.length); } else if (attrText === 'v-model') { - attrText = context.project.vue.compilerOptions.target >= 3 ? 'modelValue' : 'value'; // TODO: support for experimentalModelPropName? + attrText = root.vueCompilerOptions.target >= 3 ? 'modelValue' : 'value'; // TODO: support for experimentalModelPropName? } else if (attrText.startsWith('v-on:')) { attrText = 'on-' + hyphenateAttr(attrText.slice('v-on:'.length)); diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 089c9c472d..8aba12f980 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -162,10 +162,6 @@ export function create( return; } - if (!context.project.vue) { - return; - } - const uri = URI.parse(document.uri); const decoded = context.decodeEmbeddedDocumentUri(uri); const sourceScript = decoded && context.language.scripts.get(decoded[0]); From 2d41b9d5c1ec20955fbb8384cc689e6c6edefd56 Mon Sep 17 00:00:00 2001 From: KazariEX Date: Wed, 23 Jul 2025 16:35:54 +0800 Subject: [PATCH 07/85] feat(language-service): check casing when dropping component into template --- .../language-service/lib/plugins/vue-document-drop.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/language-service/lib/plugins/vue-document-drop.ts b/packages/language-service/lib/plugins/vue-document-drop.ts index 1f8b3fc27a..caf2c3abfe 100644 --- a/packages/language-service/lib/plugins/vue-document-drop.ts +++ b/packages/language-service/lib/plugins/vue-document-drop.ts @@ -9,7 +9,7 @@ import { camelize, capitalize, hyphenate } from '@vue/shared'; import { posix as path } from 'path-browserify'; import { getUserPreferences } from 'volar-service-typescript/lib/configs/getUserPreferences'; import { URI } from 'vscode-uri'; -import { TagNameCasing } from '../nameCasing'; +import { checkCasing, TagNameCasing } from '../nameCasing'; import { createAddComponentToOptionEdit, getLastImportNode } from '../plugins/vue-extract-file'; export function create( @@ -25,7 +25,6 @@ export function create( }, create(context) { const tsPluginClient = getTsPluginClient?.(context); - let casing = TagNameCasing.Pascal as TagNameCasing; // TODO return { async provideDocumentDropEdits(document, _position, dataTransfer) { @@ -61,9 +60,9 @@ export function create( return; } - let baseName = importUri.slice(importUri.lastIndexOf('/') + 1); - baseName = baseName.slice(0, baseName.lastIndexOf('.')); - const newName = capitalize(camelize(baseName)); + const casing = await checkCasing(context, decoded![0]); + const baseName = path.basename(importUri); + const newName = capitalize(camelize(baseName.slice(0, baseName.lastIndexOf('.')))); const additionalEdit: WorkspaceEdit = {}; const code = [...forEachEmbeddedCode(root)].find(code => @@ -134,7 +133,7 @@ export function create( } return { - insertText: `<${casing === TagNameCasing.Kebab ? hyphenate(newName) : newName}$0 />`, + insertText: `<${casing.tag === TagNameCasing.Kebab ? hyphenate(newName) : newName}$0 />`, insertTextFormat: 2 satisfies typeof InsertTextFormat.Snippet, additionalEdit, }; From a5eebe019eee08e82b5ee917aaf02ad4ca15c669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Thu, 24 Jul 2025 02:19:35 +0800 Subject: [PATCH 08/85] refactor(language-service): remove internal key (#5550) Co-authored-by: Johnson Chu --- .../lib/plugins/vue-template.ts | 704 ++++++++---------- .../lib/requests/getComponentProps.ts | 14 +- 2 files changed, 321 insertions(+), 397 deletions(-) diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 8aba12f980..4e3461e657 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -17,11 +17,6 @@ import { URI, Utils } from 'vscode-uri'; import { AttrNameCasing, checkCasing, TagNameCasing } from '../nameCasing'; import { loadModelModifiersData, loadTemplateData } from './data'; -type InternalItemId = - | 'componentEvent' - | 'componentProp' - | 'specialTag'; - const specialTags = new Set([ 'slot', 'component', @@ -48,9 +43,7 @@ export function create( ): LanguageServicePlugin { let customData: html.IHTMLDataProvider[] = []; let extraCustomData: html.IHTMLDataProvider[] = []; - let lastCompletionComponentNames = new Set(); - const cachedPropInfos = new Map(); const onDidChangeCustomDataListeners = new Set<() => void>(); const onDidChangeCustomData = (listener: () => void): Disposable => { onDidChangeCustomDataListeners.add(listener); @@ -62,6 +55,7 @@ export function create( }; const baseService = mode === 'pug' ? createPugService({ + useDefaultDataProvider: false, getCustomData() { return [ ...customData, @@ -72,6 +66,7 @@ export function create( }) : createHtmlService({ documentSelector: ['html', 'markdown'], + useDefaultDataProvider: false, getCustomData() { return [ ...customData, @@ -174,26 +169,136 @@ export function create( return; } - // #4298: Precompute HTMLDocument before provideHtmlData to avoid parseHTMLDocument requesting component names from tsserver - baseServiceInstance.provideCompletionItems?.(document, position, completionContext, token); + const { + result: completionList, + components, + propMap, + } = await runWithVueData( + sourceScript.id, + root, + () => + baseServiceInstance.provideCompletionItems!( + document, + position, + completionContext, + token, + ), + ); + + if (!completionList) { + return; + } + + addDirectiveModifiers(completionList, document); - let sync = (await provideHtmlData(sourceScript.id, root)).sync; - let currentVersion: number | undefined; - let completionList: CompletionList | null | undefined; + for (const item of completionList.items) { + let prop = propMap.get(item.label); - while (currentVersion !== (currentVersion = await sync?.())) { - completionList = await baseServiceInstance.provideCompletionItems?.( - document, - position, - completionContext, - token, - ); - } + if (prop) { + if (prop.info?.documentation) { + item.documentation = { + kind: 'markdown', + value: prop.info.documentation, + }; + } + + if (prop.info?.deprecated) { + item.tags = [1 satisfies typeof CompletionItemTag.Deprecated]; + } + } + else { + let name = item.label; + for (const str of ['v-bind:', ':']) { + if (name.startsWith(str) && name !== str) { + name = name.slice(str.length); + break; + } + } + if (specialProps.has(name)) { + prop = { + name, + kind: 'prop', + isGlobal: true, + }; + } + } + + const tokens: string[] = []; + + if ( + item.kind === 10 satisfies typeof CompletionItemKind.Property + && components?.includes(hyphenateTag(item.label)) + ) { + item.kind = 6 satisfies typeof CompletionItemKind.Variable; + tokens.push('\u0000'); + } + else if (prop) { + const { isEvent, propName } = getPropName(prop.name, prop.kind === 'event'); + + if (prop.kind === 'prop') { + if (!prop.isGlobal || specialProps.has(propName)) { + item.kind = 5 satisfies typeof CompletionItemKind.Field; + } + } + else if (isEvent) { + item.kind = 23 satisfies typeof CompletionItemKind.Event; + if (propName.startsWith('vue:')) { + tokens.push('\u0004'); + } + } + + if (!prop.isGlobal || specialProps.has(propName)) { + tokens.push('\u0000'); + + if (item.label.startsWith(':')) { + tokens.push('\u0001'); + } + else if (item.label.startsWith('@')) { + tokens.push('\u0002'); + } + else if (item.label.startsWith('v-bind:')) { + tokens.push('\u0003'); + } + else if (item.label.startsWith('v-model:')) { + tokens.push('\u0004'); + } + else if (item.label.startsWith('v-on:')) { + tokens.push('\u0005'); + } + else { + tokens.push('\u0000'); + } + + if (specialProps.has(propName)) { + tokens.push('\u0001'); + } + else { + tokens.push('\u0000'); + } + } + } + else if ( + item.label === 'v-if' + || item.label === 'v-else-if' + || item.label === 'v-else' + || item.label === 'v-for' + ) { + item.kind = 14 satisfies typeof CompletionItemKind.Keyword; + tokens.push('\u0003'); + } + else if (item.label.startsWith('v-')) { + item.kind = 3 satisfies typeof CompletionItemKind.Function; + tokens.push('\u0002'); + } + else { + tokens.push('\u0001'); + } - if (completionList) { - transformCompletionList(completionList, document); - return completionList; + item.sortText = tokens.join('') + (item.sortText ?? item.label); } + + updateExtraCustomData([]); + return completionList; }, provideHover(document, position, token) { @@ -209,51 +314,82 @@ export function create( }, }; - async function provideHtmlData(sourceDocumentUri: URI, vueCode: VueVirtualCode) { + async function runWithVueData(sourceDocumentUri: URI, root: VueVirtualCode, fn: () => T) { + // #4298: Precompute HTMLDocument before provideHtmlData to avoid parseHTMLDocument requesting component names from tsserver + await fn(); + + const { sync } = await provideHtmlData(sourceDocumentUri, root); + let lastSync = await sync(); + let result = await fn(); + while (lastSync.version !== (lastSync = await sync()).version) { + result = await fn(); + } + return { + result, + ...lastSync, + }; + } + + async function provideHtmlData(sourceDocumentUri: URI, root: VueVirtualCode) { await (initializing ??= initialize()); const casing = await checkCasing(context, sourceDocumentUri); - if (builtInData.tags) { - for (const tag of builtInData.tags) { - if (isItemKey(tag.name)) { - continue; - } - - if (specialTags.has(tag.name)) { - tag.name = generateItemKey('specialTag', tag.name, ''); - } - else if (casing.tag === TagNameCasing.Kebab) { - tag.name = hyphenateTag(tag.name); - } - else { - tag.name = camelize(capitalize(tag.name)); - } + for (const tag of builtInData.tags ?? []) { + if (specialTags.has(tag.name)) { + continue; + } + if (casing.tag === TagNameCasing.Kebab) { + tag.name = hyphenateTag(tag.name); + } + else { + tag.name = camelize(capitalize(tag.name)); } } - const promises: Promise[] = []; - const tagInfos = new Map[] = []; + const tagMap = new Map(); - - let version = 0; - let components: string[] | undefined; - - cachedPropInfos.clear(); + const propMap = new Map(); + const htmlDataProvider = html.getDefaultHTMLDataProvider(); updateExtraCustomData([ + { + getId: () => htmlDataProvider.getId(), + isApplicable: () => true, + provideTags() { + let tags = htmlDataProvider.provideTags(); + tags = tags.filter(tag => !specialTags.has(tag.name)); + return tags; + }, + provideAttributes(tag) { + return htmlDataProvider.provideAttributes(tag); + }, + provideValues(tag, attr) { + return htmlDataProvider.provideValues(tag, attr); + }, + }, html.newHTMLDataProvider('vue-template-built-in', builtInData), { getId: () => 'vue-template', isApplicable: () => true, provideTags: () => { if (!components) { - promises.push((async () => { - components = (await tsPluginClient?.getComponentNames(vueCode.fileName) ?? []) + components = []; + tasks.push((async () => { + components = (await tsPluginClient?.getComponentNames(root.fileName) ?? []) .filter(name => name !== 'Transition' && name !== 'TransitionGroup' @@ -261,12 +397,10 @@ export function create( && name !== 'Suspense' && name !== 'Teleport' ); - lastCompletionComponentNames = new Set(components); version++; })()); - return []; } - const scriptSetupRanges = tsCodegen.get(vueCode.sfc)?.getScriptSetupRanges(); + const scriptSetupRanges = tsCodegen.get(root.sfc)?.getScriptSetupRanges(); const names = new Set(); const tags: html.ITagData[] = []; @@ -280,7 +414,7 @@ export function create( } for (const binding of scriptSetupRanges?.bindings ?? []) { - const name = vueCode.sfc.scriptSetup!.content.slice(binding.range.start, binding.range.end); + const name = root.sfc.scriptSetup!.content.slice(binding.range.start, binding.range.end); if (casing.tag === TagNameCasing.Kebab) { names.add(hyphenateTag(name)); } @@ -299,35 +433,42 @@ export function create( return tags; }, provideAttributes: tag => { - const tagInfo = tagInfos.get(tag); - + let tagInfo = tagMap.get(tag); if (!tagInfo) { - promises.push((async () => { - const attrs = await tsPluginClient?.getElementAttrs(vueCode.fileName, tag) ?? []; - const propInfos = await tsPluginClient?.getComponentProps(vueCode.fileName, tag) ?? []; - const events = await tsPluginClient?.getComponentEvents(vueCode.fileName, tag) ?? []; - const directives = await tsPluginClient?.getComponentDirectives(vueCode.fileName) ?? []; - tagInfos.set(tag, { - attrs, - propInfos: propInfos.filter(prop => !prop.name.startsWith('ref_')), - events, - directives, + tagInfo = { + attrs: [], + propInfos: [], + events: [], + directives: [], + }; + tagMap.set(tag, tagInfo); + tasks.push((async () => { + tagMap.set(tag, { + attrs: await tsPluginClient?.getElementAttrs(root.fileName, tag) ?? [], + propInfos: await tsPluginClient?.getComponentProps(root.fileName, tag) ?? [], + events: await tsPluginClient?.getComponentEvents(root.fileName, tag) ?? [], + directives: await tsPluginClient?.getComponentDirectives(root.fileName) ?? [], }); version++; })()); - return []; } const { attrs, propInfos, events, directives } = tagInfo; - for (const prop of propInfos) { + for (let i = 0; i < propInfos.length; i++) { + const prop = propInfos[i]; + if (prop.name.startsWith('ref_')) { + propInfos.splice(i--, 1); + continue; + } if (hyphenateTag(prop.name).startsWith('on-vnode-')) { - prop.name = 'onVue:' + prop.name.slice('onVnode'.length); + prop.name = 'onVue:' + prop.name['onVnode'.length].toLowerCase() + + prop.name.slice('onVnodeX'.length); } } const attributes: html.IAttributeData[] = []; - const propsSet = new Set(propInfos.map(prop => prop.name)); + const propNameSet = new Set(propInfos.map(prop => prop.name)); for ( const prop of [ @@ -335,76 +476,72 @@ export function create( ...attrs.map(attr => ({ name: attr })), ] ) { - const isGlobal = prop.isAttribute || !propsSet.has(prop.name); - const name = casing.attr === AttrNameCasing.Camel ? prop.name : hyphenateAttr(prop.name); - const isEvent = hyphenateAttr(name).startsWith('on-'); + const isGlobal = prop.isAttribute || !propNameSet.has(prop.name); + const propName = casing.attr === AttrNameCasing.Camel ? prop.name : hyphenateAttr(prop.name); + const isEvent = hyphenateAttr(propName).startsWith('on-'); if (isEvent) { - const propNameBase = name.startsWith('on-') - ? name.slice('on-'.length) - : (name['on'.length].toLowerCase() + name.slice('onX'.length)); - const propKey = generateItemKey('componentEvent', isGlobal ? '*' : tag, propNameBase); - - attributes.push( - { - name: 'v-on:' + propNameBase, - description: propKey, - }, - { - name: '@' + propNameBase, - description: propKey, - }, - ); + const eventName = casing.attr === AttrNameCasing.Camel + ? propName['on'.length].toLowerCase() + propName.slice('onX'.length) + : propName.slice('on-'.length); + + for ( + const name of [ + 'v-on:' + eventName, + '@' + eventName, + ] + ) { + attributes.push({ name }); + propMap.set(name, { + name: propName, + kind: 'event', + isGlobal, + info: prop, + }); + } } else { - const propName = name; const propInfo = propInfos.find(prop => { const name = casing.attr === AttrNameCasing.Camel ? prop.name : hyphenateAttr(prop.name); return name === propName; }); - const propKey = generateItemKey( - 'componentProp', - isGlobal ? '*' : tag, - propName, - propInfo?.deprecated, - ); - - if (propInfo) { - cachedPropInfos.set(propName, propInfo); - } - attributes.push( - { - name: propName, - description: propKey, + for ( + const name of [ + propName, + ':' + propName, + 'v-bind:' + propName, + ] + ) { + attributes.push({ + name, valueSet: prop.values?.some(value => typeof value === 'string') ? '__deferred__' : undefined, - }, - { - name: ':' + propName, - description: propKey, - }, - { - name: 'v-bind:' + propName, - description: propKey, - }, - ); + }); + propMap.set(name, { + name: propName, + kind: 'prop', + isGlobal, + info: propInfo, + }); + } } } for (const event of events) { - const name = casing.attr === AttrNameCasing.Camel ? event : hyphenateAttr(event); - const propKey = generateItemKey('componentEvent', tag, name); - - attributes.push( - { - name: 'v-on:' + name, - description: propKey, - }, - { - name: '@' + name, - description: propKey, - }, - ); + const eventName = casing.attr === AttrNameCasing.Camel ? event : hyphenateAttr(event); + + for ( + const name of [ + 'v-on:' + eventName, + '@' + eventName, + ] + ) { + attributes.push({ name }); + propMap.set(name, { + name: eventName, + kind: 'event', + }); + } } for (const directive of directives) { @@ -423,7 +560,7 @@ export function create( ] ) { if (prop.name.startsWith('onUpdate:')) { - const isGlobal = !propsSet.has(prop.name); + const isGlobal = !propNameSet.has(prop.name); models.push([isGlobal, prop.name.slice('onUpdate:'.length)]); } } @@ -435,17 +572,19 @@ export function create( for (const [isGlobal, model] of models) { const name = casing.attr === AttrNameCasing.Camel ? model : hyphenateAttr(model); - const propKey = generateItemKey('componentProp', isGlobal ? '*' : tag, name); - attributes.push({ - name: 'v-model:' + name, - description: propKey, + attributes.push({ name: 'v-model:' + name }); + propMap.set('v-model:' + name, { + name, + kind: 'prop', + isGlobal, }); if (model === 'modelValue') { - attributes.push({ - name: 'v-model', - description: propKey, + propMap.set('v-model', { + name, + kind: 'prop', + isGlobal, }); } } @@ -458,239 +597,57 @@ export function create( return { async sync() { - await Promise.all(promises); - return version; + await Promise.all(tasks); + return { version, components, propMap }; }, }; } - function transformCompletionList(completionList: CompletionList, document: TextDocument) { - addDirectiveModifiers(); - - function addDirectiveModifiers() { - const replacement = getReplacement(completionList, document); - if (!replacement?.text.includes('.')) { - return; - } - - const [text, ...modifiers] = replacement.text.split('.'); - const isVOn = text.startsWith('v-on:') || text.startsWith('@') && text.length > 1; - const isVBind = text.startsWith('v-bind:') || text.startsWith(':') && text.length > 1; - const isVModel = text.startsWith('v-model:') || text === 'v-model'; - const currentModifiers = isVOn - ? vOnModifiers - : isVBind - ? vBindModifiers - : isVModel - ? vModelModifiers - : undefined; - - if (!currentModifiers) { - return; - } - - for (const modifier in currentModifiers) { - if (modifiers.includes(modifier)) { - continue; - } - - const description = currentModifiers[modifier]; - const insertText = text + modifiers.slice(0, -1).map(m => '.' + m).join('') + '.' + modifier; - const newItem: html.CompletionItem = { - label: modifier, - filterText: insertText, - documentation: { - kind: 'markdown', - value: description, - }, - textEdit: { - range: replacement.textEdit.range, - newText: insertText, - }, - kind: 20 satisfies typeof CompletionItemKind.EnumMember, - }; - - completionList.items.push(newItem); - } + function addDirectiveModifiers(completionList: CompletionList, document: TextDocument) { + const replacement = getReplacement(completionList, document); + if (!replacement?.text.includes('.')) { + return; } - completionList.items = completionList.items.filter(item => !specialTags.has(parseLabel(item.label).name)); - - const htmlDocumentations = new Map(); - - for (const item of completionList.items) { - const documentation = typeof item.documentation === 'string' ? item.documentation : item.documentation?.value; - if (documentation && !isItemKey(documentation)) { - htmlDocumentations.set(item.label, documentation); - } + const [text, ...modifiers] = replacement.text.split('.'); + const isVOn = text.startsWith('v-on:') || text.startsWith('@') && text.length > 1; + const isVBind = text.startsWith('v-bind:') || text.startsWith(':') && text.length > 1; + const isVModel = text.startsWith('v-model:') || text === 'v-model'; + const currentModifiers = isVOn + ? vOnModifiers + : isVBind + ? vBindModifiers + : isVModel + ? vModelModifiers + : undefined; + + if (!currentModifiers) { + return; } - for (const item of completionList.items) { - const parsedLabel = parseItemKey(item.label); - - if (parsedLabel) { - const name = parsedLabel.tag; - item.label = parsedLabel.leadingSlash ? '/' + name : name; - - const text = parsedLabel.leadingSlash ? `/${name}>` : name; - if (item.textEdit) { - item.textEdit.newText = text; - } - if (item.insertText) { - item.insertText = text; - } - if (item.sortText) { - item.sortText = text; - } - } - - const itemKey = typeof item.documentation === 'string' ? item.documentation : item.documentation?.value; - let parsedItem = itemKey ? parseItemKey(itemKey) : undefined; - let propInfo: ComponentPropInfo | undefined; - - if (parsedItem) { - const documentations: string[] = []; - - propInfo = cachedPropInfos.get(parsedItem.prop); - if (propInfo?.commentMarkdown) { - documentations.push(propInfo.commentMarkdown); - } - - let { isEvent, propName } = getPropName(parsedItem); - if (isEvent) { - // click -> onclick - propName = 'on' + propName; - } - if (htmlDocumentations.has(propName)) { - documentations.push(htmlDocumentations.get(propName)!); - } - - if (documentations.length) { - item.documentation = { - kind: 'markdown', - value: documentations.join('\n\n'), - }; - } - else { - item.documentation = undefined; - } - } - else { - let propName = item.label; - - for (const str of ['v-bind:', ':']) { - if (propName.startsWith(str) && propName !== str) { - propName = propName.slice(str.length); - break; - } - } - - // for special props without internal item key - if (specialProps.has(propName)) { - parsedItem = { - type: 'componentProp', - tag: '^', - prop: propName, - deprecated: false, - leadingSlash: false, - }; - } - - propInfo = cachedPropInfos.get(propName); - if (propInfo?.commentMarkdown) { - const originalDocumentation = typeof item.documentation === 'string' - ? item.documentation - : item.documentation?.value; - item.documentation = { - kind: 'markdown', - value: [ - propInfo.commentMarkdown, - originalDocumentation, - ].filter(str => !!str).join('\n\n'), - }; - } - } - - if (propInfo?.deprecated) { - item.tags = [1 satisfies typeof CompletionItemTag.Deprecated]; - } - - const tokens: string[] = []; - - if ( - item.kind === 10 satisfies typeof CompletionItemKind.Property - && lastCompletionComponentNames.has(hyphenateTag(item.label)) - ) { - item.kind = 6 satisfies typeof CompletionItemKind.Variable; - tokens.push('\u0000'); + for (const modifier in currentModifiers) { + if (modifiers.includes(modifier)) { + continue; } - else if (parsedItem) { - const isComponent = parsedItem.tag !== '*'; - const { isEvent, propName } = getPropName(parsedItem); - - if (parsedItem.type === 'componentProp') { - if (isComponent || specialProps.has(propName)) { - item.kind = 5 satisfies typeof CompletionItemKind.Field; - } - } - else if (isEvent) { - item.kind = 23 satisfies typeof CompletionItemKind.Event; - if (propName.startsWith('vue:')) { - tokens.push('\u0004'); - } - } - - if (isComponent || specialProps.has(propName)) { - tokens.push('\u0000'); - - if (item.label.startsWith(':')) { - tokens.push('\u0001'); - } - else if (item.label.startsWith('@')) { - tokens.push('\u0002'); - } - else if (item.label.startsWith('v-bind:')) { - tokens.push('\u0003'); - } - else if (item.label.startsWith('v-model:')) { - tokens.push('\u0004'); - } - else if (item.label.startsWith('v-on:')) { - tokens.push('\u0005'); - } - else { - tokens.push('\u0000'); - } - if (specialProps.has(propName)) { - tokens.push('\u0001'); - } - else { - tokens.push('\u0000'); - } - } - } - else if ( - item.label === 'v-if' - || item.label === 'v-else-if' - || item.label === 'v-else' - || item.label === 'v-for' - ) { - item.kind = 14 satisfies typeof CompletionItemKind.Keyword; - tokens.push('\u0003'); - } - else if (item.label.startsWith('v-')) { - item.kind = 3 satisfies typeof CompletionItemKind.Function; - tokens.push('\u0002'); - } - else { - tokens.push('\u0001'); - } + const description = currentModifiers[modifier]; + const insertText = text + modifiers.slice(0, -1).map(m => '.' + m).join('') + '.' + modifier; + const newItem: html.CompletionItem = { + label: modifier, + filterText: insertText, + documentation: { + kind: 'markdown', + value: description, + }, + textEdit: { + range: replacement.textEdit.range, + newText: insertText, + }, + kind: 20 satisfies typeof CompletionItemKind.EnumMember, + }; - item.sortText = tokens.join('') + (item.sortText ?? item.label); + completionList.items.push(newItem); } - - updateExtraCustomData([]); } async function initialize() { @@ -735,37 +692,6 @@ export function create( } } -function parseLabel(label: string) { - const leadingSlash = label.startsWith('/'); - const name = label.slice(leadingSlash ? 1 : 0); - return { - name, - leadingSlash, - }; -} - -function generateItemKey(type: InternalItemId, tag: string, prop: string, deprecated?: boolean) { - return `__VLS_data=${type},${tag},${prop},${Number(deprecated)}`; -} - -function isItemKey(key: string) { - return key.startsWith('__VLS_data='); -} - -function parseItemKey(key: string) { - const { leadingSlash, name } = parseLabel(key); - if (isItemKey(name)) { - const strs = name.slice('__VLS_data='.length).split(','); - return { - type: strs[0] as InternalItemId, - tag: strs[1], - prop: strs[2], - deprecated: strs[3] === '1', - leadingSlash, - }; - } -} - function getReplacement(list: html.CompletionList, doc: TextDocument) { for (const item of list.items) { if (item.textEdit && 'range' in item.textEdit) { @@ -779,14 +705,12 @@ function getReplacement(list: html.CompletionList, doc: TextDocument) { } function getPropName( - parsedItem: ReturnType & {}, + prop: string, + isEvent: boolean, ) { - const name = hyphenateAttr(parsedItem.prop); + const name = hyphenateAttr(prop); if (name.startsWith('on-')) { return { isEvent: true, propName: name.slice('on-'.length) }; } - else if (parsedItem.type === 'componentEvent') { - return { isEvent: true, propName: name }; - } - return { isEvent: false, propName: name }; + return { isEvent, propName: name }; } diff --git a/packages/typescript-plugin/lib/requests/getComponentProps.ts b/packages/typescript-plugin/lib/requests/getComponentProps.ts index 56123ea006..b4c96cfe9b 100644 --- a/packages/typescript-plugin/lib/requests/getComponentProps.ts +++ b/packages/typescript-plugin/lib/requests/getComponentProps.ts @@ -8,7 +8,7 @@ export interface ComponentPropInfo { required?: boolean; deprecated?: boolean; isAttribute?: boolean; - commentMarkdown?: string; + documentation?: string; values?: string[]; } @@ -69,9 +69,9 @@ export function getComponentProps( const name = prop.name; const required = !(prop.flags & ts.SymbolFlags.Optional) || undefined; const { - content: commentMarkdown, + documentation, deprecated, - } = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags()); + } = generateDocumentation(prop.getDocumentationComment(checker), prop.getJsDocTags()); const values: any[] = []; const type = checker.getTypeOfSymbol(prop); const subTypes: ts.Type[] | undefined = (type as any).types; @@ -106,19 +106,19 @@ export function getComponentProps( required, deprecated, isAttribute, - commentMarkdown, + documentation, values, }); } } -function generateCommentMarkdown(parts: ts.SymbolDisplayPart[], jsDocTags: ts.JSDocTagInfo[]) { +function generateDocumentation(parts: ts.SymbolDisplayPart[], jsDocTags: ts.JSDocTagInfo[]) { const parsedComment = _symbolDisplayPartsToMarkdown(parts); const parsedJsDoc = _jsDocTagInfoToMarkdown(jsDocTags); - const content = [parsedComment, parsedJsDoc].filter(str => !!str).join('\n\n'); + const documentation = [parsedComment, parsedJsDoc].filter(str => !!str).join('\n\n'); const deprecated = jsDocTags.some(tag => tag.name === 'deprecated'); return { - content, + documentation, deprecated, }; } From 2435873dc4c09fbf5394d96c083b2c2d18ecd8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Thu, 24 Jul 2025 02:56:20 +0800 Subject: [PATCH 09/85] feat(language-service): native completion experience for slot names (#5552) --- packages/language-core/lib/plugins/vue-tsx.ts | 3 +- packages/language-server/index.ts | 3 ++ .../lib/plugins/vue-template.ts | 25 +++++++++++++++-- packages/typescript-plugin/index.ts | 20 ++++++++----- .../lib/requests/getComponentSlots.ts | 28 +++++++++++++++++++ .../typescript-plugin/lib/requests/index.ts | 5 ++-- 6 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 packages/typescript-plugin/lib/requests/getComponentSlots.ts diff --git a/packages/language-core/lib/plugins/vue-tsx.ts b/packages/language-core/lib/plugins/vue-tsx.ts index 6735c55380..8f8315ebc1 100644 --- a/packages/language-core/lib/plugins/vue-tsx.ts +++ b/packages/language-core/lib/plugins/vue-tsx.ts @@ -239,9 +239,10 @@ function createTsx( }); return { + getLang, getScriptRanges, getScriptSetupRanges, - getLang, + getSetupSlotsAssignName, getGeneratedScript, getGeneratedTemplate, }; diff --git a/packages/language-server/index.ts b/packages/language-server/index.ts index f23372f8ac..34b40ed8a9 100644 --- a/packages/language-server/index.ts +++ b/packages/language-server/index.ts @@ -117,6 +117,9 @@ connection.onInitialize(params => { getComponentProps(...args) { return sendTsServerRequest('_vue:getComponentProps', args); }, + getComponentSlots(...args) { + return sendTsServerRequest('_vue:getComponentSlots', args); + }, getElementAttrs(...args) { return sendTsServerRequest('_vue:getElementAttrs', args); }, diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 4e3461e657..8c0c7793ba 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -349,6 +349,7 @@ export function create( let version = 0; let components: string[] | undefined; + let values: string[] | undefined; const tasks: Promise[] = []; const tagMap = new Map attr.name === 'name'); + if (nameAttr) { + nameAttr.valueSet = 'slot'; + } + } + return attrs; }, provideValues(tag, attr) { return htmlDataProvider.provideValues(tag, attr); @@ -591,7 +599,20 @@ export function create( return attributes; }, - provideValues: () => [], + provideValues: (tag, attr) => { + if (!values) { + values = []; + tasks.push((async () => { + if (tag === 'slot' && attr === 'name') { + values = await tsPluginClient?.getComponentSlots(root.fileName) ?? []; + } + version++; + })()); + } + return values.map(value => ({ + name: value, + })); + }, }, ]); diff --git a/packages/typescript-plugin/index.ts b/packages/typescript-plugin/index.ts index 0f47d42696..0d3e048b21 100644 --- a/packages/typescript-plugin/index.ts +++ b/packages/typescript-plugin/index.ts @@ -7,6 +7,7 @@ import { getComponentDirectives } from './lib/requests/getComponentDirectives'; import { getComponentEvents } from './lib/requests/getComponentEvents'; import { getComponentNames } from './lib/requests/getComponentNames'; import { getComponentProps } from './lib/requests/getComponentProps'; +import { getComponentSlots } from './lib/requests/getComponentSlots'; import { getElementAttrs } from './lib/requests/getElementAttrs'; import { getElementNames } from './lib/requests/getElementNames'; import { getImportPathForFile } from './lib/requests/getImportPathForFile'; @@ -109,6 +110,16 @@ export = createLanguageServicePlugin( response: getPropertiesAtLocation.apply(getRequestContext(args[0]), args), }; }); + session.addProtocolHandler('_vue:getComponentDirectives', ({ arguments: args }) => { + return { + response: getComponentDirectives.apply(getRequestContext(args[0]), args), + }; + }); + session.addProtocolHandler('_vue:getComponentEvents', ({ arguments: args }) => { + return { + response: getComponentEvents.apply(getRequestContext(args[0]), args), + }; + }); session.addProtocolHandler('_vue:getComponentNames', ({ arguments: args }) => { return { response: getComponentNames.apply(getRequestContext(args[0]), args) ?? [], @@ -119,14 +130,9 @@ export = createLanguageServicePlugin( response: getComponentProps.apply(getRequestContext(args[0]), args), }; }); - session.addProtocolHandler('_vue:getComponentEvents', ({ arguments: args }) => { - return { - response: getComponentEvents.apply(getRequestContext(args[0]), args), - }; - }); - session.addProtocolHandler('_vue:getComponentDirectives', ({ arguments: args }) => { + session.addProtocolHandler('_vue:getComponentSlots', ({ arguments: args }) => { return { - response: getComponentDirectives.apply(getRequestContext(args[0]), args), + response: getComponentSlots.apply(getRequestContext(args[0]), args), }; }); session.addProtocolHandler('_vue:getElementAttrs', ({ arguments: args }) => { diff --git a/packages/typescript-plugin/lib/requests/getComponentSlots.ts b/packages/typescript-plugin/lib/requests/getComponentSlots.ts new file mode 100644 index 0000000000..3211e4231e --- /dev/null +++ b/packages/typescript-plugin/lib/requests/getComponentSlots.ts @@ -0,0 +1,28 @@ +import { tsCodegen, VueVirtualCode } from '@vue/language-core'; +import type { RequestContext } from './types'; +import { getVariableType } from './utils'; + +export function getComponentSlots( + this: RequestContext, + fileName: string, +) { + const { typescript: ts, language, languageService, asScriptId } = this; + const volarFile = language.scripts.get(asScriptId(fileName)); + if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { + return; + } + const vueCode = volarFile.generated.root; + + const codegen = tsCodegen.get(vueCode.sfc); + if (!codegen) { + return; + } + + const assignName = codegen.getSetupSlotsAssignName() ?? `__VLS_slots`; + const slots = getVariableType(ts, languageService, vueCode, assignName); + if (!slots) { + return []; + } + + return slots.type.getProperties().map(({ name }) => name); +} diff --git a/packages/typescript-plugin/lib/requests/index.ts b/packages/typescript-plugin/lib/requests/index.ts index ca107b1448..18ffd095e2 100644 --- a/packages/typescript-plugin/lib/requests/index.ts +++ b/packages/typescript-plugin/lib/requests/index.ts @@ -6,10 +6,11 @@ export type Requests = { collectExtractProps: ToRequest; getImportPathForFile: ToRequest; getPropertiesAtLocation: ToRequest; + getComponentDirectives: ToRequest; + getComponentEvents: ToRequest; getComponentNames: ToRequest; getComponentProps: ToRequest; - getComponentEvents: ToRequest; - getComponentDirectives: ToRequest; + getComponentSlots: ToRequest; getElementAttrs: ToRequest; getElementNames: ToRequest; getEncodedSemanticClassifications: ToRequest<(fileName: string, span: ts.TextSpan) => ts.Classifications>; From 7b55e644815ed9a0a97377b7b3b58cd74f43fb03 Mon Sep 17 00:00:00 2001 From: KazariEX Date: Thu, 24 Jul 2025 03:23:45 +0800 Subject: [PATCH 10/85] fix(language-service): correct kind and order of component completion items --- .../lib/plugins/vue-template.ts | 70 ++++++++++++------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 8c0c7793ba..ee8350847d 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -171,8 +171,11 @@ export function create( const { result: completionList, - components, - propMap, + target, + info: { + components, + propMap, + }, } = await runWithVueData( sourceScript.id, root, @@ -189,9 +192,30 @@ export function create( return; } - addDirectiveModifiers(completionList, document); + switch (target) { + case 'tag': { + completionList.items.forEach(transformTag); + break; + } + case 'attribute': { + addDirectiveModifiers(completionList, document); + completionList.items.forEach(transformAttribute); + break; + } + } + + updateExtraCustomData([]); + return completionList; + + function transformTag(item: html.CompletionItem) { + const tagName = capitalize(camelize(item.label)); + if (components?.includes(tagName)) { + item.kind = 6 satisfies typeof CompletionItemKind.Variable; + item.sortText = '\u0000' + (item.sortText ?? item.label); + } + } - for (const item of completionList.items) { + function transformAttribute(item: html.CompletionItem) { let prop = propMap.get(item.label); if (prop) { @@ -201,7 +225,6 @@ export function create( value: prop.info.documentation, }; } - if (prop.info?.deprecated) { item.tags = [1 satisfies typeof CompletionItemTag.Deprecated]; } @@ -225,14 +248,7 @@ export function create( const tokens: string[] = []; - if ( - item.kind === 10 satisfies typeof CompletionItemKind.Property - && components?.includes(hyphenateTag(item.label)) - ) { - item.kind = 6 satisfies typeof CompletionItemKind.Variable; - tokens.push('\u0000'); - } - else if (prop) { + if (prop) { const { isEvent, propName } = getPropName(prop.name, prop.kind === 'event'); if (prop.kind === 'prop') { @@ -296,9 +312,6 @@ export function create( item.sortText = tokens.join('') + (item.sortText ?? item.label); } - - updateExtraCustomData([]); - return completionList; }, provideHover(document, position, token) { @@ -324,10 +337,7 @@ export function create( while (lastSync.version !== (lastSync = await sync()).version) { result = await fn(); } - return { - result, - ...lastSync, - }; + return { result, ...lastSync }; } async function provideHtmlData(sourceDocumentUri: URI, root: VueVirtualCode) { @@ -348,6 +358,7 @@ export function create( } let version = 0; + let target: 'tag' | 'attribute' | 'value'; let components: string[] | undefined; let values: string[] | undefined; @@ -371,12 +382,13 @@ export function create( getId: () => htmlDataProvider.getId(), isApplicable: () => true, provideTags() { - let tags = htmlDataProvider.provideTags(); - tags = tags.filter(tag => !specialTags.has(tag.name)); - return tags; + target = 'tag'; + return htmlDataProvider.provideTags() + .filter(tag => !specialTags.has(tag.name)); }, provideAttributes(tag) { - let attrs = htmlDataProvider.provideAttributes(tag); + target = 'attribute'; + const attrs = htmlDataProvider.provideAttributes(tag); if (tag === 'slot') { const nameAttr = attrs.find(attr => attr.name === 'name'); if (nameAttr) { @@ -386,6 +398,7 @@ export function create( return attrs; }, provideValues(tag, attr) { + target = 'value'; return htmlDataProvider.provideValues(tag, attr); }, }, @@ -619,7 +632,14 @@ export function create( return { async sync() { await Promise.all(tasks); - return { version, components, propMap }; + return { + version, + target, + info: { + components, + propMap, + }, + }; }, }; } From 76fcd82dfbd8ee27ceb94ce4b223d59166244a15 Mon Sep 17 00:00:00 2001 From: KazariEX Date: Thu, 24 Jul 2025 14:54:35 +0800 Subject: [PATCH 11/85] chore(language-service): add restart server hint to global types warning close #5554 --- .../lib/plugins/vue-global-types-error.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/language-service/lib/plugins/vue-global-types-error.ts b/packages/language-service/lib/plugins/vue-global-types-error.ts index 6fbb1c1f59..9248fb1d9d 100644 --- a/packages/language-service/lib/plugins/vue-global-types-error.ts +++ b/packages/language-service/lib/plugins/vue-global-types-error.ts @@ -41,11 +41,19 @@ export function create(): LanguageServicePlugin { start: document.positionAt(0), end: document.positionAt(0), }, - severity: 1 satisfies typeof DiagnosticSeverity.Error, + severity: 2 satisfies typeof DiagnosticSeverity.Warning, code: 404, source: 'vue', - message: - `Write global types file failed. Please ensure that "node_modules" exists and "${vueCompilerOptions.lib}" is a direct dependency, or set "vueCompilerOptions.globalTypesPath" in "tsconfig.json" manually.`, + message: ` +Failed to write the global types file. Make sure that: + +1. "node_modules" directory exists. +2. "${vueCompilerOptions.lib}" is installed as a direct dependency. + +Alternatively, you can manually set "vueCompilerOptions.globalTypesPath" in your "tsconfig.json". + +If all dependencies are installed, try running the "vue.action.restartServer" command to restart Vue and TS servers. + `.trim(), }]; }, }; From 539e53b8cf426673316874a9206366603a0422a9 Mon Sep 17 00:00:00 2001 From: KazariEX Date: Thu, 24 Jul 2025 15:12:17 +0800 Subject: [PATCH 12/85] test(tsc): skip dts snapshots for specific cases --- .../tsc/tests/__snapshots__/dts.spec.ts.snap | 27 ------------------- packages/tsc/tests/dts.spec.ts | 4 ++- 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap index 26ac39b893..5a4791ef21 100644 --- a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap +++ b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap @@ -1,32 +1,5 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`vue-tsc-dts > Input: #4577/main.vue, Output: #4577/main.vue.d.ts 1`] = ` -"export type BaseRow = { - value: string; -}; -declare const _default: (__VLS_props: NonNullable>["props"], __VLS_ctx?: __VLS_PrettifyLocal>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable>["expose"], __VLS_setup?: Promise<{ - props: __VLS_PrettifyLocal & Omit<{} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, never> & { - nonGeneric: string; - rows: Row[]; - } & Partial<{}>> & import("vue").PublicProps; - expose(exposed: import("vue").ShallowUnwrapRef<{}>): void; - attrs: any; - slots: { - default?: (props: { - row: Row; - }) => any; - }; - emit: {}; -}>) => import("vue").VNode & { - __ctx?: Awaited; -}; -export default _default; -type __VLS_PrettifyLocal = { - [K in keyof T as K]: T[K]; -} & {}; -" -`; - exports[`vue-tsc-dts > Input: empty-component/component.vue, Output: empty-component/component.vue.d.ts 1`] = ` "declare const _default: import("vue").DefineComponent2<{ setup(): void; diff --git a/packages/tsc/tests/dts.spec.ts b/packages/tsc/tests/dts.spec.ts index 7a67424d75..8815da600d 100644 --- a/packages/tsc/tests/dts.spec.ts +++ b/packages/tsc/tests/dts.spec.ts @@ -71,6 +71,9 @@ describe('vue-tsc-dts', () => { }); function readFilesRecursive(dir: string) { + if (path.relative(workspace, dir).startsWith('#')) { + return []; + } const result: string[] = []; for (const file of fs.readdirSync(dir)) { @@ -86,7 +89,6 @@ function readFilesRecursive(dir: string) { result.push(filepath); } } - return result; } From c964b327c4215ead6f18e4dfaddc5e12ab90e91e Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Thu, 24 Jul 2025 09:17:52 +0200 Subject: [PATCH 13/85] fix(component-meta): filter events out of props (#5547) --- packages/component-meta/lib/base.ts | 10 ++++++++-- packages/component-meta/tests/index.spec.ts | 15 +++++++++++++++ test-workspace/component-meta/#5546/main.vue | 14 ++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test-workspace/component-meta/#5546/main.vue diff --git a/packages/component-meta/lib/base.ts b/packages/component-meta/lib/base.ts index 7a420d244a..8797527636 100644 --- a/packages/component-meta/lib/base.ts +++ b/packages/component-meta/lib/base.ts @@ -240,7 +240,7 @@ interface ComponentMeta { let _slots: ReturnType | undefined; let _exposed: ReturnType | undefined; - return { + const meta = { get type() { return _type ?? (_type = getType()); }, @@ -258,6 +258,8 @@ interface ComponentMeta { }, }; + return meta; + function getType() { const $type = symbolProperties.find(prop => prop.escapedName === 'type'); @@ -278,6 +280,10 @@ interface ComponentMeta { const type = typeChecker.getTypeOfSymbolAtLocation($props, symbolNode); const properties = type.getProperties(); + const eventProps = new Set( + meta.events.map(event => `on${event.name.charAt(0).toUpperCase()}${event.name.slice(1)}`), + ); + result = properties .map(prop => { const { @@ -286,7 +292,7 @@ interface ComponentMeta { return resolveNestedProperties(prop); }) - .filter(prop => !vnodeEventRegex.test(prop.name)); + .filter(prop => !vnodeEventRegex.test(prop.name) && !eventProps.has(prop.name)); } // fill global diff --git a/packages/component-meta/tests/index.spec.ts b/packages/component-meta/tests/index.spec.ts index 4aa7ba3012..8d6a6e3349 100644 --- a/packages/component-meta/tests/index.spec.ts +++ b/packages/component-meta/tests/index.spec.ts @@ -1168,6 +1168,21 @@ const worker = (checker: ComponentMetaChecker, withTsconfig: boolean) => `); }); + test('component with both props and events', () => { + const componentPath = path.resolve(__dirname, '../../../test-workspace/component-meta/#5546/main.vue'); + const meta = checker.getComponentMeta(componentPath); + + expect(meta.type).toEqual(TypeMeta.Class); + + // Nothing special about this prop + expect(meta.props.find(prop => prop.name === 'title')).toBeDefined(); + // Event + expect(meta.props.find(prop => prop.name === 'onClose')).toBeUndefined(); + expect(meta.events.find(event => event.name === 'close')).toBeDefined(); + // Prop that starts with `on` + expect(meta.props.find(prop => prop.name === 'onCompleted')).toBeDefined(); + }); + test('non-component', () => { const componentPath = path.resolve( __dirname, diff --git a/test-workspace/component-meta/#5546/main.vue b/test-workspace/component-meta/#5546/main.vue new file mode 100644 index 0000000000..d200323eff --- /dev/null +++ b/test-workspace/component-meta/#5546/main.vue @@ -0,0 +1,14 @@ + + + \ No newline at end of file From f24ca40d611c28539b814276d7e10908d2bd7433 Mon Sep 17 00:00:00 2001 From: KazariEX Date: Thu, 24 Jul 2025 21:04:36 +0800 Subject: [PATCH 14/85] refactor(typescript-plugin): remove unnecessary `isTsPlugin` and `asScriptId` --- packages/typescript-plugin/index.ts | 3 -- packages/typescript-plugin/lib/common.ts | 32 ++++++++----------- .../lib/requests/collectExtractProps.ts | 6 ++-- .../lib/requests/getComponentDirectives.ts | 4 +-- .../lib/requests/getComponentEvents.ts | 4 +-- .../lib/requests/getComponentNames.ts | 4 +-- .../lib/requests/getComponentProps.ts | 4 +-- .../lib/requests/getComponentSlots.ts | 4 +-- .../lib/requests/getElementAttrs.ts | 4 +-- .../lib/requests/getElementNames.ts | 4 +-- .../lib/requests/getPropertiesAtLocation.ts | 8 ++--- .../typescript-plugin/lib/requests/types.ts | 6 ++-- 12 files changed, 36 insertions(+), 47 deletions(-) diff --git a/packages/typescript-plugin/index.ts b/packages/typescript-plugin/index.ts index 0d3e048b21..7ddf482836 100644 --- a/packages/typescript-plugin/index.ts +++ b/packages/typescript-plugin/index.ts @@ -40,7 +40,6 @@ export = createLanguageServicePlugin( language, info.languageService, vueOptions, - fileName => fileName, ); // #3963 @@ -166,8 +165,6 @@ export = createLanguageServicePlugin( languageService: service[2], languageServiceHost: service[1], language: service[0], - isTsPlugin: true, - asScriptId: (fileName: string) => fileName, }; } }, diff --git a/packages/typescript-plugin/lib/common.ts b/packages/typescript-plugin/lib/common.ts index 043215657a..e7710bcdc8 100644 --- a/packages/typescript-plugin/lib/common.ts +++ b/packages/typescript-plugin/lib/common.ts @@ -4,24 +4,23 @@ import type * as ts from 'typescript'; const windowsPathReg = /\\/g; -export function createVueLanguageServiceProxy( +export function createVueLanguageServiceProxy( ts: typeof import('typescript'), - language: Language, + language: Language, languageService: ts.LanguageService, vueOptions: VueCompilerOptions, - asScriptId: (fileName: string) => T, ) { const proxyCache = new Map(); const getProxyMethod = (target: ts.LanguageService, p: string | symbol): Function | undefined => { switch (p) { case 'getCompletionsAtPosition': - return getCompletionsAtPosition(ts, language, vueOptions, asScriptId, target[p]); + return getCompletionsAtPosition(ts, language, vueOptions, target[p]); case 'getCompletionEntryDetails': - return getCompletionEntryDetails(language, asScriptId, target[p]); + return getCompletionEntryDetails(language, target[p]); case 'getCodeFixesAtPosition': return getCodeFixesAtPosition(target[p]); case 'getDefinitionAndBoundSpan': - return getDefinitionAndBoundSpan(ts, language, languageService, vueOptions, asScriptId, target[p]); + return getDefinitionAndBoundSpan(ts, language, languageService, vueOptions, target[p]); } }; @@ -44,11 +43,10 @@ export function createVueLanguageServiceProxy( }); } -function getCompletionsAtPosition( +function getCompletionsAtPosition( ts: typeof import('typescript'), - language: Language, + language: Language, vueOptions: VueCompilerOptions, - asScriptId: (fileName: string) => T, getCompletionsAtPosition: ts.LanguageService['getCompletionsAtPosition'], ): ts.LanguageService['getCompletionsAtPosition'] { return (filePath, position, options, formattingSettings) => { @@ -63,7 +61,7 @@ function getCompletionsAtPosition( ); // filter global variables in template and styles - const sourceScript = language.scripts.get(asScriptId(fileName)); + const sourceScript = language.scripts.get(fileName); const root = sourceScript?.generated?.root; if (root instanceof VueVirtualCode) { const blocks = [ @@ -131,9 +129,8 @@ function getCompletionsAtPosition( }; } -function getCompletionEntryDetails( - language: Language, - asScriptId: (fileName: string) => T, +function getCompletionEntryDetails( + language: Language, getCompletionEntryDetails: ts.LanguageService['getCompletionEntryDetails'], ): ts.LanguageService['getCompletionEntryDetails'] { return (...args) => { @@ -158,7 +155,7 @@ function getCompletionEntryDetails( if (args[6]?.__isAutoImport) { // @ts-expect-error const { fileName } = args[6].__isAutoImport; - const sourceScript = language.scripts.get(asScriptId(fileName)); + const sourceScript = language.scripts.get(fileName); if (sourceScript?.generated?.root instanceof VueVirtualCode) { const sfc = sourceScript.generated.root.vueSfc; if (!sfc?.descriptor.script && !sfc?.descriptor.scriptSetup) { @@ -190,12 +187,11 @@ function getCodeFixesAtPosition( }; } -function getDefinitionAndBoundSpan( +function getDefinitionAndBoundSpan( ts: typeof import('typescript'), - language: Language, + language: Language, languageService: ts.LanguageService, vueOptions: VueCompilerOptions, - asScriptId: (fileName: string) => T, getDefinitionAndBoundSpan: ts.LanguageService['getDefinitionAndBoundSpan'], ): ts.LanguageService['getDefinitionAndBoundSpan'] { return (fileName, position) => { @@ -205,7 +201,7 @@ function getDefinitionAndBoundSpan( } const program = languageService.getProgram()!; - const sourceScript = language.scripts.get(asScriptId(fileName)); + const sourceScript = language.scripts.get(fileName); if (!sourceScript?.generated) { return result; } diff --git a/packages/typescript-plugin/lib/requests/collectExtractProps.ts b/packages/typescript-plugin/lib/requests/collectExtractProps.ts index e1f7b609b1..d272872947 100644 --- a/packages/typescript-plugin/lib/requests/collectExtractProps.ts +++ b/packages/typescript-plugin/lib/requests/collectExtractProps.ts @@ -6,9 +6,9 @@ export function collectExtractProps( fileName: string, templateCodeRange: [number, number], ) { - const { typescript: ts, languageService, language, isTsPlugin, asScriptId } = this; + const { typescript: ts, languageService, language } = this; - const sourceScript = language.scripts.get(asScriptId(fileName)); + const sourceScript = language.scripts.get(fileName); if (!sourceScript?.generated) { return; } @@ -41,7 +41,7 @@ export function collectExtractProps( for (const map of maps) { let mapped = false; for ( - const source of map.toSourceLocation(name.getEnd() - (isTsPlugin ? sourceScript.snapshot.getLength() : 0)) + const source of map.toSourceLocation(name.getEnd() - sourceScript.snapshot.getLength()) ) { if ( source[0] >= sfc.template!.startTagEnd + templateCodeRange[0] diff --git a/packages/typescript-plugin/lib/requests/getComponentDirectives.ts b/packages/typescript-plugin/lib/requests/getComponentDirectives.ts index c554da1209..c9b4dec522 100644 --- a/packages/typescript-plugin/lib/requests/getComponentDirectives.ts +++ b/packages/typescript-plugin/lib/requests/getComponentDirectives.ts @@ -15,8 +15,8 @@ export function getComponentDirectives( this: RequestContext, fileName: string, ) { - const { typescript: ts, language, languageService, asScriptId } = this; - const volarFile = language.scripts.get(asScriptId(fileName)); + const { typescript: ts, language, languageService } = this; + const volarFile = language.scripts.get(fileName); if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { return; } diff --git a/packages/typescript-plugin/lib/requests/getComponentEvents.ts b/packages/typescript-plugin/lib/requests/getComponentEvents.ts index ce602429f0..4e5cecf15f 100644 --- a/packages/typescript-plugin/lib/requests/getComponentEvents.ts +++ b/packages/typescript-plugin/lib/requests/getComponentEvents.ts @@ -7,8 +7,8 @@ export function getComponentEvents( fileName: string, tag: string, ) { - const { typescript: ts, language, languageService, asScriptId } = this; - const volarFile = language.scripts.get(asScriptId(fileName)); + const { typescript: ts, language, languageService } = this; + const volarFile = language.scripts.get(fileName); if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { return; } diff --git a/packages/typescript-plugin/lib/requests/getComponentNames.ts b/packages/typescript-plugin/lib/requests/getComponentNames.ts index 37e80b2de3..e80a7ac659 100644 --- a/packages/typescript-plugin/lib/requests/getComponentNames.ts +++ b/packages/typescript-plugin/lib/requests/getComponentNames.ts @@ -7,8 +7,8 @@ export function getComponentNames( this: RequestContext, fileName: string, ) { - const { typescript: ts, language, languageService, asScriptId } = this; - const volarFile = language.scripts.get(asScriptId(fileName)); + const { typescript: ts, language, languageService } = this; + const volarFile = language.scripts.get(fileName); if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { return; } diff --git a/packages/typescript-plugin/lib/requests/getComponentProps.ts b/packages/typescript-plugin/lib/requests/getComponentProps.ts index b4c96cfe9b..f8c88b25e6 100644 --- a/packages/typescript-plugin/lib/requests/getComponentProps.ts +++ b/packages/typescript-plugin/lib/requests/getComponentProps.ts @@ -17,8 +17,8 @@ export function getComponentProps( fileName: string, tag: string, ) { - const { typescript: ts, language, languageService, asScriptId } = this; - const volarFile = language.scripts.get(asScriptId(fileName)); + const { typescript: ts, language, languageService } = this; + const volarFile = language.scripts.get(fileName); if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { return; } diff --git a/packages/typescript-plugin/lib/requests/getComponentSlots.ts b/packages/typescript-plugin/lib/requests/getComponentSlots.ts index 3211e4231e..b48828b164 100644 --- a/packages/typescript-plugin/lib/requests/getComponentSlots.ts +++ b/packages/typescript-plugin/lib/requests/getComponentSlots.ts @@ -6,8 +6,8 @@ export function getComponentSlots( this: RequestContext, fileName: string, ) { - const { typescript: ts, language, languageService, asScriptId } = this; - const volarFile = language.scripts.get(asScriptId(fileName)); + const { typescript: ts, language, languageService } = this; + const volarFile = language.scripts.get(fileName); if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { return; } diff --git a/packages/typescript-plugin/lib/requests/getElementAttrs.ts b/packages/typescript-plugin/lib/requests/getElementAttrs.ts index 6c06fb785d..dee3f83373 100644 --- a/packages/typescript-plugin/lib/requests/getElementAttrs.ts +++ b/packages/typescript-plugin/lib/requests/getElementAttrs.ts @@ -7,8 +7,8 @@ export function getElementAttrs( fileName: string, tagName: string, ) { - const { typescript: ts, language, languageService, asScriptId } = this; - const volarFile = language.scripts.get(asScriptId(fileName)); + const { typescript: ts, language, languageService } = this; + const volarFile = language.scripts.get(fileName); if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { return; } diff --git a/packages/typescript-plugin/lib/requests/getElementNames.ts b/packages/typescript-plugin/lib/requests/getElementNames.ts index f10906b2d7..15ac70b8ee 100644 --- a/packages/typescript-plugin/lib/requests/getElementNames.ts +++ b/packages/typescript-plugin/lib/requests/getElementNames.ts @@ -7,8 +7,8 @@ export function getElementNames( this: RequestContext, fileName: string, ) { - const { typescript: ts, language, languageService, asScriptId } = this; - const volarFile = language.scripts.get(asScriptId(fileName)); + const { typescript: ts, language, languageService } = this; + const volarFile = language.scripts.get(fileName); if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { return; } diff --git a/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts b/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts index efb1aea7a5..f6131f43d4 100644 --- a/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts +++ b/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts @@ -9,10 +9,10 @@ export function getPropertiesAtLocation( fileName: string, position: number, ) { - const { languageService, language, typescript: ts, isTsPlugin, asScriptId } = this; + const { languageService, language, typescript: ts } = this; // mapping - const file = language.scripts.get(asScriptId(fileName)); + const file = language.scripts.get(fileName); if (file?.generated) { const virtualScript = file.generated.languagePlugin.typescript?.getServiceScript(file.generated.root); if (!virtualScript) { @@ -34,9 +34,7 @@ export function getPropertiesAtLocation( if (!mapped) { return; } - if (isTsPlugin) { - position += file.snapshot.getLength(); - } + position += file.snapshot.getLength(); } const program = languageService.getProgram()!; diff --git a/packages/typescript-plugin/lib/requests/types.ts b/packages/typescript-plugin/lib/requests/types.ts index 8c4a43a3f7..77ebce0657 100644 --- a/packages/typescript-plugin/lib/requests/types.ts +++ b/packages/typescript-plugin/lib/requests/types.ts @@ -1,11 +1,9 @@ import type { Language } from '@vue/language-core'; import type * as ts from 'typescript'; -export interface RequestContext { +export interface RequestContext { typescript: typeof ts; languageService: ts.LanguageService; languageServiceHost: ts.LanguageServiceHost; - language: Language; - isTsPlugin: boolean; - asScriptId: (fileName: string) => T; + language: Language; } From 8c7b41f1678e52af01c78f33b9671385588293cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Thu, 24 Jul 2025 23:00:21 +0800 Subject: [PATCH 15/85] refactor(language-service): reduce boilerplate code for virtual code operation (#5556) --- packages/language-service/lib/plugins/css.ts | 19 +- .../lib/plugins/typescript-semantic-tokens.ts | 16 +- .../language-service/lib/plugins/utils.ts | 56 +++- .../lib/plugins/vue-autoinsert-dotvalue.ts | 25 +- .../lib/plugins/vue-compiler-dom-errors.ts | 60 ++-- .../plugins/vue-component-semantic-tokens.ts | 91 +++--- .../lib/plugins/vue-document-drop.ts | 23 +- .../lib/plugins/vue-document-highlights.ts | 16 +- .../lib/plugins/vue-extract-file.ts | 31 +- .../lib/plugins/vue-global-types-error.ts | 19 +- .../lib/plugins/vue-inlayhints.ts | 17 +- .../lib/plugins/vue-missing-props-hints.ts | 26 +- .../lib/plugins/vue-scoped-class-links.ts | 19 +- .../language-service/lib/plugins/vue-sfc.ts | 295 +++++++++--------- .../plugins/vue-suggest-define-assignment.ts | 22 +- .../lib/plugins/vue-template-ref-links.ts | 19 +- .../lib/plugins/vue-template.ts | 38 +-- .../lib/plugins/vue-twoslash-queries.ts | 25 +- packages/typescript-plugin/index.ts | 5 +- 19 files changed, 347 insertions(+), 475 deletions(-) diff --git a/packages/language-service/lib/plugins/css.ts b/packages/language-service/lib/plugins/css.ts index 0df678bd71..68b2102959 100644 --- a/packages/language-service/lib/plugins/css.ts +++ b/packages/language-service/lib/plugins/css.ts @@ -1,8 +1,8 @@ import type { LanguageServicePlugin, TextDocument, VirtualCode } from '@volar/language-service'; -import { isRenameEnabled, VueVirtualCode } from '@vue/language-core'; +import { isRenameEnabled } from '@vue/language-core'; import { create as baseCreate, type Provide } from 'volar-service-css'; import type * as css from 'vscode-css-languageservice'; -import { URI } from 'vscode-uri'; +import { getEmbeddedInfo } from './utils'; export function create(): LanguageServicePlugin { const base = baseCreate({ scssDocumentSelector: ['scss', 'postcss'] }); @@ -54,20 +54,13 @@ export function create(): LanguageServicePlugin { document: TextDocument, position: css.Position, ) { - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || !virtualCode?.id.startsWith('style_')) { + const info = getEmbeddedInfo(context, document, id => id.startsWith('style_')); + if (!info) { return false; } + const { sourceScript, virtualCode, root } = info; - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { - return false; - } - - const block = root.sfc.styles.find(style => style.name === decoded![1]); + const block = root.sfc.styles.find(style => style.name === virtualCode.id); if (!block) { return false; } diff --git a/packages/language-service/lib/plugins/typescript-semantic-tokens.ts b/packages/language-service/lib/plugins/typescript-semantic-tokens.ts index 298a3c67e7..11600eb026 100644 --- a/packages/language-service/lib/plugins/typescript-semantic-tokens.ts +++ b/packages/language-service/lib/plugins/typescript-semantic-tokens.ts @@ -1,7 +1,6 @@ import type { LanguageServiceContext, LanguageServicePlugin } from '@volar/language-service'; -import { VueVirtualCode } from '@vue/language-core'; import { convertClassificationsToSemanticTokens } from 'volar-service-typescript/lib/semanticFeatures/semanticTokens'; -import { URI } from 'vscode-uri'; +import { getEmbeddedInfo } from './utils'; export function create( getTsPluginClient?: ( @@ -43,18 +42,11 @@ export function create( return { async provideDocumentSemanticTokens(document, range, legend) { - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || virtualCode?.id !== 'main') { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, document, 'main'); + if (!info) { return; } + const { root } = info; const start = document.offsetAt(range.start); const end = document.offsetAt(range.end); diff --git a/packages/language-service/lib/plugins/utils.ts b/packages/language-service/lib/plugins/utils.ts index 0abc86981f..60ddd36d42 100644 --- a/packages/language-service/lib/plugins/utils.ts +++ b/packages/language-service/lib/plugins/utils.ts @@ -1,12 +1,56 @@ -import type { TextDocument } from '@volar/language-service'; +import { type LanguageServiceContext, type SourceScript, type TextDocument } from '@volar/language-service'; +import { VueVirtualCode } from '@vue/language-core'; +import { URI } from 'vscode-uri'; export function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } -export function isTsDocument(document: TextDocument) { - return document.languageId === 'javascript' - || document.languageId === 'typescript' - || document.languageId === 'javascriptreact' - || document.languageId === 'typescriptreact'; +export function getEmbeddedInfo( + context: LanguageServiceContext, + document: TextDocument, + embeddedCodeId?: string | ((id: string) => boolean), + languageId?: string, +) { + const uri = URI.parse(document.uri); + const decoded = context.decodeEmbeddedDocumentUri(uri); + if (!decoded) { + return; + } + + if (embeddedCodeId) { + if (typeof embeddedCodeId === 'string') { + if (decoded[1] !== embeddedCodeId) { + return; + } + } + else if (!embeddedCodeId(decoded[1])) { + return; + } + } + + if (languageId && document.languageId !== languageId) { + return; + } + + const sourceScript = context.language.scripts.get(decoded[0]); + if (!sourceScript?.generated) { + return; + } + + const virtualCode = sourceScript.generated.embeddedCodes.get(decoded[1]); + if (!virtualCode) { + return; + } + + const root = sourceScript.generated.root; + if (!(root instanceof VueVirtualCode)) { + return; + } + + return { + sourceScript: sourceScript as Required>, + virtualCode, + root, + }; } diff --git a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts index da79c6c3cd..44484695bd 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts @@ -1,8 +1,7 @@ import type { LanguageServiceContext, LanguageServicePlugin, TextDocument } from '@volar/language-service'; -import { hyphenateAttr, VueVirtualCode } from '@vue/language-core'; +import { hyphenateAttr } from '@vue/language-core'; import type * as ts from 'typescript'; -import { URI } from 'vscode-uri'; -import { isTsDocument, sleep } from './utils'; +import { getEmbeddedInfo, sleep } from './utils'; export function create( ts: typeof import('typescript'), @@ -24,12 +23,13 @@ export function create( return { async provideAutoInsertSnippet(document, selection, change) { - // selection must at end of change - if (document.offsetAt(selection) !== change.rangeOffset + change.text.length) { + const info = getEmbeddedInfo(context, document, id => id.startsWith('script_')); + if (!info) { return; } - if (!isTsDocument(document)) { + // selection must at end of change + if (document.offsetAt(selection) !== change.rangeOffset + change.text.length) { return; } @@ -49,18 +49,7 @@ export function create( return; } - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || !virtualCode) { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { - return; - } + const { sourceScript, virtualCode, root } = info; const { sfc } = root; const blocks = [sfc.script, sfc.scriptSetup].filter(block => !!block); diff --git a/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts b/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts index c62d2f783c..4721d69d32 100644 --- a/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts +++ b/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts @@ -1,6 +1,5 @@ -import type { Diagnostic, DiagnosticSeverity, LanguageServicePlugin, TextDocument } from '@volar/language-service'; -import { VueVirtualCode } from '@vue/language-core'; -import { URI } from 'vscode-uri'; +import type { Diagnostic, DiagnosticSeverity, LanguageServicePlugin } from '@volar/language-service'; +import { getEmbeddedInfo } from './utils'; export function create(): LanguageServicePlugin { return { @@ -14,61 +13,42 @@ export function create(): LanguageServicePlugin { create(context) { return { provideDiagnostics(document) { - if (!isSupportedDocument(document)) { + const info = getEmbeddedInfo(context, document, 'template'); + if (!info) { return; } + const { root } = info; - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - - const root = sourceScript?.generated?.root; - if (!(root instanceof VueVirtualCode)) { + const { template } = root.sfc; + if (!template) { return; } - const templateErrors: Diagnostic[] = []; - const { template } = root.sfc; - - if (template) { - for (const error of template.errors) { - onCompilerError(error, 1 satisfies typeof DiagnosticSeverity.Error); - } + const diagnostics: Diagnostic[] = []; - for (const warning of template.warnings) { - onCompilerError(warning, 2 satisfies typeof DiagnosticSeverity.Warning); - } - - function onCompilerError( - error: NonNullable['errors'][number], - severity: DiagnosticSeverity, - ) { - const templateHtmlRange = { - start: error.loc?.start.offset ?? 0, - end: error.loc?.end.offset ?? 0, - }; - let errorMessage = error.message; - - templateErrors.push({ + for ( + const [errors, severity] of [ + [template.errors, 1 satisfies typeof DiagnosticSeverity.Error], + [template.warnings, 2 satisfies typeof DiagnosticSeverity.Warning], + ] as const + ) { + for (const error of errors) { + diagnostics.push({ range: { - start: document.positionAt(templateHtmlRange.start), - end: document.positionAt(templateHtmlRange.end), + start: document.positionAt(error.loc?.start.offset ?? 0), + end: document.positionAt(error.loc?.end.offset ?? 0), }, severity, code: error.code, source: 'vue', - message: errorMessage, + message: error.message, }); } } - return templateErrors; + return diagnostics; }, }; }, }; - - function isSupportedDocument(document: TextDocument) { - return document.languageId === 'jade' || document.languageId === 'html'; - } } diff --git a/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts b/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts index d4110da8d2..b62c927110 100644 --- a/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts +++ b/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts @@ -1,7 +1,7 @@ import type { LanguageServiceContext, LanguageServicePlugin, SemanticToken } from '@volar/language-service'; -import { forEachElementNode, hyphenateTag, VueVirtualCode } from '@vue/language-core'; +import { forEachElementNode, hyphenateTag } from '@vue/language-core'; import type * as ts from 'typescript'; -import { URI } from 'vscode-uri'; +import { getEmbeddedInfo } from './utils'; export function create( getTsPluginClient?: ( @@ -23,64 +23,32 @@ export function create( return { async provideDocumentSemanticTokens(document, range, legend) { - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || virtualCode?.id !== 'template') { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, document, 'template'); + if (!info) { return; } + const { root } = info; const { template } = root.sfc; - if (!template) { + if (!template?.ast) { return; } - const result: SemanticToken[] = []; - - const tokenTypes = legend.tokenTypes.indexOf('component'); - const componentSpans = await getComponentSpans(root.fileName, template, { - start: document.offsetAt(range.start), - length: document.offsetAt(range.end) - document.offsetAt(range.start), - }); - - for (const span of componentSpans) { - const position = document.positionAt(span.start); - result.push([ - position.line, - position.character, - span.length, - tokenTypes, - 0, - ]); - } - return result; - }, - }; + const componentSpans: ts.TextSpan[] = []; + const start = document.offsetAt(range.start); + const end = document.offsetAt(range.end); - async function getComponentSpans( - fileName: string, - template: NonNullable, - spanTemplateRange: ts.TextSpan, - ) { - const result: ts.TextSpan[] = []; - const validComponentNames = await tsPluginClient?.getComponentNames(fileName) ?? []; - const elements = new Set(await tsPluginClient?.getElementNames(fileName) ?? []); - const components = new Set([ - ...validComponentNames, - ...validComponentNames.map(hyphenateTag), - ]); + const validComponentNames = await tsPluginClient?.getComponentNames(root.fileName) ?? []; + const elements = new Set(await tsPluginClient?.getElementNames(root.fileName) ?? []); + const components = new Set([ + ...validComponentNames, + ...validComponentNames.map(hyphenateTag), + ]); - if (template.ast) { for (const node of forEachElementNode(template.ast)) { if ( - node.loc.end.offset <= spanTemplateRange.start - || node.loc.start.offset >= (spanTemplateRange.start + spanTemplateRange.length) + node.loc.end.offset <= start + || node.loc.start.offset >= end ) { continue; } @@ -89,21 +57,36 @@ export function create( if (template.lang === 'html') { start += '<'.length; } - result.push({ + componentSpans.push({ start, length: node.tag.length, }); if (template.lang === 'html' && !node.isSelfClosing) { - result.push({ + componentSpans.push({ start: node.loc.start.offset + node.loc.source.lastIndexOf(node.tag), length: node.tag.length, }); } } } - } - return result; - } + + const result: SemanticToken[] = []; + const tokenType = legend.tokenTypes.indexOf('component'); + + for (const span of componentSpans) { + const position = document.positionAt(span.start); + result.push([ + position.line, + position.character, + span.length, + tokenType, + 0, + ]); + } + + return result; + }, + }; }, }; } diff --git a/packages/language-service/lib/plugins/vue-document-drop.ts b/packages/language-service/lib/plugins/vue-document-drop.ts index caf2c3abfe..f80be41aa3 100644 --- a/packages/language-service/lib/plugins/vue-document-drop.ts +++ b/packages/language-service/lib/plugins/vue-document-drop.ts @@ -4,13 +4,14 @@ import type { LanguageServicePlugin, WorkspaceEdit, } from '@volar/language-service'; -import { forEachEmbeddedCode, VueVirtualCode } from '@vue/language-core'; +import { forEachEmbeddedCode } from '@vue/language-core'; import { camelize, capitalize, hyphenate } from '@vue/shared'; import { posix as path } from 'path-browserify'; import { getUserPreferences } from 'volar-service-typescript/lib/configs/getUserPreferences'; import { URI } from 'vscode-uri'; import { checkCasing, TagNameCasing } from '../nameCasing'; import { createAddComponentToOptionEdit, getLastImportNode } from '../plugins/vue-extract-file'; +import { getEmbeddedInfo } from './utils'; export function create( ts: typeof import('typescript'), @@ -28,21 +29,11 @@ export function create( return { async provideDocumentDropEdits(document, _position, dataTransfer) { - if (document.languageId !== 'html') { - return; - } - - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - if (!sourceScript?.generated) { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, document, 'template', 'html'); + if (!info) { return; } + const { sourceScript, root } = info; let importUri: string | undefined; for (const [mimeType, item] of dataTransfer) { @@ -60,7 +51,7 @@ export function create( return; } - const casing = await checkCasing(context, decoded![0]); + const casing = await checkCasing(context, sourceScript.id); const baseName = path.basename(importUri); const newName = capitalize(camelize(baseName.slice(0, baseName.lastIndexOf('.')))); @@ -73,7 +64,7 @@ export function create( let importPath: string | undefined; - const serviceScript = sourceScript.generated?.languagePlugin.typescript?.getServiceScript(root); + const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(root); if (tsPluginClient && serviceScript) { const tsDocumentUri = context.encodeEmbeddedDocumentUri(sourceScript.id, serviceScript.code.id); const tsDocument = context.documents.get( diff --git a/packages/language-service/lib/plugins/vue-document-highlights.ts b/packages/language-service/lib/plugins/vue-document-highlights.ts index cae6000347..972519c4bd 100644 --- a/packages/language-service/lib/plugins/vue-document-highlights.ts +++ b/packages/language-service/lib/plugins/vue-document-highlights.ts @@ -1,7 +1,6 @@ import type { DocumentHighlightKind, LanguageServicePlugin } from '@volar/language-service'; -import { VueVirtualCode } from '@vue/language-core'; import type * as ts from 'typescript'; -import { URI } from 'vscode-uri'; +import { getEmbeddedInfo } from './utils'; export function create( getDocumentHighlights: (fileName: string, position: number) => Promise, @@ -14,18 +13,11 @@ export function create( create(context) { return { async provideDocumentHighlights(document, position) { - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || virtualCode?.id !== 'main') { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, document, 'main'); + if (!info) { return; } + const { root } = info; const result = await getDocumentHighlights(root.fileName, document.offsetAt(position)); diff --git a/packages/language-service/lib/plugins/vue-extract-file.ts b/packages/language-service/lib/plugins/vue-extract-file.ts index fcac7a576c..229281d074 100644 --- a/packages/language-service/lib/plugins/vue-extract-file.ts +++ b/packages/language-service/lib/plugins/vue-extract-file.ts @@ -6,9 +6,10 @@ import type { TextEdit, } from '@volar/language-service'; import type { ExpressionNode, TemplateChildNode } from '@vue/compiler-dom'; -import { type Sfc, tsCodegen, VueVirtualCode } from '@vue/language-core'; +import { type Sfc, tsCodegen } from '@vue/language-core'; import type * as ts from 'typescript'; import { URI } from 'vscode-uri'; +import { getEmbeddedInfo } from './utils'; interface ActionData { uri: string; @@ -46,18 +47,11 @@ export function create( return; } - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || virtualCode?.id !== 'template') { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, document, 'template'); + if (!info) { return; } + const { root } = info; const { sfc } = root; const script = sfc.scriptSetup ?? sfc.script; @@ -87,18 +81,11 @@ export function create( const { uri, range, newName } = codeAction.data as ActionData; const [startOffset, endOffset]: [number, number] = range; - const parsedUri = URI.parse(uri); - const decoded = context.decodeEmbeddedDocumentUri(parsedUri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || virtualCode?.id !== 'template') { - return codeAction; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, { uri } as any, 'template'); + if (!info) { return codeAction; } + const { sourceScript, virtualCode, root } = info; const { sfc } = root; const script = sfc.scriptSetup ?? sfc.script; @@ -121,7 +108,7 @@ export function create( const scriptInitialIndent = await context.env.getConfiguration!('vue.format.script.initialIndent') ?? false; - const document = context.documents.get(parsedUri, virtualCode.languageId, virtualCode.snapshot); + const document = context.documents.get(URI.parse(uri), virtualCode.languageId, virtualCode.snapshot); const sfcDocument = context.documents.get(sourceScript.id, sourceScript.languageId, sourceScript.snapshot); const newUri = sfcDocument.uri.slice(0, sfcDocument.uri.lastIndexOf('/') + 1) + `${newName}.vue`; const lastImportNode = getLastImportNode(ts, script.ast); diff --git a/packages/language-service/lib/plugins/vue-global-types-error.ts b/packages/language-service/lib/plugins/vue-global-types-error.ts index 9248fb1d9d..7bf78db634 100644 --- a/packages/language-service/lib/plugins/vue-global-types-error.ts +++ b/packages/language-service/lib/plugins/vue-global-types-error.ts @@ -1,6 +1,5 @@ import type { DiagnosticSeverity, LanguageServicePlugin } from '@volar/language-service'; -import { VueVirtualCode } from '@vue/language-core'; -import { URI } from 'vscode-uri'; +import { getEmbeddedInfo } from './utils'; export function create(): LanguageServicePlugin { return { @@ -14,21 +13,11 @@ export function create(): LanguageServicePlugin { create(context) { return { provideDiagnostics(document) { - if (document.languageId !== 'vue-root-tags') { - return; - } - - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - if (!sourceScript?.generated) { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, document, 'root_tags'); + if (!info) { return; } + const { root } = info; const { vueCompilerOptions } = root; const globalTypesPath = vueCompilerOptions.globalTypesPath(root.fileName); diff --git a/packages/language-service/lib/plugins/vue-inlayhints.ts b/packages/language-service/lib/plugins/vue-inlayhints.ts index 24ce557efd..02f5f4bf73 100644 --- a/packages/language-service/lib/plugins/vue-inlayhints.ts +++ b/packages/language-service/lib/plugins/vue-inlayhints.ts @@ -1,7 +1,7 @@ import type { InlayHint, InlayHintKind, LanguageServicePlugin } from '@volar/language-service'; -import { collectBindingIdentifiers, tsCodegen, VueVirtualCode } from '@vue/language-core'; +import { collectBindingIdentifiers, tsCodegen } from '@vue/language-core'; import type * as ts from 'typescript'; -import { URI } from 'vscode-uri'; +import { getEmbeddedInfo } from './utils'; export function create(ts: typeof import('typescript')): LanguageServicePlugin { return { @@ -12,18 +12,11 @@ export function create(ts: typeof import('typescript')): LanguageServicePlugin { create(context) { return { async provideInlayHints(document, range) { - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || virtualCode?.id !== 'main') { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, document, 'main'); + if (!info) { return; } + const { root } = info; const settings: Record = {}; async function getSettingEnabled(key: string) { diff --git a/packages/language-service/lib/plugins/vue-missing-props-hints.ts b/packages/language-service/lib/plugins/vue-missing-props-hints.ts index 39f10f3ebb..78bff9182a 100644 --- a/packages/language-service/lib/plugins/vue-missing-props-hints.ts +++ b/packages/language-service/lib/plugins/vue-missing-props-hints.ts @@ -5,10 +5,10 @@ import type { LanguageServicePlugin, TextDocument, } from '@volar/language-service'; -import { hyphenateAttr, hyphenateTag, VueVirtualCode } from '@vue/language-core'; +import { hyphenateAttr, hyphenateTag } from '@vue/language-core'; import * as html from 'vscode-html-languageservice'; -import { URI } from 'vscode-uri'; import { AttrNameCasing, checkCasing } from '../nameCasing'; +import { getEmbeddedInfo } from './utils'; export function create( getTsPluginClient?: ( @@ -26,34 +26,24 @@ export function create( return { async provideInlayHints(document, range, cancellationToken) { - if (!isSupportedDocument(document)) { + const info = getEmbeddedInfo(context, document, 'template'); + if (!info) { return; } + const { sourceScript, root } = info; const enabled = await context.env.getConfiguration?.('vue.inlayHints.missingProps') ?? false; if (!enabled) { return; } - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - if (!sourceScript?.generated) { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { - return; - } - const scanner = getScanner(context, document); if (!scanner) { return; } const result: InlayHint[] = []; - const casing = await checkCasing(context, decoded![0]); + const casing = await checkCasing(context, sourceScript.id); const components = await tsPluginClient?.getComponentNames(root.fileName) ?? []; const componentProps: Record = {}; @@ -179,8 +169,4 @@ export function create( } } } - - function isSupportedDocument(document: TextDocument) { - return document.languageId === 'jade' || document.languageId === 'html'; - } } diff --git a/packages/language-service/lib/plugins/vue-scoped-class-links.ts b/packages/language-service/lib/plugins/vue-scoped-class-links.ts index 669d8c531a..d02d655df1 100644 --- a/packages/language-service/lib/plugins/vue-scoped-class-links.ts +++ b/packages/language-service/lib/plugins/vue-scoped-class-links.ts @@ -1,6 +1,6 @@ import type { LanguageServicePlugin } from '@volar/language-service'; -import { tsCodegen, VueVirtualCode } from '@vue/language-core'; -import { URI } from 'vscode-uri'; +import { tsCodegen } from '@vue/language-core'; +import { getEmbeddedInfo } from './utils'; export function create(): LanguageServicePlugin { return { @@ -11,18 +11,11 @@ export function create(): LanguageServicePlugin { create(context) { return { provideDocumentLinks(document) { - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || virtualCode?.id !== 'template') { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, document, 'template'); + if (!info) { return; } + const { sourceScript, root } = info; const { sfc } = root; const codegen = tsCodegen.get(sfc); @@ -37,7 +30,7 @@ export function create(): LanguageServicePlugin { continue; } - const styleDocumentUri = context.encodeEmbeddedDocumentUri(decoded![0], 'style_' + i); + const styleDocumentUri = context.encodeEmbeddedDocumentUri(sourceScript.id, 'style_' + i); const styleVirtualCode = sourceScript.generated.embeddedCodes.get('style_' + i); if (!styleVirtualCode) { continue; diff --git a/packages/language-service/lib/plugins/vue-sfc.ts b/packages/language-service/lib/plugins/vue-sfc.ts index c902ce89b8..11584a39af 100644 --- a/packages/language-service/lib/plugins/vue-sfc.ts +++ b/packages/language-service/lib/plugins/vue-sfc.ts @@ -4,16 +4,14 @@ import type { Diagnostic, DiagnosticSeverity, DocumentSymbol, - LanguageServiceContext, LanguageServicePlugin, SymbolKind, - TextDocument, } from '@volar/language-service'; import { VueVirtualCode } from '@vue/language-core'; import { create as createHtmlService } from 'volar-service-html'; import * as html from 'vscode-html-languageservice'; -import { URI } from 'vscode-uri'; import { loadLanguageBlocks } from './data'; +import { getEmbeddedInfo } from './utils'; let sfcDataProvider: html.IHTMLDataProvider | undefined; @@ -26,27 +24,31 @@ export function create(): LanguageServicePlugin { return [sfcDataProvider]; }, async getFormattingOptions(document, options, context) { - return await worker(document, context, async root => { - const formatSettings = await context.env.getConfiguration?.('html.format') ?? {}; - const blockTypes = ['template', 'script', 'style']; + const info = getEmbeddedInfo(context, document, 'root_tags'); + if (!info) { + return {}; + } + const { root } = info; + + const formatSettings = await context.env.getConfiguration?.('html.format') ?? {}; + const blockTypes = ['template', 'script', 'style']; - for (const customBlock of root.sfc.customBlocks) { - blockTypes.push(customBlock.type); - } + for (const customBlock of root.sfc.customBlocks) { + blockTypes.push(customBlock.type); + } - return { - ...options, - ...formatSettings, - wrapAttributes: await context.env.getConfiguration?.('vue.format.wrapAttributes') ?? 'auto', - unformatted: '', - contentUnformatted: blockTypes.join(','), - endWithNewline: options.insertFinalNewline - ? true - : options.trimFinalNewlines - ? false - : document.getText().endsWith('\n'), - }; - }) ?? {}; + return { + ...options, + ...formatSettings, + wrapAttributes: await context.env.getConfiguration?.('vue.format.wrapAttributes') ?? 'auto', + unformatted: '', + contentUnformatted: blockTypes.join(','), + endWithNewline: options.insertFinalNewline + ? true + : options.trimFinalNewlines + ? false + : document.getText().endsWith('\n'), + }; }, }); return { @@ -88,133 +90,141 @@ export function create(): LanguageServicePlugin { return options; }, - provideDiagnostics(document, token) { - return worker(document, context, async root => { - const { vueSfc, sfc } = root; - if (!vueSfc) { - return; - } + async provideDiagnostics(document, token) { + const info = getEmbeddedInfo(context, document, 'root_tags'); + if (!info) { + return []; + } + const { root } = info; + + const { vueSfc, sfc } = root; + if (!vueSfc) { + return; + } - const originalResult = await htmlServiceInstance.provideDiagnostics?.(document, token); - const sfcErrors: Diagnostic[] = []; - const { template } = sfc; + const originalResult = await htmlServiceInstance.provideDiagnostics?.(document, token); + const sfcErrors: Diagnostic[] = []; + const { template } = sfc; - const { - startTagEnd = Infinity, - endTagStart = -Infinity, - } = template ?? {}; + const { + startTagEnd = Infinity, + endTagStart = -Infinity, + } = template ?? {}; - for (const error of vueSfc.errors) { - if ('code' in error) { - const start = error.loc?.start.offset ?? 0; - const end = error.loc?.end.offset ?? 0; - if (end < startTagEnd || start >= endTagStart) { - sfcErrors.push({ - range: { - start: document.positionAt(start), - end: document.positionAt(end), - }, - severity: 1 satisfies typeof DiagnosticSeverity.Error, - code: error.code, - source: 'vue', - message: error.message, - }); - } + for (const error of vueSfc.errors) { + if ('code' in error) { + const start = error.loc?.start.offset ?? 0; + const end = error.loc?.end.offset ?? 0; + if (end < startTagEnd || start >= endTagStart) { + sfcErrors.push({ + range: { + start: document.positionAt(start), + end: document.positionAt(end), + }, + severity: 1 satisfies typeof DiagnosticSeverity.Error, + code: error.code, + source: 'vue', + message: error.message, + }); } } + } - return [ - ...originalResult ?? [], - ...sfcErrors, - ]; - }); + return [ + ...originalResult ?? [], + ...sfcErrors, + ]; }, provideDocumentSymbols(document) { - return worker(document, context, root => { - const result: DocumentSymbol[] = []; - const { sfc } = root; + const info = getEmbeddedInfo(context, document, 'root_tags'); + if (!info) { + return; + } + const { root } = info; - if (sfc.template) { - result.push({ - name: 'template', - kind: 2 satisfies typeof SymbolKind.Module, - range: { - start: document.positionAt(sfc.template.start), - end: document.positionAt(sfc.template.end), - }, - selectionRange: { - start: document.positionAt(sfc.template.start), - end: document.positionAt(sfc.template.startTagEnd), - }, - }); - } - if (sfc.script) { - result.push({ - name: 'script', - kind: 2 satisfies typeof SymbolKind.Module, - range: { - start: document.positionAt(sfc.script.start), - end: document.positionAt(sfc.script.end), - }, - selectionRange: { - start: document.positionAt(sfc.script.start), - end: document.positionAt(sfc.script.startTagEnd), - }, - }); - } - if (sfc.scriptSetup) { - result.push({ - name: 'script setup', - kind: 2 satisfies typeof SymbolKind.Module, - range: { - start: document.positionAt(sfc.scriptSetup.start), - end: document.positionAt(sfc.scriptSetup.end), - }, - selectionRange: { - start: document.positionAt(sfc.scriptSetup.start), - end: document.positionAt(sfc.scriptSetup.startTagEnd), - }, - }); - } - for (const style of sfc.styles) { - let name = 'style'; - if (style.scoped) { - name += ' scoped'; - } - if (style.module) { - name += ' module'; - } - result.push({ - name, - kind: 2 satisfies typeof SymbolKind.Module, - range: { - start: document.positionAt(style.start), - end: document.positionAt(style.end), - }, - selectionRange: { - start: document.positionAt(style.start), - end: document.positionAt(style.startTagEnd), - }, - }); + const result: DocumentSymbol[] = []; + const { sfc } = root; + + if (sfc.template) { + result.push({ + name: 'template', + kind: 2 satisfies typeof SymbolKind.Module, + range: { + start: document.positionAt(sfc.template.start), + end: document.positionAt(sfc.template.end), + }, + selectionRange: { + start: document.positionAt(sfc.template.start), + end: document.positionAt(sfc.template.startTagEnd), + }, + }); + } + if (sfc.script) { + result.push({ + name: 'script', + kind: 2 satisfies typeof SymbolKind.Module, + range: { + start: document.positionAt(sfc.script.start), + end: document.positionAt(sfc.script.end), + }, + selectionRange: { + start: document.positionAt(sfc.script.start), + end: document.positionAt(sfc.script.startTagEnd), + }, + }); + } + if (sfc.scriptSetup) { + result.push({ + name: 'script setup', + kind: 2 satisfies typeof SymbolKind.Module, + range: { + start: document.positionAt(sfc.scriptSetup.start), + end: document.positionAt(sfc.scriptSetup.end), + }, + selectionRange: { + start: document.positionAt(sfc.scriptSetup.start), + end: document.positionAt(sfc.scriptSetup.startTagEnd), + }, + }); + } + for (const style of sfc.styles) { + let name = 'style'; + if (style.scoped) { + name += ' scoped'; } - for (const customBlock of sfc.customBlocks) { - result.push({ - name: `${customBlock.type}`, - kind: 2 satisfies typeof SymbolKind.Module, - range: { - start: document.positionAt(customBlock.start), - end: document.positionAt(customBlock.end), - }, - selectionRange: { - start: document.positionAt(customBlock.start), - end: document.positionAt(customBlock.startTagEnd), - }, - }); + if (style.module) { + name += ' module'; } + result.push({ + name, + kind: 2 satisfies typeof SymbolKind.Module, + range: { + start: document.positionAt(style.start), + end: document.positionAt(style.end), + }, + selectionRange: { + start: document.positionAt(style.start), + end: document.positionAt(style.startTagEnd), + }, + }); + } + for (const customBlock of sfc.customBlocks) { + result.push({ + name: `${customBlock.type}`, + kind: 2 satisfies typeof SymbolKind.Module, + range: { + start: document.positionAt(customBlock.start), + end: document.positionAt(customBlock.end), + }, + selectionRange: { + start: document.positionAt(customBlock.start), + end: document.positionAt(customBlock.startTagEnd), + }, + }); + } - return result; - }); + return result; }, async provideCompletionItems(document, position, context, token) { @@ -300,19 +310,6 @@ export function create(): LanguageServicePlugin { }; }, }; - - function worker(document: TextDocument, context: LanguageServiceContext, callback: (root: VueVirtualCode) => T) { - if (document.languageId !== 'vue-root-tags') { - return; - } - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const root = sourceScript?.generated?.root; - if (root instanceof VueVirtualCode) { - return callback(root); - } - } } function getStyleCompletionItem( diff --git a/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts b/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts index e3871ce2f7..e79b554bd5 100644 --- a/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts +++ b/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts @@ -1,7 +1,6 @@ import type { CompletionItem, CompletionItemKind, LanguageServicePlugin } from '@volar/language-service'; -import { type TextRange, tsCodegen, VueVirtualCode } from '@vue/language-core'; -import { URI } from 'vscode-uri'; -import { isTsDocument } from './utils'; +import { type TextRange, tsCodegen } from '@vue/language-core'; +import { getEmbeddedInfo } from './utils'; export function create(): LanguageServicePlugin { return { @@ -13,28 +12,17 @@ export function create(): LanguageServicePlugin { return { isAdditionalCompletion: true, async provideCompletionItems(document) { - if (!isTsDocument(document)) { + const info = getEmbeddedInfo(context, document, id => id.startsWith('script_')); + if (!info) { return; } + const { virtualCode, root } = info; const enabled = await context.env.getConfiguration?.('vue.suggest.defineAssignment') ?? true; if (!enabled) { return; } - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || !virtualCode) { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { - return; - } - const { sfc } = root; const codegen = tsCodegen.get(sfc); const scriptSetup = sfc.scriptSetup; diff --git a/packages/language-service/lib/plugins/vue-template-ref-links.ts b/packages/language-service/lib/plugins/vue-template-ref-links.ts index f2354ea43d..cd526386c3 100644 --- a/packages/language-service/lib/plugins/vue-template-ref-links.ts +++ b/packages/language-service/lib/plugins/vue-template-ref-links.ts @@ -1,6 +1,6 @@ import type { LanguageServicePlugin } from '@volar/language-service'; -import { tsCodegen, VueVirtualCode } from '@vue/language-core'; -import { URI } from 'vscode-uri'; +import { tsCodegen } from '@vue/language-core'; +import { getEmbeddedInfo } from './utils'; export function create(): LanguageServicePlugin { return { @@ -11,18 +11,11 @@ export function create(): LanguageServicePlugin { create(context) { return { provideDocumentLinks(document) { - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if (!sourceScript?.generated || virtualCode?.id !== 'scriptsetup_raw') { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, document, 'scriptsetup_raw'); + if (!info) { return; } + const { sourceScript, root } = info; const { sfc } = root; const codegen = tsCodegen.get(sfc); @@ -35,7 +28,7 @@ export function create(): LanguageServicePlugin { if (!templateVirtualCode) { return; } - const templateDocumentUri = context.encodeEmbeddedDocumentUri(decoded![0], 'template'); + const templateDocumentUri = context.encodeEmbeddedDocumentUri(sourceScript.id, 'template'); const templateDocument = context.documents.get( templateDocumentUri, templateVirtualCode.languageId, diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index ee8350847d..36059f14fd 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -7,7 +7,7 @@ import type { LanguageServicePlugin, TextDocument, } from '@volar/language-service'; -import { hyphenateAttr, hyphenateTag, tsCodegen, VueVirtualCode } from '@vue/language-core'; +import { hyphenateAttr, hyphenateTag, tsCodegen, type VueVirtualCode } from '@vue/language-core'; import { camelize, capitalize } from '@vue/shared'; import type { ComponentPropInfo } from '@vue/typescript-plugin/lib/requests/getComponentProps'; import { create as createHtmlService } from 'volar-service-html'; @@ -16,6 +16,7 @@ import * as html from 'vscode-html-languageservice'; import { URI, Utils } from 'vscode-uri'; import { AttrNameCasing, checkCasing, TagNameCasing } from '../nameCasing'; import { loadModelModifiersData, loadTemplateData } from './data'; +import { getEmbeddedInfo } from './utils'; const specialTags = new Set([ 'slot', @@ -75,6 +76,8 @@ export function create( }, onDidChangeCustomData, }); + const htmlDataProvider = html.getDefaultHTMLDataProvider(); + const languageId = mode === 'pug' ? 'jade' : 'html'; return { name: `vue-template (${mode})`, @@ -153,21 +156,11 @@ export function create( }, async provideCompletionItems(document, position, completionContext, token) { - if (!isSupportedDocument(document)) { - return; - } - - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - if (!sourceScript?.generated) { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo(context, document, 'template', languageId); + if (!info) { return; } + const { sourceScript, root } = info; const { result: completionList, @@ -315,12 +308,15 @@ export function create( }, provideHover(document, position, token) { - if (!isSupportedDocument(document)) { + const info = getEmbeddedInfo(context, document, 'template', languageId); + if (!info) { return; } if (context.decodeEmbeddedDocumentUri(URI.parse(document.uri))) { - updateExtraCustomData([]); + updateExtraCustomData([ + htmlDataProvider, + ]); } return baseServiceInstance.provideHover?.(document, position, token); @@ -375,7 +371,6 @@ export function create( isGlobal?: boolean; info?: ComponentPropInfo; }>(); - const htmlDataProvider = html.getDefaultHTMLDataProvider(); updateExtraCustomData([ { @@ -722,15 +717,6 @@ export function create( extraCustomData = extraData; onDidChangeCustomDataListeners.forEach(l => l()); } - - function isSupportedDocument(document: TextDocument) { - if (mode === 'pug') { - return document.languageId === 'jade'; - } - else { - return document.languageId === 'html'; - } - } } function getReplacement(list: html.CompletionList, doc: TextDocument) { diff --git a/packages/language-service/lib/plugins/vue-twoslash-queries.ts b/packages/language-service/lib/plugins/vue-twoslash-queries.ts index 16e7de4983..7303174602 100644 --- a/packages/language-service/lib/plugins/vue-twoslash-queries.ts +++ b/packages/language-service/lib/plugins/vue-twoslash-queries.ts @@ -1,6 +1,5 @@ import type { InlayHint, LanguageServiceContext, LanguageServicePlugin, Position } from '@volar/language-service'; -import { VueVirtualCode } from '@vue/language-core'; -import { URI } from 'vscode-uri'; +import { getEmbeddedInfo } from './utils'; const twoslashTemplateReg = //g; const twoslashScriptReg = /(?<=^|\n)\s*\/\/\s*\^\?/g; @@ -19,21 +18,15 @@ export function create( const tsPluginClient = getTsPluginClient?.(context); return { async provideInlayHints(document, range) { - const uri = URI.parse(document.uri); - const decoded = context.decodeEmbeddedDocumentUri(uri); - const sourceScript = decoded && context.language.scripts.get(decoded[0]); - const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); - if ( - !sourceScript?.generated - || (virtualCode?.id !== 'template' && !virtualCode?.id.startsWith('script_')) - ) { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { + const info = getEmbeddedInfo( + context, + document, + id => id === 'template' || id.startsWith('script_'), + ); + if (!info) { return; } + const { sourceScript, virtualCode, root } = info; const hoverOffsets: [Position, number][] = []; const inlayHints: InlayHint[] = []; @@ -51,7 +44,7 @@ export function create( ]); } - const sourceDocument = context.documents.get(decoded![0], sourceScript.languageId, sourceScript.snapshot); + const sourceDocument = context.documents.get(sourceScript.id, sourceScript.languageId, sourceScript.snapshot); for (const [pointerPosition, hoverOffset] of hoverOffsets) { const map = context.language.maps.get(virtualCode, sourceScript); for (const [sourceOffset] of map.toSourceLocation(hoverOffset)) { diff --git a/packages/typescript-plugin/index.ts b/packages/typescript-plugin/index.ts index 7ddf482836..a098844a1c 100644 --- a/packages/typescript-plugin/index.ts +++ b/packages/typescript-plugin/index.ts @@ -15,7 +15,10 @@ import { getPropertiesAtLocation } from './lib/requests/getPropertiesAtLocation' import type { RequestContext } from './lib/requests/types'; const windowsPathReg = /\\/g; -const project2Service = new WeakMap(); +const project2Service = new WeakMap< + ts.server.Project, + [vue.Language, ts.LanguageServiceHost, ts.LanguageService] +>(); export = createLanguageServicePlugin( (ts, info) => { From 38c61beb72d9b320101e4c21baa65104fd1386d7 Mon Sep 17 00:00:00 2001 From: KazariEX Date: Thu, 24 Jul 2025 23:51:39 +0800 Subject: [PATCH 16/85] refactor(language-service): simplify `tsPluginClient` type and its propagation across functions --- packages/language-service/index.ts | 31 +++++++------------ .../lib/plugins/typescript-semantic-tokens.ts | 8 ++--- .../lib/plugins/vue-autoinsert-dotvalue.ts | 7 ++--- .../plugins/vue-component-semantic-tokens.ts | 8 ++--- .../lib/plugins/vue-document-drop.ts | 13 ++------ .../lib/plugins/vue-document-highlights.ts | 5 ++- .../lib/plugins/vue-extract-file.ts | 16 ++-------- .../lib/plugins/vue-missing-props-hints.ts | 5 +-- .../lib/plugins/vue-template.ts | 6 +--- .../lib/plugins/vue-twoslash-queries.ts | 7 ++--- .../typescript-plugin/lib/requests/index.ts | 1 + 11 files changed, 28 insertions(+), 79 deletions(-) diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index f1188ba217..9c249e2828 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -4,8 +4,6 @@ export * from '@volar/language-service'; // for @vue/language-server usage export * from '@volar/language-service/lib/utils/featureWorkers'; -import type * as ts from 'typescript'; - import { create as createEmmetPlugin } from 'volar-service-emmet'; import { create as createJsonPlugin } from 'volar-service-json'; import { create as createPugFormatPlugin } from 'volar-service-pug-beautify'; @@ -33,37 +31,33 @@ import { create as createVueTwoslashQueriesPlugin } from './lib/plugins/vue-twos export function createVueLanguageServicePlugins( ts: typeof import('typescript'), - tsPluginClient: - | import('@vue/typescript-plugin/lib/requests').Requests & { - getDocumentHighlights: (fileName: string, position: number) => Promise; - } - | undefined, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ) { - const getTsPluginClient = () => tsPluginClient; const plugins = [ createCssPlugin(), createJsonPlugin(), createPugFormatPlugin(), createTypeScriptDocCommentTemplatePlugin(ts), - createTypescriptSemanticTokensPlugin(getTsPluginClient), + createTypescriptSemanticTokensPlugin(tsPluginClient), createTypeScriptSyntacticPlugin(ts), createVueAutoSpacePlugin(), - createVueAutoDotValuePlugin(ts, getTsPluginClient), + createVueAutoDotValuePlugin(ts, tsPluginClient), createVueCompilerDomErrorsPlugin(), - createVueComponentSemanticTokensPlugin(getTsPluginClient), - createVueDocumentDropPlugin(ts, getTsPluginClient), + createVueComponentSemanticTokensPlugin(tsPluginClient), + createVueDocumentDropPlugin(ts, tsPluginClient), + createVueDocumentHighlightsPlugin(tsPluginClient), createVueDirectiveCommentsPlugin(), - createVueExtractFilePlugin(ts, getTsPluginClient), + createVueExtractFilePlugin(ts, tsPluginClient), createVueGlobalTypesErrorPlugin(), createVueInlayHintsPlugin(ts), - createVueMissingPropsHintsPlugin(getTsPluginClient), + createVueMissingPropsHintsPlugin(tsPluginClient), createVueScopedClassLinksPlugin(), createVueSfcPlugin(), createVueSuggestDefineAssignmentPlugin(), - createVueTemplatePlugin('html', getTsPluginClient), - createVueTemplatePlugin('pug', getTsPluginClient), + createVueTemplatePlugin('html', tsPluginClient), + createVueTemplatePlugin('pug', tsPluginClient), createVueTemplateRefLinksPlugin(), - createVueTwoslashQueriesPlugin(getTsPluginClient), + createVueTwoslashQueriesPlugin(tsPluginClient), createEmmetPlugin({ mappedLanguages: { 'vue-root-tags': 'html', @@ -71,8 +65,5 @@ export function createVueLanguageServicePlugins( }, }), ]; - if (tsPluginClient) { - plugins.push(createVueDocumentHighlightsPlugin(tsPluginClient.getDocumentHighlights)); - } return plugins; } diff --git a/packages/language-service/lib/plugins/typescript-semantic-tokens.ts b/packages/language-service/lib/plugins/typescript-semantic-tokens.ts index 11600eb026..58233dbe05 100644 --- a/packages/language-service/lib/plugins/typescript-semantic-tokens.ts +++ b/packages/language-service/lib/plugins/typescript-semantic-tokens.ts @@ -1,11 +1,9 @@ -import type { LanguageServiceContext, LanguageServicePlugin } from '@volar/language-service'; +import type { LanguageServicePlugin } from '@volar/language-service'; import { convertClassificationsToSemanticTokens } from 'volar-service-typescript/lib/semanticFeatures/semanticTokens'; import { getEmbeddedInfo } from './utils'; export function create( - getTsPluginClient?: ( - context: LanguageServiceContext, - ) => import('@vue/typescript-plugin/lib/requests').Requests | undefined, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { return { name: 'typescript-semantic-tokens', @@ -38,8 +36,6 @@ export function create( }, }, create(context) { - const tsPluginClient = getTsPluginClient?.(context); - return { async provideDocumentSemanticTokens(document, range, legend) { const info = getEmbeddedInfo(context, document, 'main'); diff --git a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts index 44484695bd..46f2ddc681 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts @@ -1,13 +1,11 @@ -import type { LanguageServiceContext, LanguageServicePlugin, TextDocument } from '@volar/language-service'; +import type { LanguageServicePlugin, TextDocument } from '@volar/language-service'; import { hyphenateAttr } from '@vue/language-core'; import type * as ts from 'typescript'; import { getEmbeddedInfo, sleep } from './utils'; export function create( ts: typeof import('typescript'), - getTsPluginClient?: ( - context: LanguageServiceContext, - ) => import('@vue/typescript-plugin/lib/requests').Requests | undefined, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { return { name: 'vue-autoinsert-dotvalue', @@ -18,7 +16,6 @@ export function create( }, }, create(context) { - const tsPluginClient = getTsPluginClient?.(context); let currentReq = 0; return { diff --git a/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts b/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts index b62c927110..1b973c22db 100644 --- a/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts +++ b/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts @@ -1,12 +1,10 @@ -import type { LanguageServiceContext, LanguageServicePlugin, SemanticToken } from '@volar/language-service'; +import type { LanguageServicePlugin, SemanticToken } from '@volar/language-service'; import { forEachElementNode, hyphenateTag } from '@vue/language-core'; import type * as ts from 'typescript'; import { getEmbeddedInfo } from './utils'; export function create( - getTsPluginClient?: ( - context: LanguageServiceContext, - ) => import('@vue/typescript-plugin/lib/requests').Requests | undefined, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { return { name: 'vue-component-semantic-tokens', @@ -19,8 +17,6 @@ export function create( }, }, create(context) { - const tsPluginClient = getTsPluginClient?.(context); - return { async provideDocumentSemanticTokens(document, range, legend) { const info = getEmbeddedInfo(context, document, 'template'); diff --git a/packages/language-service/lib/plugins/vue-document-drop.ts b/packages/language-service/lib/plugins/vue-document-drop.ts index f80be41aa3..0382d31073 100644 --- a/packages/language-service/lib/plugins/vue-document-drop.ts +++ b/packages/language-service/lib/plugins/vue-document-drop.ts @@ -1,9 +1,4 @@ -import type { - InsertTextFormat, - LanguageServiceContext, - LanguageServicePlugin, - WorkspaceEdit, -} from '@volar/language-service'; +import type { InsertTextFormat, LanguageServicePlugin, WorkspaceEdit } from '@volar/language-service'; import { forEachEmbeddedCode } from '@vue/language-core'; import { camelize, capitalize, hyphenate } from '@vue/shared'; import { posix as path } from 'path-browserify'; @@ -15,9 +10,7 @@ import { getEmbeddedInfo } from './utils'; export function create( ts: typeof import('typescript'), - getTsPluginClient?: ( - context: LanguageServiceContext, - ) => import('@vue/typescript-plugin/lib/requests').Requests | undefined, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { return { name: 'vue-document-drop', @@ -25,8 +18,6 @@ export function create( documentDropEditsProvider: true, }, create(context) { - const tsPluginClient = getTsPluginClient?.(context); - return { async provideDocumentDropEdits(document, _position, dataTransfer) { const info = getEmbeddedInfo(context, document, 'template', 'html'); diff --git a/packages/language-service/lib/plugins/vue-document-highlights.ts b/packages/language-service/lib/plugins/vue-document-highlights.ts index 972519c4bd..29efe66b2d 100644 --- a/packages/language-service/lib/plugins/vue-document-highlights.ts +++ b/packages/language-service/lib/plugins/vue-document-highlights.ts @@ -1,9 +1,8 @@ import type { DocumentHighlightKind, LanguageServicePlugin } from '@volar/language-service'; -import type * as ts from 'typescript'; import { getEmbeddedInfo } from './utils'; export function create( - getDocumentHighlights: (fileName: string, position: number) => Promise, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { return { name: 'vue-document-highlights', @@ -19,7 +18,7 @@ export function create( } const { root } = info; - const result = await getDocumentHighlights(root.fileName, document.offsetAt(position)); + const result = await tsPluginClient?.getDocumentHighlights(root.fileName, document.offsetAt(position)); return result ?.filter(({ fileName }) => fileName === root.fileName) diff --git a/packages/language-service/lib/plugins/vue-extract-file.ts b/packages/language-service/lib/plugins/vue-extract-file.ts index 229281d074..64656a78f8 100644 --- a/packages/language-service/lib/plugins/vue-extract-file.ts +++ b/packages/language-service/lib/plugins/vue-extract-file.ts @@ -1,10 +1,4 @@ -import type { - CreateFile, - LanguageServiceContext, - LanguageServicePlugin, - TextDocumentEdit, - TextEdit, -} from '@volar/language-service'; +import type { CreateFile, LanguageServicePlugin, TextDocumentEdit, TextEdit } from '@volar/language-service'; import type { ExpressionNode, TemplateChildNode } from '@vue/compiler-dom'; import { type Sfc, tsCodegen } from '@vue/language-core'; import type * as ts from 'typescript'; @@ -21,9 +15,7 @@ const unicodeReg = /\\u/g; export function create( ts: typeof import('typescript'), - getTsPluginClient?: ( - context: LanguageServiceContext, - ) => import('@vue/typescript-plugin/lib/requests').Requests | undefined, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { return { name: 'vue-extract-file', @@ -34,7 +26,6 @@ export function create( }, }, create(context) { - const tsPluginClient = getTsPluginClient?.(context); return { provideCodeActions(document, range, ctx) { if (ctx.only && !ctx.only.includes('refactor')) { @@ -99,9 +90,6 @@ export function create( } const toExtract = await tsPluginClient?.collectExtractProps(root.fileName, templateCodeRange) ?? []; - if (!toExtract) { - return codeAction; - } const templateInitialIndent = await context.env.getConfiguration!('vue.format.template.initialIndent') ?? true; diff --git a/packages/language-service/lib/plugins/vue-missing-props-hints.ts b/packages/language-service/lib/plugins/vue-missing-props-hints.ts index 78bff9182a..e7fd707956 100644 --- a/packages/language-service/lib/plugins/vue-missing-props-hints.ts +++ b/packages/language-service/lib/plugins/vue-missing-props-hints.ts @@ -11,9 +11,7 @@ import { AttrNameCasing, checkCasing } from '../nameCasing'; import { getEmbeddedInfo } from './utils'; export function create( - getTsPluginClient?: ( - context: LanguageServiceContext, - ) => import('@vue/typescript-plugin/lib/requests').Requests | undefined, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { return { name: 'vue-missing-props-hints', @@ -21,7 +19,6 @@ export function create( inlayHintProvider: {}, }, create(context) { - const tsPluginClient = getTsPluginClient?.(context); let intrinsicElementNames: Set; return { diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 36059f14fd..78a34cb2aa 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -3,7 +3,6 @@ import type { CompletionItemTag, CompletionList, Disposable, - LanguageServiceContext, LanguageServicePlugin, TextDocument, } from '@volar/language-service'; @@ -38,9 +37,7 @@ let modelData: html.HTMLDataV1; export function create( mode: 'html' | 'pug', - getTsPluginClient?: ( - context: LanguageServiceContext, - ) => import('@vue/typescript-plugin/lib/requests').Requests | undefined, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { let customData: html.IHTMLDataProvider[] = []; let extraCustomData: html.IHTMLDataProvider[] = []; @@ -92,7 +89,6 @@ export function create( hoverProvider: true, }, create(context) { - const tsPluginClient = getTsPluginClient?.(context); const baseServiceInstance = baseService.create(context); builtInData ??= loadTemplateData(context.env.locale ?? 'en'); diff --git a/packages/language-service/lib/plugins/vue-twoslash-queries.ts b/packages/language-service/lib/plugins/vue-twoslash-queries.ts index 7303174602..bac7e85c75 100644 --- a/packages/language-service/lib/plugins/vue-twoslash-queries.ts +++ b/packages/language-service/lib/plugins/vue-twoslash-queries.ts @@ -1,13 +1,11 @@ -import type { InlayHint, LanguageServiceContext, LanguageServicePlugin, Position } from '@volar/language-service'; +import type { InlayHint, LanguageServicePlugin, Position } from '@volar/language-service'; import { getEmbeddedInfo } from './utils'; const twoslashTemplateReg = //g; const twoslashScriptReg = /(?<=^|\n)\s*\/\/\s*\^\?/g; export function create( - getTsPluginClient?: ( - context: LanguageServiceContext, - ) => import('@vue/typescript-plugin/lib/requests').Requests | undefined, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { return { name: 'vue-twoslash-queries', @@ -15,7 +13,6 @@ export function create( inlayHintProvider: {}, }, create(context) { - const tsPluginClient = getTsPluginClient?.(context); return { async provideInlayHints(document, range) { const info = getEmbeddedInfo( diff --git a/packages/typescript-plugin/lib/requests/index.ts b/packages/typescript-plugin/lib/requests/index.ts index 18ffd095e2..80e6f6a51e 100644 --- a/packages/typescript-plugin/lib/requests/index.ts +++ b/packages/typescript-plugin/lib/requests/index.ts @@ -13,6 +13,7 @@ export type Requests = { getComponentSlots: ToRequest; getElementAttrs: ToRequest; getElementNames: ToRequest; + getDocumentHighlights: ToRequest<(fileName: string, position: number) => ts.DocumentHighlights[]>; getEncodedSemanticClassifications: ToRequest<(fileName: string, span: ts.TextSpan) => ts.Classifications>; getQuickInfoAtPosition: ToRequest<(fileName: string, position: ts.LineAndCharacter) => string>; }; From 456d1b43fd8fb212ba49f33ccd643b9e9972a659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Fri, 25 Jul 2025 01:53:49 +0800 Subject: [PATCH 17/85] v3.0.4 (#5557) --- CHANGELOG.md | 19 ++++++++++++++++ extensions/vscode/package.json | 6 ++--- lerna.json | 2 +- packages/component-meta/package.json | 4 ++-- packages/component-type-helpers/package.json | 2 +- packages/language-core/package.json | 2 +- packages/language-plugin-pug/package.json | 4 ++-- packages/language-server/package.json | 8 +++---- packages/language-service/package.json | 6 ++--- packages/tsc/package.json | 4 ++-- packages/typescript-plugin/package.json | 4 ++-- pnpm-lock.yaml | 24 ++++++++++---------- test-workspace/package.json | 4 ++-- 13 files changed, 54 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9644756046..bddba05d3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 3.0.4 (2025-07-25) + +### Features + +- feat(language-service): check casing when dropping component into template - Thanks to @KazariEX! +- feat(language-service): native completion experience for slot names (#5552) - Thanks to @KazariEX! + +### Bug Fixes + +- fix(language-core): avoid clearing global types path when local compiler options is present - Thanks to @KazariEX! +- fix(language-core): do not evaluate `skipTemplateCodegen` when exposing `$slots` - Thanks to @KazariEX! +- fix(language-service): correct kind and order of component completion items - Thanks to @KazariEX! +- fix(component-meta): filter events out of props (#5547) - Thanks to @Akryum! + +### Other Changes + +- refactor(language-core): allow configuring `checkUnknownEvents` and `checkUnknownComponents` in sfc (#5537) - Thanks to @KazariEX! +- chore(language-service): add restart server hint to global types warning - Thanks to @KazariEX! + ## 3.0.3 (2025-07-18) ### Bug Fixes diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 7de1a28ba6..02609994fe 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "volar", - "version": "3.0.3", + "version": "3.0.4", "repository": { "type": "git", "url": "https://github.com/vuejs/language-tools.git", @@ -466,8 +466,8 @@ "@volar/vscode": "2.4.20", "@vscode/vsce": "^3.2.1", "@vue/compiler-sfc": "^3.5.0", - "@vue/language-server": "3.0.3", - "@vue/typescript-plugin": "3.0.3", + "@vue/language-server": "3.0.4", + "@vue/typescript-plugin": "3.0.4", "reactive-vscode": "^0.2.9", "rolldown": "1.0.0-beta.8", "semver": "^7.5.4", diff --git a/lerna.json b/lerna.json index 8954f700b2..95378e03fc 100644 --- a/lerna.json +++ b/lerna.json @@ -6,5 +6,5 @@ "packages/**", "test-workspace" ], - "version": "3.0.3" + "version": "3.0.4" } diff --git a/packages/component-meta/package.json b/packages/component-meta/package.json index b3285a3a87..504a2f739c 100644 --- a/packages/component-meta/package.json +++ b/packages/component-meta/package.json @@ -1,6 +1,6 @@ { "name": "vue-component-meta", - "version": "3.0.3", + "version": "3.0.4", "license": "MIT", "files": [ "**/*.js", @@ -14,7 +14,7 @@ }, "dependencies": { "@volar/typescript": "2.4.20", - "@vue/language-core": "3.0.3", + "@vue/language-core": "3.0.4", "path-browserify": "^1.0.1" }, "peerDependencies": { diff --git a/packages/component-type-helpers/package.json b/packages/component-type-helpers/package.json index d20dbd84f8..a12841c5ce 100644 --- a/packages/component-type-helpers/package.json +++ b/packages/component-type-helpers/package.json @@ -1,6 +1,6 @@ { "name": "vue-component-type-helpers", - "version": "3.0.3", + "version": "3.0.4", "license": "MIT", "files": [ "**/*.js", diff --git a/packages/language-core/package.json b/packages/language-core/package.json index ddffb27f98..24a78ae47d 100644 --- a/packages/language-core/package.json +++ b/packages/language-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/language-core", - "version": "3.0.3", + "version": "3.0.4", "license": "MIT", "files": [ "**/*.js", diff --git a/packages/language-plugin-pug/package.json b/packages/language-plugin-pug/package.json index 4e3faf336b..290f9f1b81 100644 --- a/packages/language-plugin-pug/package.json +++ b/packages/language-plugin-pug/package.json @@ -1,6 +1,6 @@ { "name": "@vue/language-plugin-pug", - "version": "3.0.3", + "version": "3.0.4", "license": "MIT", "files": [ "**/*.js", @@ -19,6 +19,6 @@ "devDependencies": { "@types/node": "^22.10.4", "@vue/compiler-dom": "^3.5.0", - "@vue/language-core": "3.0.3" + "@vue/language-core": "3.0.4" } } diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 7ccd69686e..2c45282971 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -1,6 +1,6 @@ { "name": "@vue/language-server", - "version": "3.0.3", + "version": "3.0.4", "license": "MIT", "files": [ "**/*.js", @@ -17,9 +17,9 @@ }, "dependencies": { "@volar/language-server": "2.4.20", - "@vue/language-core": "3.0.3", - "@vue/language-service": "3.0.3", - "@vue/typescript-plugin": "3.0.3", + "@vue/language-core": "3.0.4", + "@vue/language-service": "3.0.4", + "@vue/typescript-plugin": "3.0.4", "vscode-uri": "^3.0.8" }, "peerDependencies": { diff --git a/packages/language-service/package.json b/packages/language-service/package.json index 8e16b30a46..990ea50d2a 100644 --- a/packages/language-service/package.json +++ b/packages/language-service/package.json @@ -1,6 +1,6 @@ { "name": "@vue/language-service", - "version": "3.0.3", + "version": "3.0.4", "license": "MIT", "files": [ "data", @@ -18,7 +18,7 @@ }, "dependencies": { "@volar/language-service": "2.4.20", - "@vue/language-core": "3.0.3", + "@vue/language-core": "3.0.4", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1", "volar-service-css": "0.0.65", @@ -37,7 +37,7 @@ "@volar/kit": "2.4.20", "@volar/typescript": "2.4.20", "@vue/compiler-dom": "^3.5.0", - "@vue/typescript-plugin": "3.0.3", + "@vue/typescript-plugin": "3.0.4", "vscode-css-languageservice": "^6.3.1" } } diff --git a/packages/tsc/package.json b/packages/tsc/package.json index 7bc2969071..4bb8ee5818 100644 --- a/packages/tsc/package.json +++ b/packages/tsc/package.json @@ -1,6 +1,6 @@ { "name": "vue-tsc", - "version": "3.0.3", + "version": "3.0.4", "license": "MIT", "files": [ "bin", @@ -21,7 +21,7 @@ }, "dependencies": { "@volar/typescript": "2.4.20", - "@vue/language-core": "3.0.3" + "@vue/language-core": "3.0.4" }, "devDependencies": { "@types/node": "^22.10.4" diff --git a/packages/typescript-plugin/package.json b/packages/typescript-plugin/package.json index dd6fbe089e..3c402e5190 100644 --- a/packages/typescript-plugin/package.json +++ b/packages/typescript-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vue/typescript-plugin", - "version": "3.0.3", + "version": "3.0.4", "license": "MIT", "files": [ "**/*.js", @@ -14,7 +14,7 @@ }, "dependencies": { "@volar/typescript": "2.4.20", - "@vue/language-core": "3.0.3", + "@vue/language-core": "3.0.4", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b68a10e378..ef90bc2b68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,10 +60,10 @@ importers: specifier: ^3.5.0 version: 3.5.13 '@vue/language-server': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../../packages/language-server '@vue/typescript-plugin': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../../packages/typescript-plugin reactive-vscode: specifier: ^0.2.9 @@ -87,7 +87,7 @@ importers: specifier: 2.4.20 version: 2.4.20 '@vue/language-core': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../language-core path-browserify: specifier: ^1.0.1 @@ -170,7 +170,7 @@ importers: specifier: ^3.5.0 version: 3.5.13 '@vue/language-core': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../language-core packages/language-server: @@ -179,13 +179,13 @@ importers: specifier: 2.4.20 version: 2.4.20 '@vue/language-core': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../language-core '@vue/language-service': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../language-service '@vue/typescript-plugin': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../typescript-plugin typescript: specifier: '*' @@ -207,7 +207,7 @@ importers: specifier: 2.4.20 version: 2.4.20 '@vue/language-core': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../language-core '@vue/shared': specifier: ^3.5.0 @@ -259,7 +259,7 @@ importers: specifier: ^3.5.0 version: 3.5.13 '@vue/typescript-plugin': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../typescript-plugin vscode-css-languageservice: specifier: ^6.3.1 @@ -271,7 +271,7 @@ importers: specifier: 2.4.20 version: 2.4.20 '@vue/language-core': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../language-core typescript: specifier: '>=5.0.0' @@ -287,7 +287,7 @@ importers: specifier: 2.4.20 version: 2.4.20 '@vue/language-core': - specifier: 3.0.3 + specifier: 3.0.4 version: link:../language-core '@vue/shared': specifier: ^3.5.0 @@ -312,7 +312,7 @@ importers: specifier: https://pkg.pr.new/vue@e1bc0eb02e22bc0c236e1471c11d96a368764b72 version: https://pkg.pr.new/vue@e1bc0eb02e22bc0c236e1471c11d96a368764b72(typescript@5.8.3) vue-component-type-helpers: - specifier: 3.0.3 + specifier: 3.0.4 version: link:../packages/component-type-helpers vue3.4: specifier: npm:vue@3.4.38 diff --git a/test-workspace/package.json b/test-workspace/package.json index 1791c1b3a9..2dc823ebb7 100644 --- a/test-workspace/package.json +++ b/test-workspace/package.json @@ -1,10 +1,10 @@ { "private": true, - "version": "3.0.3", + "version": "3.0.4", "devDependencies": { "typescript": "latest", "vue": "https://pkg.pr.new/vue@e1bc0eb02e22bc0c236e1471c11d96a368764b72", - "vue-component-type-helpers": "3.0.3", + "vue-component-type-helpers": "3.0.4", "vue3.4": "npm:vue@3.4.38" } } From 0e0ffd974b6642fe74e6ac634999941b9757031c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Fri, 25 Jul 2025 02:23:18 +0800 Subject: [PATCH 18/85] fix(language-core): generate `modelModifiers` for explicitly declared default model name (#5558) --- packages/language-core/lib/codegen/script/scriptSetup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-core/lib/codegen/script/scriptSetup.ts b/packages/language-core/lib/codegen/script/scriptSetup.ts index 83af4ed7ff..aa889d3cc3 100644 --- a/packages/language-core/lib/codegen/script/scriptSetup.ts +++ b/packages/language-core/lib/codegen/script/scriptSetup.ts @@ -509,7 +509,7 @@ function* generateComponentProps( yield `,${newLine}`; if (defineModel.modifierType) { - const modifierName = `${defineModel.name ? propName : 'model'}Modifiers`; + const modifierName = `${propName === 'modelValue' ? 'model' : propName}Modifiers`; const modifierType = getRangeText(scriptSetup, defineModel.modifierType); yield `'${modifierName}'?: Partial>,${newLine}`; } From 848d21a7a54d53026bb56ff0adcf6baeba4fb2a5 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 25 Jul 2025 05:40:45 +0800 Subject: [PATCH 19/85] refactor(language-service): rename 'pug' to 'jade' in template plugin --- packages/language-service/index.ts | 2 +- packages/language-service/lib/plugins/vue-template.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index 9c249e2828..e9307189cd 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -55,7 +55,7 @@ export function createVueLanguageServicePlugins( createVueSfcPlugin(), createVueSuggestDefineAssignmentPlugin(), createVueTemplatePlugin('html', tsPluginClient), - createVueTemplatePlugin('pug', tsPluginClient), + createVueTemplatePlugin('jade', tsPluginClient), createVueTemplateRefLinksPlugin(), createVueTwoslashQueriesPlugin(tsPluginClient), createEmmetPlugin({ diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 78a34cb2aa..14bf47a242 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -36,7 +36,7 @@ let builtInData: html.HTMLDataV1; let modelData: html.HTMLDataV1; export function create( - mode: 'html' | 'pug', + languageId: 'html' | 'jade', tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, ): LanguageServicePlugin { let customData: html.IHTMLDataProvider[] = []; @@ -51,7 +51,7 @@ export function create( }, }; }; - const baseService = mode === 'pug' + const baseService = languageId === 'jade' ? createPugService({ useDefaultDataProvider: false, getCustomData() { @@ -74,10 +74,9 @@ export function create( onDidChangeCustomData, }); const htmlDataProvider = html.getDefaultHTMLDataProvider(); - const languageId = mode === 'pug' ? 'jade' : 'html'; return { - name: `vue-template (${mode})`, + name: `vue-template (${languageId})`, capabilities: { ...baseService.capabilities, completionProvider: { From 1d005d5aeaf3701b543104b7766db0c150326507 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 25 Jul 2025 05:57:04 +0800 Subject: [PATCH 20/85] refactor(language-service): update tsPluginClient usage to destructured requests in plugins --- packages/language-service/index.ts | 49 +++++++++++++------ .../lib/plugins/typescript-semantic-tokens.ts | 4 +- .../lib/plugins/vue-autoinsert-dotvalue.ts | 4 +- .../plugins/vue-component-semantic-tokens.ts | 6 +-- .../lib/plugins/vue-document-drop.ts | 6 +-- .../lib/plugins/vue-document-highlights.ts | 4 +- .../lib/plugins/vue-extract-file.ts | 4 +- .../lib/plugins/vue-missing-props-hints.ts | 8 +-- .../lib/plugins/vue-template.ts | 21 +++++--- .../lib/plugins/vue-twoslash-queries.ts | 4 +- .../language-service/tests/utils/format.ts | 2 +- .../typescript-plugin/lib/requests/index.ts | 35 +++++++------ 12 files changed, 87 insertions(+), 60 deletions(-) diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index e9307189cd..76f04a05df 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -31,39 +31,56 @@ import { create as createVueTwoslashQueriesPlugin } from './lib/plugins/vue-twos export function createVueLanguageServicePlugins( ts: typeof import('typescript'), - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests = { + collectExtractProps: () => undefined, + getImportPathForFile: () => undefined, + getPropertiesAtLocation: () => undefined, + getComponentDirectives: () => undefined, + getComponentEvents: () => undefined, + getComponentNames: () => undefined, + getComponentProps: () => undefined, + getComponentSlots: () => undefined, + getElementAttrs: () => undefined, + getElementNames: () => undefined, + getDocumentHighlights: () => undefined, + getEncodedSemanticClassifications: () => undefined, + getQuickInfoAtPosition: () => undefined, + }, ) { - const plugins = [ + return [ createCssPlugin(), createJsonPlugin(), createPugFormatPlugin(), - createTypeScriptDocCommentTemplatePlugin(ts), - createTypescriptSemanticTokensPlugin(tsPluginClient), - createTypeScriptSyntacticPlugin(ts), createVueAutoSpacePlugin(), - createVueAutoDotValuePlugin(ts, tsPluginClient), createVueCompilerDomErrorsPlugin(), - createVueComponentSemanticTokensPlugin(tsPluginClient), - createVueDocumentDropPlugin(ts, tsPluginClient), - createVueDocumentHighlightsPlugin(tsPluginClient), createVueDirectiveCommentsPlugin(), - createVueExtractFilePlugin(ts, tsPluginClient), createVueGlobalTypesErrorPlugin(), - createVueInlayHintsPlugin(ts), - createVueMissingPropsHintsPlugin(tsPluginClient), createVueScopedClassLinksPlugin(), createVueSfcPlugin(), createVueSuggestDefineAssignmentPlugin(), - createVueTemplatePlugin('html', tsPluginClient), - createVueTemplatePlugin('jade', tsPluginClient), createVueTemplateRefLinksPlugin(), - createVueTwoslashQueriesPlugin(tsPluginClient), createEmmetPlugin({ mappedLanguages: { 'vue-root-tags': 'html', 'postcss': 'scss', }, }), + + // TS related plugins + createTypeScriptDocCommentTemplatePlugin(ts), + createTypeScriptSyntacticPlugin(ts), + createVueInlayHintsPlugin(ts), + + // type aware plugins + createTypescriptSemanticTokensPlugin(tsPluginClient), + createVueAutoDotValuePlugin(ts, tsPluginClient), + createVueComponentSemanticTokensPlugin(tsPluginClient), + createVueDocumentDropPlugin(ts, tsPluginClient), + createVueDocumentHighlightsPlugin(tsPluginClient), + createVueExtractFilePlugin(ts, tsPluginClient), + createVueMissingPropsHintsPlugin(tsPluginClient), + createVueTemplatePlugin('html', tsPluginClient), + createVueTemplatePlugin('jade', tsPluginClient), + createVueTwoslashQueriesPlugin(tsPluginClient), ]; - return plugins; } diff --git a/packages/language-service/lib/plugins/typescript-semantic-tokens.ts b/packages/language-service/lib/plugins/typescript-semantic-tokens.ts index 58233dbe05..64374e2f53 100644 --- a/packages/language-service/lib/plugins/typescript-semantic-tokens.ts +++ b/packages/language-service/lib/plugins/typescript-semantic-tokens.ts @@ -3,7 +3,7 @@ import { convertClassificationsToSemanticTokens } from 'volar-service-typescript import { getEmbeddedInfo } from './utils'; export function create( - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, + { getEncodedSemanticClassifications }: import('@vue/typescript-plugin/lib/requests').Requests, ): LanguageServicePlugin { return { name: 'typescript-semantic-tokens', @@ -50,7 +50,7 @@ export function create( start: start, length: end - start, }; - const classifications = await tsPluginClient?.getEncodedSemanticClassifications( + const classifications = await getEncodedSemanticClassifications( root.fileName, span, ); diff --git a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts index 46f2ddc681..199e7ae40e 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts @@ -5,7 +5,7 @@ import { getEmbeddedInfo, sleep } from './utils'; export function create( ts: typeof import('typescript'), - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, + { getPropertiesAtLocation }: import('@vue/typescript-plugin/lib/requests').Requests, ): LanguageServicePlugin { return { name: 'vue-autoinsert-dotvalue', @@ -73,7 +73,7 @@ export function create( } } - const props = await tsPluginClient?.getPropertiesAtLocation(root.fileName, sourceOffset) ?? []; + const props = await getPropertiesAtLocation(root.fileName, sourceOffset) ?? []; if (props.some(prop => prop === 'value')) { return '${1:.value}'; } diff --git a/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts b/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts index 1b973c22db..655a05c7c7 100644 --- a/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts +++ b/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts @@ -4,7 +4,7 @@ import type * as ts from 'typescript'; import { getEmbeddedInfo } from './utils'; export function create( - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, + { getComponentNames, getElementNames }: import('@vue/typescript-plugin/lib/requests').Requests, ): LanguageServicePlugin { return { name: 'vue-component-semantic-tokens', @@ -34,8 +34,8 @@ export function create( const start = document.offsetAt(range.start); const end = document.offsetAt(range.end); - const validComponentNames = await tsPluginClient?.getComponentNames(root.fileName) ?? []; - const elements = new Set(await tsPluginClient?.getElementNames(root.fileName) ?? []); + const validComponentNames = await getComponentNames(root.fileName) ?? []; + const elements = new Set(await getElementNames(root.fileName) ?? []); const components = new Set([ ...validComponentNames, ...validComponentNames.map(hyphenateTag), diff --git a/packages/language-service/lib/plugins/vue-document-drop.ts b/packages/language-service/lib/plugins/vue-document-drop.ts index 0382d31073..b38e3df399 100644 --- a/packages/language-service/lib/plugins/vue-document-drop.ts +++ b/packages/language-service/lib/plugins/vue-document-drop.ts @@ -10,7 +10,7 @@ import { getEmbeddedInfo } from './utils'; export function create( ts: typeof import('typescript'), - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, + { getImportPathForFile }: import('@vue/typescript-plugin/lib/requests').Requests, ): LanguageServicePlugin { return { name: 'vue-document-drop', @@ -56,7 +56,7 @@ export function create( let importPath: string | undefined; const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(root); - if (tsPluginClient && serviceScript) { + if (serviceScript) { const tsDocumentUri = context.encodeEmbeddedDocumentUri(sourceScript.id, serviceScript.code.id); const tsDocument = context.documents.get( tsDocumentUri, @@ -64,7 +64,7 @@ export function create( serviceScript.code.snapshot, ); const preferences = await getUserPreferences(context, tsDocument); - const importPathRequest = await tsPluginClient.getImportPathForFile( + const importPathRequest = await getImportPathForFile( root.fileName, incomingFileName, preferences, diff --git a/packages/language-service/lib/plugins/vue-document-highlights.ts b/packages/language-service/lib/plugins/vue-document-highlights.ts index 29efe66b2d..e2fe6cdc8f 100644 --- a/packages/language-service/lib/plugins/vue-document-highlights.ts +++ b/packages/language-service/lib/plugins/vue-document-highlights.ts @@ -2,7 +2,7 @@ import type { DocumentHighlightKind, LanguageServicePlugin } from '@volar/langua import { getEmbeddedInfo } from './utils'; export function create( - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, + { getDocumentHighlights }: import('@vue/typescript-plugin/lib/requests').Requests, ): LanguageServicePlugin { return { name: 'vue-document-highlights', @@ -18,7 +18,7 @@ export function create( } const { root } = info; - const result = await tsPluginClient?.getDocumentHighlights(root.fileName, document.offsetAt(position)); + const result = await getDocumentHighlights(root.fileName, document.offsetAt(position)); return result ?.filter(({ fileName }) => fileName === root.fileName) diff --git a/packages/language-service/lib/plugins/vue-extract-file.ts b/packages/language-service/lib/plugins/vue-extract-file.ts index 64656a78f8..0e49d7deba 100644 --- a/packages/language-service/lib/plugins/vue-extract-file.ts +++ b/packages/language-service/lib/plugins/vue-extract-file.ts @@ -15,7 +15,7 @@ const unicodeReg = /\\u/g; export function create( ts: typeof import('typescript'), - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, + { collectExtractProps }: import('@vue/typescript-plugin/lib/requests').Requests, ): LanguageServicePlugin { return { name: 'vue-extract-file', @@ -89,7 +89,7 @@ export function create( return codeAction; } - const toExtract = await tsPluginClient?.collectExtractProps(root.fileName, templateCodeRange) ?? []; + const toExtract = await collectExtractProps(root.fileName, templateCodeRange) ?? []; const templateInitialIndent = await context.env.getConfiguration!('vue.format.template.initialIndent') ?? true; diff --git a/packages/language-service/lib/plugins/vue-missing-props-hints.ts b/packages/language-service/lib/plugins/vue-missing-props-hints.ts index e7fd707956..0c41c383ef 100644 --- a/packages/language-service/lib/plugins/vue-missing-props-hints.ts +++ b/packages/language-service/lib/plugins/vue-missing-props-hints.ts @@ -11,7 +11,7 @@ import { AttrNameCasing, checkCasing } from '../nameCasing'; import { getEmbeddedInfo } from './utils'; export function create( - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, + { getComponentNames, getElementNames, getComponentProps }: import('@vue/typescript-plugin/lib/requests').Requests, ): LanguageServicePlugin { return { name: 'vue-missing-props-hints', @@ -41,11 +41,11 @@ export function create( const result: InlayHint[] = []; const casing = await checkCasing(context, sourceScript.id); - const components = await tsPluginClient?.getComponentNames(root.fileName) ?? []; + const components = await getComponentNames(root.fileName) ?? []; const componentProps: Record = {}; intrinsicElementNames ??= new Set( - await tsPluginClient?.getElementNames(root.fileName) ?? [], + await getElementNames(root.fileName) ?? [], ); let token: html.TokenType; @@ -76,7 +76,7 @@ export function create( if (cancellationToken.isCancellationRequested) { break; } - componentProps[checkTag] = (await tsPluginClient?.getComponentProps(root.fileName, checkTag) ?? []) + componentProps[checkTag] = (await getComponentProps(root.fileName, checkTag) ?? []) .filter(prop => prop.required) .map(prop => prop.name); } diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 14bf47a242..a5636ac696 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -37,7 +37,14 @@ let modelData: html.HTMLDataV1; export function create( languageId: 'html' | 'jade', - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, + { + getComponentNames, + getElementAttrs, + getComponentProps, + getComponentEvents, + getComponentDirectives, + getComponentSlots, + }: import('@vue/typescript-plugin/lib/requests').Requests, ): LanguageServicePlugin { let customData: html.IHTMLDataProvider[] = []; let extraCustomData: html.IHTMLDataProvider[] = []; @@ -400,7 +407,7 @@ export function create( if (!components) { components = []; tasks.push((async () => { - components = (await tsPluginClient?.getComponentNames(root.fileName) ?? []) + components = (await getComponentNames(root.fileName) ?? []) .filter(name => name !== 'Transition' && name !== 'TransitionGroup' @@ -455,10 +462,10 @@ export function create( tagMap.set(tag, tagInfo); tasks.push((async () => { tagMap.set(tag, { - attrs: await tsPluginClient?.getElementAttrs(root.fileName, tag) ?? [], - propInfos: await tsPluginClient?.getComponentProps(root.fileName, tag) ?? [], - events: await tsPluginClient?.getComponentEvents(root.fileName, tag) ?? [], - directives: await tsPluginClient?.getComponentDirectives(root.fileName) ?? [], + attrs: await getElementAttrs(root.fileName, tag) ?? [], + propInfos: await getComponentProps(root.fileName, tag) ?? [], + events: await getComponentEvents(root.fileName, tag) ?? [], + directives: await getComponentDirectives(root.fileName) ?? [], }); version++; })()); @@ -607,7 +614,7 @@ export function create( values = []; tasks.push((async () => { if (tag === 'slot' && attr === 'name') { - values = await tsPluginClient?.getComponentSlots(root.fileName) ?? []; + values = await getComponentSlots(root.fileName) ?? []; } version++; })()); diff --git a/packages/language-service/lib/plugins/vue-twoslash-queries.ts b/packages/language-service/lib/plugins/vue-twoslash-queries.ts index bac7e85c75..c93e80bc34 100644 --- a/packages/language-service/lib/plugins/vue-twoslash-queries.ts +++ b/packages/language-service/lib/plugins/vue-twoslash-queries.ts @@ -5,7 +5,7 @@ const twoslashTemplateReg = //g; const twoslashScriptReg = /(?<=^|\n)\s*\/\/\s*\^\?/g; export function create( - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined, + { getQuickInfoAtPosition }: import('@vue/typescript-plugin/lib/requests').Requests, ): LanguageServicePlugin { return { name: 'vue-twoslash-queries', @@ -45,7 +45,7 @@ export function create( for (const [pointerPosition, hoverOffset] of hoverOffsets) { const map = context.language.maps.get(virtualCode, sourceScript); for (const [sourceOffset] of map.toSourceLocation(hoverOffset)) { - const quickInfo = await tsPluginClient?.getQuickInfoAtPosition( + const quickInfo = await getQuickInfoAtPosition( root.fileName, sourceDocument.positionAt(sourceOffset), ); diff --git a/packages/language-service/tests/utils/format.ts b/packages/language-service/tests/utils/format.ts index 00e13d3a37..a590bdb389 100644 --- a/packages/language-service/tests/utils/format.ts +++ b/packages/language-service/tests/utils/format.ts @@ -12,7 +12,7 @@ const vueLanguagePlugin = createVueLanguagePlugin( resolvedVueOptions, () => '', ); -const vueServicePLugins = createVueLanguageServicePlugins(ts, undefined); +const vueServicePLugins = createVueLanguageServicePlugins(ts); const formatter = kit.createFormatter([vueLanguagePlugin], vueServicePLugins); export function defineFormatTest(options: { diff --git a/packages/typescript-plugin/lib/requests/index.ts b/packages/typescript-plugin/lib/requests/index.ts index 80e6f6a51e..5f71d49db8 100644 --- a/packages/typescript-plugin/lib/requests/index.ts +++ b/packages/typescript-plugin/lib/requests/index.ts @@ -1,19 +1,22 @@ import type * as ts from 'typescript'; -type ToRequest any> = (...args: Parameters) => Promise | null | undefined>; +type Request any> = ( + ...args: Parameters +) => MaybePromise | null | undefined>; +type MaybePromise = T | Promise; -export type Requests = { - collectExtractProps: ToRequest; - getImportPathForFile: ToRequest; - getPropertiesAtLocation: ToRequest; - getComponentDirectives: ToRequest; - getComponentEvents: ToRequest; - getComponentNames: ToRequest; - getComponentProps: ToRequest; - getComponentSlots: ToRequest; - getElementAttrs: ToRequest; - getElementNames: ToRequest; - getDocumentHighlights: ToRequest<(fileName: string, position: number) => ts.DocumentHighlights[]>; - getEncodedSemanticClassifications: ToRequest<(fileName: string, span: ts.TextSpan) => ts.Classifications>; - getQuickInfoAtPosition: ToRequest<(fileName: string, position: ts.LineAndCharacter) => string>; -}; +export interface Requests { + collectExtractProps: Request; + getImportPathForFile: Request; + getPropertiesAtLocation: Request; + getComponentDirectives: Request; + getComponentEvents: Request; + getComponentNames: Request; + getComponentProps: Request; + getComponentSlots: Request; + getElementAttrs: Request; + getElementNames: Request; + getDocumentHighlights: Request<(fileName: string, position: number) => ts.DocumentHighlights[]>; + getEncodedSemanticClassifications: Request<(fileName: string, span: ts.TextSpan) => ts.Classifications>; + getQuickInfoAtPosition: Request<(fileName: string, position: ts.LineAndCharacter) => string>; +} From 3e6393b7ecce6f3307b1872e6a6aad5ec555017d Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 25 Jul 2025 06:10:00 +0800 Subject: [PATCH 21/85] fix(language-service): more responsive .value insertion --- .../lib/{plugins => }/data.ts | 62 +++++++++---------- packages/language-service/lib/plugins/css.ts | 2 +- .../lib/plugins/typescript-semantic-tokens.ts | 2 +- .../lib/plugins/vue-autoinsert-dotvalue.ts | 28 +++------ .../lib/plugins/vue-autoinsert-space.ts | 9 +-- .../lib/plugins/vue-compiler-dom-errors.ts | 2 +- .../plugins/vue-component-semantic-tokens.ts | 2 +- .../lib/plugins/vue-document-drop.ts | 2 +- .../lib/plugins/vue-document-highlights.ts | 2 +- .../lib/plugins/vue-extract-file.ts | 2 +- .../lib/plugins/vue-global-types-error.ts | 2 +- .../lib/plugins/vue-inlayhints.ts | 2 +- .../lib/plugins/vue-missing-props-hints.ts | 2 +- .../lib/plugins/vue-scoped-class-links.ts | 2 +- .../language-service/lib/plugins/vue-sfc.ts | 4 +- .../plugins/vue-suggest-define-assignment.ts | 2 +- .../lib/plugins/vue-template-ref-links.ts | 2 +- .../lib/plugins/vue-template.ts | 4 +- .../lib/plugins/vue-twoslash-queries.ts | 2 +- .../lib/{plugins => }/utils.ts | 4 -- 20 files changed, 59 insertions(+), 80 deletions(-) rename packages/language-service/lib/{plugins => }/data.ts (60%) rename packages/language-service/lib/{plugins => }/utils.ts (92%) diff --git a/packages/language-service/lib/plugins/data.ts b/packages/language-service/lib/data.ts similarity index 60% rename from packages/language-service/lib/plugins/data.ts rename to packages/language-service/lib/data.ts index e227d57992..3fb47db74f 100644 --- a/packages/language-service/lib/plugins/data.ts +++ b/packages/language-service/lib/data.ts @@ -8,34 +8,34 @@ export function loadTemplateData(lang: string) { let data: html.HTMLDataV1; if (lang === 'ja') { - data = require('../../data/template/ja.json'); + data = require('../data/template/ja.json'); } else if (lang === 'fr') { - data = require('../../data/template/fr.json'); + data = require('../data/template/fr.json'); } else if (lang === 'ko') { - data = require('../../data/template/ko.json'); + data = require('../data/template/ko.json'); } else if (lang === 'pt-br') { - data = require('../../data/template/pt.json'); + data = require('../data/template/pt.json'); } else if (lang === 'zh-cn') { - data = require('../../data/template/zh-cn.json'); + data = require('../data/template/zh-cn.json'); } else if (lang === 'zh-tw') { - data = require('../../data/template/zh-hk.json'); + data = require('../data/template/zh-hk.json'); } else if (lang === 'it') { - data = require('../../data/template/it.json'); + data = require('../data/template/it.json'); } else if (lang === 'cs') { - data = require('../../data/template/cs.json'); + data = require('../data/template/cs.json'); } else if (lang === 'ru') { - data = require('../../data/template/ru.json'); + data = require('../data/template/ru.json'); } else { - data = require('../../data/template/en.json'); + data = require('../data/template/en.json'); } resolveReferences(data); @@ -73,34 +73,34 @@ export function loadLanguageBlocks(lang: string): html.HTMLDataV1 { let data: html.HTMLDataV1; if (lang === 'ja') { - data = require('../../data/language-blocks/ja.json'); + data = require('../data/language-blocks/ja.json'); } else if (lang === 'fr') { - data = require('../../data/language-blocks/fr.json'); + data = require('../data/language-blocks/fr.json'); } else if (lang === 'ko') { - data = require('../../data/language-blocks/ko.json'); + data = require('../data/language-blocks/ko.json'); } else if (lang === 'pt-br') { - data = require('../../data/language-blocks/pt.json'); + data = require('../data/language-blocks/pt.json'); } else if (lang === 'zh-cn') { - data = require('../../data/language-blocks/zh-cn.json'); + data = require('../data/language-blocks/zh-cn.json'); } else if (lang === 'zh-tw') { - data = require('../../data/language-blocks/zh-hk.json'); + data = require('../data/language-blocks/zh-hk.json'); } else if (lang === 'it') { - data = require('../../data/language-blocks/it.json'); + data = require('../data/language-blocks/it.json'); } else if (lang === 'cs') { - data = require('../../data/language-blocks/cs.json'); + data = require('../data/language-blocks/cs.json'); } else if (lang === 'ru') { - data = require('../../data/language-blocks/ru.json'); + data = require('../data/language-blocks/ru.json'); } else { - data = require('../../data/language-blocks/en.json'); + data = require('../data/language-blocks/en.json'); } resolveReferences(data); @@ -114,34 +114,34 @@ export function loadModelModifiersData(lang: string): html.HTMLDataV1 { let data: html.HTMLDataV1; if (lang === 'ja') { - data = require('../../data/model-modifiers/ja.json'); + data = require('../data/model-modifiers/ja.json'); } else if (lang === 'fr') { - data = require('../../data/model-modifiers/fr.json'); + data = require('../data/model-modifiers/fr.json'); } else if (lang === 'ko') { - data = require('../../data/model-modifiers/ko.json'); + data = require('../data/model-modifiers/ko.json'); } else if (lang === 'pt-br') { - data = require('../../data/model-modifiers/pt.json'); + data = require('../data/model-modifiers/pt.json'); } else if (lang === 'zh-cn') { - data = require('../../data/model-modifiers/zh-cn.json'); + data = require('../data/model-modifiers/zh-cn.json'); } else if (lang === 'zh-tw') { - data = require('../../data/model-modifiers/zh-hk.json'); + data = require('../data/model-modifiers/zh-hk.json'); } else if (lang === 'it') { - data = require('../../data/model-modifiers/it.json'); + data = require('../data/model-modifiers/it.json'); } else if (lang === 'cs') { - data = require('../../data/model-modifiers/cs.json'); + data = require('../data/model-modifiers/cs.json'); } else if (lang === 'ru') { - data = require('../../data/model-modifiers/ru.json'); + data = require('../data/model-modifiers/ru.json'); } else { - data = require('../../data/model-modifiers/en.json'); + data = require('../data/model-modifiers/en.json'); } resolveReferences(data); @@ -150,7 +150,7 @@ export function loadModelModifiersData(lang: string): html.HTMLDataV1 { } function resolveReferences(data: html.HTMLDataV1) { - locale ??= require('../../data/locale.json'); + locale ??= require('../data/locale.json'); for ( const item of [ diff --git a/packages/language-service/lib/plugins/css.ts b/packages/language-service/lib/plugins/css.ts index 68b2102959..fb168961a8 100644 --- a/packages/language-service/lib/plugins/css.ts +++ b/packages/language-service/lib/plugins/css.ts @@ -2,7 +2,7 @@ import type { LanguageServicePlugin, TextDocument, VirtualCode } from '@volar/la import { isRenameEnabled } from '@vue/language-core'; import { create as baseCreate, type Provide } from 'volar-service-css'; import type * as css from 'vscode-css-languageservice'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create(): LanguageServicePlugin { const base = baseCreate({ scssDocumentSelector: ['scss', 'postcss'] }); diff --git a/packages/language-service/lib/plugins/typescript-semantic-tokens.ts b/packages/language-service/lib/plugins/typescript-semantic-tokens.ts index 64374e2f53..b1600f6ca5 100644 --- a/packages/language-service/lib/plugins/typescript-semantic-tokens.ts +++ b/packages/language-service/lib/plugins/typescript-semantic-tokens.ts @@ -1,6 +1,6 @@ import type { LanguageServicePlugin } from '@volar/language-service'; import { convertClassificationsToSemanticTokens } from 'volar-service-typescript/lib/semanticFeatures/semanticTokens'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create( { getEncodedSemanticClassifications }: import('@vue/typescript-plugin/lib/requests').Requests, diff --git a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts index 199e7ae40e..265d046b0b 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts @@ -1,7 +1,7 @@ import type { LanguageServicePlugin, TextDocument } from '@volar/language-service'; import { hyphenateAttr } from '@vue/language-core'; import type * as ts from 'typescript'; -import { getEmbeddedInfo, sleep } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create( ts: typeof import('typescript'), @@ -16,8 +16,6 @@ export function create( }, }, create(context) { - let currentReq = 0; - return { async provideAutoInsertSnippet(document, selection, change) { const info = getEmbeddedInfo(context, document, id => id.startsWith('script_')); @@ -34,37 +32,27 @@ export function create( return; } - const req = ++currentReq; - // Wait for tsserver to sync - await sleep(250); - if (req !== currentReq) { - return; - } - - const enabled = await context.env.getConfiguration?.('vue.autoInsert.dotValue') ?? true; - if (!enabled) { - return; - } + let sourceOffset: number | undefined; const { sourceScript, virtualCode, root } = info; - const { sfc } = root; - const blocks = [sfc.script, sfc.scriptSetup].filter(block => !!block); - if (!blocks.length) { + const scriptBlocks = [sfc.script, sfc.scriptSetup].filter(block => !!block); + const map = context.language.maps.get(virtualCode, sourceScript); + + if (!scriptBlocks.length) { return; } - let sourceOffset: number | undefined; - const map = context.language.maps.get(virtualCode, sourceScript); for (const [offset] of map.toSourceLocation(document.offsetAt(selection))) { sourceOffset = offset; break; } + if (sourceOffset === undefined) { return; } - for (const { ast, startTagEnd, endTagStart } of blocks) { + for (const { ast, startTagEnd, endTagStart } of scriptBlocks) { if (sourceOffset < startTagEnd || sourceOffset > endTagStart) { continue; } diff --git a/packages/language-service/lib/plugins/vue-autoinsert-space.ts b/packages/language-service/lib/plugins/vue-autoinsert-space.ts index 2de90a028d..e4fc4ea6b4 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-space.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-space.ts @@ -9,15 +9,10 @@ export function create(): LanguageServicePlugin { configurationSections: ['vue.autoInsert.bracketSpacing'], }, }, - create(context) { + create() { return { - async provideAutoInsertSnippet(document, selection, change) { + provideAutoInsertSnippet(document, selection, change) { if (document.languageId === 'html' || document.languageId === 'jade') { - const enabled = await context.env.getConfiguration?.('vue.autoInsert.bracketSpacing') ?? true; - if (!enabled) { - return; - } - if ( change.text === '{}' && document.getText().slice(change.rangeOffset - 1, change.rangeOffset + 3) === '{{}}' diff --git a/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts b/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts index 4721d69d32..e4581673da 100644 --- a/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts +++ b/packages/language-service/lib/plugins/vue-compiler-dom-errors.ts @@ -1,5 +1,5 @@ import type { Diagnostic, DiagnosticSeverity, LanguageServicePlugin } from '@volar/language-service'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create(): LanguageServicePlugin { return { diff --git a/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts b/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts index 655a05c7c7..1b301aed5c 100644 --- a/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts +++ b/packages/language-service/lib/plugins/vue-component-semantic-tokens.ts @@ -1,7 +1,7 @@ import type { LanguageServicePlugin, SemanticToken } from '@volar/language-service'; import { forEachElementNode, hyphenateTag } from '@vue/language-core'; import type * as ts from 'typescript'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create( { getComponentNames, getElementNames }: import('@vue/typescript-plugin/lib/requests').Requests, diff --git a/packages/language-service/lib/plugins/vue-document-drop.ts b/packages/language-service/lib/plugins/vue-document-drop.ts index b38e3df399..f556e01287 100644 --- a/packages/language-service/lib/plugins/vue-document-drop.ts +++ b/packages/language-service/lib/plugins/vue-document-drop.ts @@ -6,7 +6,7 @@ import { getUserPreferences } from 'volar-service-typescript/lib/configs/getUser import { URI } from 'vscode-uri'; import { checkCasing, TagNameCasing } from '../nameCasing'; import { createAddComponentToOptionEdit, getLastImportNode } from '../plugins/vue-extract-file'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create( ts: typeof import('typescript'), diff --git a/packages/language-service/lib/plugins/vue-document-highlights.ts b/packages/language-service/lib/plugins/vue-document-highlights.ts index e2fe6cdc8f..452d6cf1cc 100644 --- a/packages/language-service/lib/plugins/vue-document-highlights.ts +++ b/packages/language-service/lib/plugins/vue-document-highlights.ts @@ -1,5 +1,5 @@ import type { DocumentHighlightKind, LanguageServicePlugin } from '@volar/language-service'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create( { getDocumentHighlights }: import('@vue/typescript-plugin/lib/requests').Requests, diff --git a/packages/language-service/lib/plugins/vue-extract-file.ts b/packages/language-service/lib/plugins/vue-extract-file.ts index 0e49d7deba..06235d620b 100644 --- a/packages/language-service/lib/plugins/vue-extract-file.ts +++ b/packages/language-service/lib/plugins/vue-extract-file.ts @@ -3,7 +3,7 @@ import type { ExpressionNode, TemplateChildNode } from '@vue/compiler-dom'; import { type Sfc, tsCodegen } from '@vue/language-core'; import type * as ts from 'typescript'; import { URI } from 'vscode-uri'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; interface ActionData { uri: string; diff --git a/packages/language-service/lib/plugins/vue-global-types-error.ts b/packages/language-service/lib/plugins/vue-global-types-error.ts index 7bf78db634..557894ad76 100644 --- a/packages/language-service/lib/plugins/vue-global-types-error.ts +++ b/packages/language-service/lib/plugins/vue-global-types-error.ts @@ -1,5 +1,5 @@ import type { DiagnosticSeverity, LanguageServicePlugin } from '@volar/language-service'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create(): LanguageServicePlugin { return { diff --git a/packages/language-service/lib/plugins/vue-inlayhints.ts b/packages/language-service/lib/plugins/vue-inlayhints.ts index 02f5f4bf73..4e75c68be4 100644 --- a/packages/language-service/lib/plugins/vue-inlayhints.ts +++ b/packages/language-service/lib/plugins/vue-inlayhints.ts @@ -1,7 +1,7 @@ import type { InlayHint, InlayHintKind, LanguageServicePlugin } from '@volar/language-service'; import { collectBindingIdentifiers, tsCodegen } from '@vue/language-core'; import type * as ts from 'typescript'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create(ts: typeof import('typescript')): LanguageServicePlugin { return { diff --git a/packages/language-service/lib/plugins/vue-missing-props-hints.ts b/packages/language-service/lib/plugins/vue-missing-props-hints.ts index 0c41c383ef..12ad3b8563 100644 --- a/packages/language-service/lib/plugins/vue-missing-props-hints.ts +++ b/packages/language-service/lib/plugins/vue-missing-props-hints.ts @@ -8,7 +8,7 @@ import type { import { hyphenateAttr, hyphenateTag } from '@vue/language-core'; import * as html from 'vscode-html-languageservice'; import { AttrNameCasing, checkCasing } from '../nameCasing'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create( { getComponentNames, getElementNames, getComponentProps }: import('@vue/typescript-plugin/lib/requests').Requests, diff --git a/packages/language-service/lib/plugins/vue-scoped-class-links.ts b/packages/language-service/lib/plugins/vue-scoped-class-links.ts index d02d655df1..8fc02c9e85 100644 --- a/packages/language-service/lib/plugins/vue-scoped-class-links.ts +++ b/packages/language-service/lib/plugins/vue-scoped-class-links.ts @@ -1,6 +1,6 @@ import type { LanguageServicePlugin } from '@volar/language-service'; import { tsCodegen } from '@vue/language-core'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create(): LanguageServicePlugin { return { diff --git a/packages/language-service/lib/plugins/vue-sfc.ts b/packages/language-service/lib/plugins/vue-sfc.ts index 11584a39af..68095b32cd 100644 --- a/packages/language-service/lib/plugins/vue-sfc.ts +++ b/packages/language-service/lib/plugins/vue-sfc.ts @@ -10,8 +10,8 @@ import type { import { VueVirtualCode } from '@vue/language-core'; import { create as createHtmlService } from 'volar-service-html'; import * as html from 'vscode-html-languageservice'; -import { loadLanguageBlocks } from './data'; -import { getEmbeddedInfo } from './utils'; +import { loadLanguageBlocks } from '../data'; +import { getEmbeddedInfo } from '../utils'; let sfcDataProvider: html.IHTMLDataProvider | undefined; diff --git a/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts b/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts index e79b554bd5..46eda5ac48 100644 --- a/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts +++ b/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts @@ -1,6 +1,6 @@ import type { CompletionItem, CompletionItemKind, LanguageServicePlugin } from '@volar/language-service'; import { type TextRange, tsCodegen } from '@vue/language-core'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create(): LanguageServicePlugin { return { diff --git a/packages/language-service/lib/plugins/vue-template-ref-links.ts b/packages/language-service/lib/plugins/vue-template-ref-links.ts index cd526386c3..73c74f72dc 100644 --- a/packages/language-service/lib/plugins/vue-template-ref-links.ts +++ b/packages/language-service/lib/plugins/vue-template-ref-links.ts @@ -1,6 +1,6 @@ import type { LanguageServicePlugin } from '@volar/language-service'; import { tsCodegen } from '@vue/language-core'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; export function create(): LanguageServicePlugin { return { diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index a5636ac696..42a73b4243 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -13,9 +13,9 @@ import { create as createHtmlService } from 'volar-service-html'; import { create as createPugService } from 'volar-service-pug'; import * as html from 'vscode-html-languageservice'; import { URI, Utils } from 'vscode-uri'; +import { loadModelModifiersData, loadTemplateData } from '../data'; import { AttrNameCasing, checkCasing, TagNameCasing } from '../nameCasing'; -import { loadModelModifiersData, loadTemplateData } from './data'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; const specialTags = new Set([ 'slot', diff --git a/packages/language-service/lib/plugins/vue-twoslash-queries.ts b/packages/language-service/lib/plugins/vue-twoslash-queries.ts index c93e80bc34..b67f327790 100644 --- a/packages/language-service/lib/plugins/vue-twoslash-queries.ts +++ b/packages/language-service/lib/plugins/vue-twoslash-queries.ts @@ -1,5 +1,5 @@ import type { InlayHint, LanguageServicePlugin, Position } from '@volar/language-service'; -import { getEmbeddedInfo } from './utils'; +import { getEmbeddedInfo } from '../utils'; const twoslashTemplateReg = //g; const twoslashScriptReg = /(?<=^|\n)\s*\/\/\s*\^\?/g; diff --git a/packages/language-service/lib/plugins/utils.ts b/packages/language-service/lib/utils.ts similarity index 92% rename from packages/language-service/lib/plugins/utils.ts rename to packages/language-service/lib/utils.ts index 60ddd36d42..f915843766 100644 --- a/packages/language-service/lib/plugins/utils.ts +++ b/packages/language-service/lib/utils.ts @@ -2,10 +2,6 @@ import { type LanguageServiceContext, type SourceScript, type TextDocument } fro import { VueVirtualCode } from '@vue/language-core'; import { URI } from 'vscode-uri'; -export function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - export function getEmbeddedInfo( context: LanguageServiceContext, document: TextDocument, From dff519af4505d9935cbba80fe3ba5234571e23fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:15:44 +0800 Subject: [PATCH 22/85] fix(vscode): add `class` scope fallback for `component` semantic tokens (#5559) --- extensions/vscode/package.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 02609994fe..50a8c8f071 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -222,7 +222,8 @@ "language": "vue", "scopes": { "component": [ - "support.class.component.vue" + "support.class.component.vue", + "entity.name.type.class.vue" ] } }, @@ -230,7 +231,8 @@ "language": "markdown", "scopes": { "component": [ - "support.class.component.vue" + "support.class.component.vue", + "entity.name.type.class.vue" ] } }, @@ -238,7 +240,8 @@ "language": "html", "scopes": { "component": [ - "support.class.component.vue" + "support.class.component.vue", + "entity.name.type.class.vue" ] } } From d80cfee142fda34772286c1e4b7c35d92e9e7b38 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 25 Jul 2025 21:19:57 +0800 Subject: [PATCH 23/85] fix(vscode): make sure extension is loaded immediately close #5533 --- extensions/vscode/languages/stub.json | 1 + extensions/vscode/package.json | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 extensions/vscode/languages/stub.json diff --git a/extensions/vscode/languages/stub.json b/extensions/vscode/languages/stub.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/extensions/vscode/languages/stub.json @@ -0,0 +1 @@ +{} diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 50a8c8f071..62e015bb65 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -67,6 +67,11 @@ { "id": "jade", "configuration": "./languages/sfc-template-language-configuration.json" + }, + { + "id": "plaintext", + "configuration": "./languages/stub.json", + "//": "Make sure Vue extension is loaded immediately when VSCode starts." } ], "typescriptServerPlugins": [ From d407159442b42bc3021b58834f829a83de3c571a Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 26 Jul 2025 05:35:26 +0800 Subject: [PATCH 24/85] fix(language-service): only check globalTypesPath for FS files --- .../language-service/lib/plugins/vue-global-types-error.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/language-service/lib/plugins/vue-global-types-error.ts b/packages/language-service/lib/plugins/vue-global-types-error.ts index 557894ad76..db1253b2e5 100644 --- a/packages/language-service/lib/plugins/vue-global-types-error.ts +++ b/packages/language-service/lib/plugins/vue-global-types-error.ts @@ -17,7 +17,10 @@ export function create(): LanguageServicePlugin { if (!info) { return; } - const { root } = info; + const { sourceScript, root } = info; + if (sourceScript.id.scheme !== 'file') { + return; + } const { vueCompilerOptions } = root; const globalTypesPath = vueCompilerOptions.globalTypesPath(root.fileName); From e3aa6d1dd50bf8fa22fc765aa2a7ffe841254934 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 26 Jul 2025 05:56:47 +0800 Subject: [PATCH 25/85] fix(vscode): handle fail tsserver requests to avlid memory leak --- extensions/vscode/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/vscode/index.ts b/extensions/vscode/index.ts index 98feb55257..fc15a96ebf 100644 --- a/extensions/vscode/index.ts +++ b/extensions/vscode/index.ts @@ -171,13 +171,16 @@ function launch(context: vscode.ExtensionContext) { ); client.onNotification('tsserver/request', async ([seq, command, args]) => { - const res = await vscode.commands.executeCommand<{ body: unknown } | undefined>( + vscode.commands.executeCommand<{ body: unknown } | undefined>( 'typescript.tsserverRequest', command, args, { isAsync: true, lowPriority: true }, - ); - client.sendNotification('tsserver/response', [seq, res?.body]); + ).then(res => { + client.sendNotification('tsserver/response', [seq, res?.body]); + }, () => { + client.sendNotification('tsserver/response', [seq, undefined]); + }); }); client.start(); From a2b2bb36cd892c6db215234ade856195fd8f736b Mon Sep 17 00:00:00 2001 From: KazariEX Date: Sat, 26 Jul 2025 23:42:37 +0800 Subject: [PATCH 26/85] fix(typescript-plugin): ensure all requests do not return void --- packages/language-service/index.ts | 22 +++------ .../lib/requests/collectExtractProps.ts | 14 ++---- .../lib/requests/getComponentDirectives.ts | 12 +++-- .../lib/requests/getComponentEvents.ts | 14 +++--- .../lib/requests/getComponentNames.ts | 21 +++----- .../lib/requests/getComponentProps.ts | 14 +++--- .../lib/requests/getComponentSlots.ts | 13 ++--- .../lib/requests/getElementAttrs.ts | 11 +++-- .../lib/requests/getElementNames.ts | 19 +++----- .../lib/requests/getPropertiesAtLocation.ts | 48 ++++++++++--------- 10 files changed, 87 insertions(+), 101 deletions(-) diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index 76f04a05df..a95353cb72 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -31,22 +31,14 @@ import { create as createVueTwoslashQueriesPlugin } from './lib/plugins/vue-twos export function createVueLanguageServicePlugins( ts: typeof import('typescript'), - tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests = { - collectExtractProps: () => undefined, - getImportPathForFile: () => undefined, - getPropertiesAtLocation: () => undefined, - getComponentDirectives: () => undefined, - getComponentEvents: () => undefined, - getComponentNames: () => undefined, - getComponentProps: () => undefined, - getComponentSlots: () => undefined, - getElementAttrs: () => undefined, - getElementNames: () => undefined, - getDocumentHighlights: () => undefined, - getEncodedSemanticClassifications: () => undefined, - getQuickInfoAtPosition: () => undefined, - }, + tsPluginClient?: import('@vue/typescript-plugin/lib/requests').Requests, ) { + tsPluginClient ??= new Proxy({}, { + get() { + return () => undefined; + }, + }) as NonNullable; + return [ createCssPlugin(), createJsonPlugin(), diff --git a/packages/typescript-plugin/lib/requests/collectExtractProps.ts b/packages/typescript-plugin/lib/requests/collectExtractProps.ts index d272872947..363457a095 100644 --- a/packages/typescript-plugin/lib/requests/collectExtractProps.ts +++ b/packages/typescript-plugin/lib/requests/collectExtractProps.ts @@ -9,13 +9,9 @@ export function collectExtractProps( const { typescript: ts, languageService, language } = this; const sourceScript = language.scripts.get(fileName); - if (!sourceScript?.generated) { - return; - } - - const root = sourceScript.generated.root; - if (!(root instanceof VueVirtualCode)) { - return; + const root = sourceScript?.generated?.root; + if (!sourceScript?.generated || !(root instanceof VueVirtualCode)) { + return []; } const result = new Map map) : []; + const script = sourceScript.generated.languagePlugin.typescript?.getServiceScript(root); + const maps = script ? [...language.maps.forEach(script.code)].map(([, map]) => map) : []; const { sfc } = root; sourceFile.forEachChild(function visit(node) { diff --git a/packages/typescript-plugin/lib/requests/getComponentDirectives.ts b/packages/typescript-plugin/lib/requests/getComponentDirectives.ts index c9b4dec522..f5181e44c1 100644 --- a/packages/typescript-plugin/lib/requests/getComponentDirectives.ts +++ b/packages/typescript-plugin/lib/requests/getComponentDirectives.ts @@ -16,12 +16,14 @@ export function getComponentDirectives( fileName: string, ) { const { typescript: ts, language, languageService } = this; - const volarFile = language.scripts.get(fileName); - if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { - return; + + const sourceScript = language.scripts.get(fileName); + const root = sourceScript?.generated?.root; + if (!sourceScript?.generated || !(root instanceof VueVirtualCode)) { + return []; } - const vueCode = volarFile.generated.root; - const directives = getVariableType(ts, languageService, vueCode, '__VLS_directives'); + + const directives = getVariableType(ts, languageService, root, '__VLS_directives'); if (!directives) { return []; } diff --git a/packages/typescript-plugin/lib/requests/getComponentEvents.ts b/packages/typescript-plugin/lib/requests/getComponentEvents.ts index 4e5cecf15f..812feed769 100644 --- a/packages/typescript-plugin/lib/requests/getComponentEvents.ts +++ b/packages/typescript-plugin/lib/requests/getComponentEvents.ts @@ -8,19 +8,21 @@ export function getComponentEvents( tag: string, ) { const { typescript: ts, language, languageService } = this; - const volarFile = language.scripts.get(fileName); - if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { - return; + + const sourceScript = language.scripts.get(fileName); + const root = sourceScript?.generated?.root; + if (!sourceScript?.generated || !(root instanceof VueVirtualCode)) { + return []; } - const vueCode = volarFile.generated.root; + const program = languageService.getProgram()!; const checker = program.getTypeChecker(); - const components = getVariableType(ts, languageService, vueCode, '__VLS_components'); + const components = getVariableType(ts, languageService, root, '__VLS_components'); if (!components) { return []; } - const componentType = getComponentType(ts, languageService, vueCode, components, fileName, tag); + const componentType = getComponentType(ts, languageService, root, components, fileName, tag); if (!componentType) { return []; } diff --git a/packages/typescript-plugin/lib/requests/getComponentNames.ts b/packages/typescript-plugin/lib/requests/getComponentNames.ts index e80a7ac659..a6e28f5e26 100644 --- a/packages/typescript-plugin/lib/requests/getComponentNames.ts +++ b/packages/typescript-plugin/lib/requests/getComponentNames.ts @@ -1,5 +1,4 @@ import { VueVirtualCode } from '@vue/language-core'; -import type * as ts from 'typescript'; import type { RequestContext } from './types'; import { getSelfComponentName, getVariableType } from './utils'; @@ -8,26 +7,20 @@ export function getComponentNames( fileName: string, ) { const { typescript: ts, language, languageService } = this; - const volarFile = language.scripts.get(fileName); - if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { - return; + + const sourceScript = language.scripts.get(fileName); + const root = sourceScript?.generated?.root; + if (!sourceScript?.generated || !(root instanceof VueVirtualCode)) { + return []; } - const vueCode = volarFile.generated.root; - return _getComponentNames(ts, languageService, vueCode); -} -export function _getComponentNames( - ts: typeof import('typescript'), - tsLs: ts.LanguageService, - vueCode: VueVirtualCode, -) { - const names = getVariableType(ts, tsLs, vueCode, '__VLS_components') + const names = getVariableType(ts, languageService, root, '__VLS_components') ?.type ?.getProperties() .map(c => c.name) .filter(entry => !entry.includes('$') && !entry.startsWith('_')) ?? []; - names.push(getSelfComponentName(vueCode.fileName)); + names.push(getSelfComponentName(fileName)); return names; } diff --git a/packages/typescript-plugin/lib/requests/getComponentProps.ts b/packages/typescript-plugin/lib/requests/getComponentProps.ts index f8c88b25e6..ff4160437a 100644 --- a/packages/typescript-plugin/lib/requests/getComponentProps.ts +++ b/packages/typescript-plugin/lib/requests/getComponentProps.ts @@ -18,17 +18,19 @@ export function getComponentProps( tag: string, ) { const { typescript: ts, language, languageService } = this; - const volarFile = language.scripts.get(fileName); - if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { - return; + + const sourceScript = language.scripts.get(fileName); + const root = sourceScript?.generated?.root; + if (!sourceScript?.generated || !(root instanceof VueVirtualCode)) { + return []; } - const vueCode = volarFile.generated.root; - const components = getVariableType(ts, languageService, vueCode, '__VLS_components'); + + const components = getVariableType(ts, languageService, root, '__VLS_components'); if (!components) { return []; } - const componentType = getComponentType(ts, languageService, vueCode, components, fileName, tag); + const componentType = getComponentType(ts, languageService, root, components, fileName, tag); if (!componentType) { return []; } diff --git a/packages/typescript-plugin/lib/requests/getComponentSlots.ts b/packages/typescript-plugin/lib/requests/getComponentSlots.ts index b48828b164..d00f2c9651 100644 --- a/packages/typescript-plugin/lib/requests/getComponentSlots.ts +++ b/packages/typescript-plugin/lib/requests/getComponentSlots.ts @@ -7,19 +7,20 @@ export function getComponentSlots( fileName: string, ) { const { typescript: ts, language, languageService } = this; - const volarFile = language.scripts.get(fileName); - if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { - return; + + const sourceScript = language.scripts.get(fileName); + const root = sourceScript?.generated?.root; + if (!sourceScript?.generated || !(root instanceof VueVirtualCode)) { + return []; } - const vueCode = volarFile.generated.root; - const codegen = tsCodegen.get(vueCode.sfc); + const codegen = tsCodegen.get(root.sfc); if (!codegen) { return; } const assignName = codegen.getSetupSlotsAssignName() ?? `__VLS_slots`; - const slots = getVariableType(ts, languageService, vueCode, assignName); + const slots = getVariableType(ts, languageService, root, assignName); if (!slots) { return []; } diff --git a/packages/typescript-plugin/lib/requests/getElementAttrs.ts b/packages/typescript-plugin/lib/requests/getElementAttrs.ts index dee3f83373..f8fcd3b63e 100644 --- a/packages/typescript-plugin/lib/requests/getElementAttrs.ts +++ b/packages/typescript-plugin/lib/requests/getElementAttrs.ts @@ -8,15 +8,16 @@ export function getElementAttrs( tagName: string, ) { const { typescript: ts, language, languageService } = this; - const volarFile = language.scripts.get(fileName); - if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { - return; + + const sourceScript = language.scripts.get(fileName); + const root = sourceScript?.generated?.root; + if (!sourceScript?.generated || !(root instanceof VueVirtualCode)) { + return []; } - const vueCode = volarFile.generated.root; const program = languageService.getProgram()!; const checker = program.getTypeChecker(); - const elements = getVariableType(ts, languageService, vueCode, '__VLS_elements'); + const elements = getVariableType(ts, languageService, root, '__VLS_elements'); if (!elements) { return []; } diff --git a/packages/typescript-plugin/lib/requests/getElementNames.ts b/packages/typescript-plugin/lib/requests/getElementNames.ts index 15ac70b8ee..13faeecfeb 100644 --- a/packages/typescript-plugin/lib/requests/getElementNames.ts +++ b/packages/typescript-plugin/lib/requests/getElementNames.ts @@ -1,5 +1,4 @@ import { VueVirtualCode } from '@vue/language-core'; -import type * as ts from 'typescript'; import type { RequestContext } from './types'; import { getVariableType } from './utils'; @@ -8,20 +7,14 @@ export function getElementNames( fileName: string, ) { const { typescript: ts, language, languageService } = this; - const volarFile = language.scripts.get(fileName); - if (!(volarFile?.generated?.root instanceof VueVirtualCode)) { - return; + + const sourceScript = language.scripts.get(fileName); + const root = sourceScript?.generated?.root; + if (!sourceScript?.generated || !(root instanceof VueVirtualCode)) { + return []; } - const vueCode = volarFile.generated.root; - return _getElementNames(ts, languageService, vueCode); -} -export function _getElementNames( - ts: typeof import('typescript'), - tsLs: ts.LanguageService, - vueCode: VueVirtualCode, -) { - return getVariableType(ts, tsLs, vueCode, '__VLS_elements') + return getVariableType(ts, languageService, root, '__VLS_elements') ?.type ?.getProperties() .map(c => c.name) diff --git a/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts b/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts index f6131f43d4..5315d2f3e4 100644 --- a/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts +++ b/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts @@ -1,6 +1,6 @@ /// -import { isCompletionEnabled } from '@vue/language-core'; +import { isCompletionEnabled, VueVirtualCode } from '@vue/language-core'; import type * as ts from 'typescript'; import type { RequestContext } from './types'; @@ -12,40 +12,44 @@ export function getPropertiesAtLocation( const { languageService, language, typescript: ts } = this; // mapping - const file = language.scripts.get(fileName); - if (file?.generated) { - const virtualScript = file.generated.languagePlugin.typescript?.getServiceScript(file.generated.root); - if (!virtualScript) { - return; - } - let mapped = false; - for (const [_sourceScript, map] of language.maps.forEach(virtualScript.code)) { - for (const [position2, mapping] of map.toGeneratedLocation(position)) { - if (isCompletionEnabled(mapping.data)) { - position = position2; - mapped = true; - break; - } - } - if (mapped) { + const sourceScript = language.scripts.get(fileName); + const root = sourceScript?.generated?.root; + if (!sourceScript?.generated || !(root instanceof VueVirtualCode)) { + return []; + } + + const virtualScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(root); + if (!virtualScript) { + return []; + } + + let mapped = false; + for (const [_sourceScript, map] of language.maps.forEach(virtualScript.code)) { + for (const [position2, mapping] of map.toGeneratedLocation(position)) { + if (isCompletionEnabled(mapping.data)) { + position = position2; + mapped = true; break; } } - if (!mapped) { - return; + if (mapped) { + break; } - position += file.snapshot.getLength(); } + if (!mapped) { + return []; + } + position += sourceScript.snapshot.getLength(); const program = languageService.getProgram()!; const sourceFile = program.getSourceFile(fileName); if (!sourceFile) { - return; + return []; } const node = findPositionIdentifier(sourceFile, sourceFile, position); if (!node) { - return; + return []; } const checker = program.getTypeChecker(); From ae8093a08b373831127cd8f59070d92013fdf0d5 Mon Sep 17 00:00:00 2001 From: KazariEX Date: Sat, 26 Jul 2025 23:52:38 +0800 Subject: [PATCH 27/85] refactor(typescript-plugin): explicitly annotate requests with type --- .../lib/plugins/vue-document-drop.ts | 15 +++++++-------- .../lib/requests/collectExtractProps.ts | 14 ++++++++------ .../lib/requests/getComponentDirectives.ts | 2 +- .../lib/requests/getComponentEvents.ts | 2 +- .../lib/requests/getComponentNames.ts | 2 +- .../lib/requests/getComponentProps.ts | 2 +- .../lib/requests/getComponentSlots.ts | 4 ++-- .../lib/requests/getElementAttrs.ts | 2 +- .../lib/requests/getElementNames.ts | 2 +- .../lib/requests/getImportPathForFile.ts | 12 ++++++------ .../lib/requests/getPropertiesAtLocation.ts | 2 +- 11 files changed, 30 insertions(+), 29 deletions(-) diff --git a/packages/language-service/lib/plugins/vue-document-drop.ts b/packages/language-service/lib/plugins/vue-document-drop.ts index f556e01287..dc4639c7ea 100644 --- a/packages/language-service/lib/plugins/vue-document-drop.ts +++ b/packages/language-service/lib/plugins/vue-document-drop.ts @@ -64,14 +64,13 @@ export function create( serviceScript.code.snapshot, ); const preferences = await getUserPreferences(context, tsDocument); - const importPathRequest = await getImportPathForFile( - root.fileName, - incomingFileName, - preferences, - ); - if (importPathRequest) { - importPath = importPathRequest; - } + importPath = ( + await getImportPathForFile( + root.fileName, + incomingFileName, + preferences, + ) ?? {} + ).path; } if (!importPath) { diff --git a/packages/typescript-plugin/lib/requests/collectExtractProps.ts b/packages/typescript-plugin/lib/requests/collectExtractProps.ts index 363457a095..4aa3548111 100644 --- a/packages/typescript-plugin/lib/requests/collectExtractProps.ts +++ b/packages/typescript-plugin/lib/requests/collectExtractProps.ts @@ -1,11 +1,17 @@ import { isSemanticTokensEnabled, VueVirtualCode } from '@vue/language-core'; import type { RequestContext } from './types'; +interface ExtractPropsInfo { + name: string; + type: string; + model: boolean; +} + export function collectExtractProps( this: RequestContext, fileName: string, templateCodeRange: [number, number], -) { +): ExtractPropsInfo[] { const { typescript: ts, languageService, language } = this; const sourceScript = language.scripts.get(fileName); @@ -14,11 +20,7 @@ export function collectExtractProps( return []; } - const result = new Map(); + const result = new Map(); const program = languageService.getProgram()!; const sourceFile = program.getSourceFile(fileName)!; const checker = program.getTypeChecker(); diff --git a/packages/typescript-plugin/lib/requests/getComponentDirectives.ts b/packages/typescript-plugin/lib/requests/getComponentDirectives.ts index f5181e44c1..24f204975c 100644 --- a/packages/typescript-plugin/lib/requests/getComponentDirectives.ts +++ b/packages/typescript-plugin/lib/requests/getComponentDirectives.ts @@ -14,7 +14,7 @@ const builtInDirectives = new Set([ export function getComponentDirectives( this: RequestContext, fileName: string, -) { +): string[] { const { typescript: ts, language, languageService } = this; const sourceScript = language.scripts.get(fileName); diff --git a/packages/typescript-plugin/lib/requests/getComponentEvents.ts b/packages/typescript-plugin/lib/requests/getComponentEvents.ts index 812feed769..ceea3992f7 100644 --- a/packages/typescript-plugin/lib/requests/getComponentEvents.ts +++ b/packages/typescript-plugin/lib/requests/getComponentEvents.ts @@ -6,7 +6,7 @@ export function getComponentEvents( this: RequestContext, fileName: string, tag: string, -) { +): string[] { const { typescript: ts, language, languageService } = this; const sourceScript = language.scripts.get(fileName); diff --git a/packages/typescript-plugin/lib/requests/getComponentNames.ts b/packages/typescript-plugin/lib/requests/getComponentNames.ts index a6e28f5e26..f4c4200b7c 100644 --- a/packages/typescript-plugin/lib/requests/getComponentNames.ts +++ b/packages/typescript-plugin/lib/requests/getComponentNames.ts @@ -5,7 +5,7 @@ import { getSelfComponentName, getVariableType } from './utils'; export function getComponentNames( this: RequestContext, fileName: string, -) { +): string[] { const { typescript: ts, language, languageService } = this; const sourceScript = language.scripts.get(fileName); diff --git a/packages/typescript-plugin/lib/requests/getComponentProps.ts b/packages/typescript-plugin/lib/requests/getComponentProps.ts index ff4160437a..e30130a259 100644 --- a/packages/typescript-plugin/lib/requests/getComponentProps.ts +++ b/packages/typescript-plugin/lib/requests/getComponentProps.ts @@ -16,7 +16,7 @@ export function getComponentProps( this: RequestContext, fileName: string, tag: string, -) { +): ComponentPropInfo[] { const { typescript: ts, language, languageService } = this; const sourceScript = language.scripts.get(fileName); diff --git a/packages/typescript-plugin/lib/requests/getComponentSlots.ts b/packages/typescript-plugin/lib/requests/getComponentSlots.ts index d00f2c9651..dce94857cb 100644 --- a/packages/typescript-plugin/lib/requests/getComponentSlots.ts +++ b/packages/typescript-plugin/lib/requests/getComponentSlots.ts @@ -5,7 +5,7 @@ import { getVariableType } from './utils'; export function getComponentSlots( this: RequestContext, fileName: string, -) { +): string[] { const { typescript: ts, language, languageService } = this; const sourceScript = language.scripts.get(fileName); @@ -16,7 +16,7 @@ export function getComponentSlots( const codegen = tsCodegen.get(root.sfc); if (!codegen) { - return; + return []; } const assignName = codegen.getSetupSlotsAssignName() ?? `__VLS_slots`; diff --git a/packages/typescript-plugin/lib/requests/getElementAttrs.ts b/packages/typescript-plugin/lib/requests/getElementAttrs.ts index f8fcd3b63e..0361f57030 100644 --- a/packages/typescript-plugin/lib/requests/getElementAttrs.ts +++ b/packages/typescript-plugin/lib/requests/getElementAttrs.ts @@ -6,7 +6,7 @@ export function getElementAttrs( this: RequestContext, fileName: string, tagName: string, -) { +): string[] { const { typescript: ts, language, languageService } = this; const sourceScript = language.scripts.get(fileName); diff --git a/packages/typescript-plugin/lib/requests/getElementNames.ts b/packages/typescript-plugin/lib/requests/getElementNames.ts index 13faeecfeb..3e384963b3 100644 --- a/packages/typescript-plugin/lib/requests/getElementNames.ts +++ b/packages/typescript-plugin/lib/requests/getElementNames.ts @@ -5,7 +5,7 @@ import { getVariableType } from './utils'; export function getElementNames( this: RequestContext, fileName: string, -) { +): string[] { const { typescript: ts, language, languageService } = this; const sourceScript = language.scripts.get(fileName); diff --git a/packages/typescript-plugin/lib/requests/getImportPathForFile.ts b/packages/typescript-plugin/lib/requests/getImportPathForFile.ts index 00c7afa428..1b92b07f95 100644 --- a/packages/typescript-plugin/lib/requests/getImportPathForFile.ts +++ b/packages/typescript-plugin/lib/requests/getImportPathForFile.ts @@ -6,13 +6,13 @@ export function getImportPathForFile( fileName: string, incomingFileName: string, preferences: ts.UserPreferences, -) { +): { path?: string } { const { typescript: ts, languageService, languageServiceHost } = this; const program = languageService.getProgram(); const incomingFile = program?.getSourceFile(incomingFileName); const sourceFile = program?.getSourceFile(fileName); if (!program || !sourceFile || !incomingFile) { - return; + return {}; } const getModuleSpecifiersWithCacheInfo: ( @@ -28,7 +28,7 @@ export function getImportPathForFile( computedWithoutCache: boolean; } = (ts as any).moduleSpecifiers.getModuleSpecifiersWithCacheInfo; const resolutionHost = (ts as any).createModuleSpecifierResolutionHost(program, languageServiceHost); - const moduleSpecifiers = getModuleSpecifiersWithCacheInfo( + const { moduleSpecifiers } = getModuleSpecifiersWithCacheInfo( (incomingFile as any).symbol, program.getTypeChecker(), languageServiceHost.getCompilationSettings(), @@ -37,7 +37,7 @@ export function getImportPathForFile( preferences, ); - for (const moduleSpecifier of moduleSpecifiers.moduleSpecifiers) { - return moduleSpecifier; - } + return { + path: moduleSpecifiers[0], + }; } diff --git a/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts b/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts index 5315d2f3e4..1d9e12e217 100644 --- a/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts +++ b/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts @@ -8,7 +8,7 @@ export function getPropertiesAtLocation( this: RequestContext, fileName: string, position: number, -) { +): string[] { const { languageService, language, typescript: ts } = this; // mapping From fba08b394c33295699b0ac01cd4d45d344824a6c Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Jul 2025 08:47:09 +0800 Subject: [PATCH 28/85] fix(vscode): do not delay the execution of restartExtensionHost close #5544 --- extensions/vscode/index.ts | 40 ++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/extensions/vscode/index.ts b/extensions/vscode/index.ts index fc15a96ebf..4ba2152f8e 100644 --- a/extensions/vscode/index.ts +++ b/extensions/vscode/index.ts @@ -48,9 +48,7 @@ class _LanguageClient extends lsp.LanguageClient { } } -export const { activate, deactivate } = defineExtension(async () => { - await vscode.extensions.getExtension('vscode.typescript-language-features')?.activate(); - +export const { activate, deactivate } = defineExtension(() => { const context = extensionContext.value!; const volarLabs = createLabsInfo(); const activeTextEditor = useActiveTextEditor(); @@ -67,23 +65,18 @@ export const { activate, deactivate } = defineExtension(async () => { nextTick(() => stop()); if (needRestart) { - if (vscode.env.remoteName) { - vscode.window.showInformationMessage( - 'Please restart the extension host to activate Vue support in remote environments.', - 'Restart Extension Host', - 'Reload Window', - ).then(action => { - if (action === 'Restart Extension Host') { - vscode.commands.executeCommand('workbench.action.restartExtensionHost'); - } - else if (action === 'Reload Window') { - vscode.commands.executeCommand('workbench.action.reloadWindow'); - } - }); - } - else { - vscode.commands.executeCommand('workbench.action.restartExtensionHost'); - } + vscode.window.showInformationMessage( + 'Please restart the extension host to activate Vue support in remote environments.', + 'Restart Extension Host', + 'Reload Window', + ).then(action => { + if (action === 'Restart Extension Host') { + vscode.commands.executeCommand('workbench.action.restartExtensionHost'); + } + else if (action === 'Reload Window') { + vscode.commands.executeCommand('workbench.action.reloadWindow'); + } + }); return; } @@ -245,7 +238,12 @@ try { } if (tsExtension.isActive) { - needRestart = true; + if (!vscode.env.remoteName) { + vscode.commands.executeCommand('workbench.action.restartExtensionHost'); + } + else { + needRestart = true; + } } } catch {} From 403412a6ded3fac98d426109b7c0d53d10c2521b Mon Sep 17 00:00:00 2001 From: KazariEX Date: Sun, 27 Jul 2025 16:02:52 +0800 Subject: [PATCH 29/85] test(tsc): add missing project references --- .../passedFixtures/vue3.4_strictTemplate/tsconfig.json | 9 --------- .../#3718/main.vue | 0 test-workspace/tsc/tsconfig.json | 6 +++++- 3 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 test-workspace/tsc/passedFixtures/vue3.4_strictTemplate/tsconfig.json rename test-workspace/tsc/passedFixtures/{vue3.4_strictTemplate => vue3_strictTemplate}/#3718/main.vue (100%) diff --git a/test-workspace/tsc/passedFixtures/vue3.4_strictTemplate/tsconfig.json b/test-workspace/tsc/passedFixtures/vue3.4_strictTemplate/tsconfig.json deleted file mode 100644 index f4a3569427..0000000000 --- a/test-workspace/tsc/passedFixtures/vue3.4_strictTemplate/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "vueCompilerOptions": { - "target": 3.3, - "lib": "vue3.4", - "strictTemplates": true - }, - "include": ["**/*"] -} diff --git a/test-workspace/tsc/passedFixtures/vue3.4_strictTemplate/#3718/main.vue b/test-workspace/tsc/passedFixtures/vue3_strictTemplate/#3718/main.vue similarity index 100% rename from test-workspace/tsc/passedFixtures/vue3.4_strictTemplate/#3718/main.vue rename to test-workspace/tsc/passedFixtures/vue3_strictTemplate/#3718/main.vue diff --git a/test-workspace/tsc/tsconfig.json b/test-workspace/tsc/tsconfig.json index 740f6be87d..ab58c0bcb4 100644 --- a/test-workspace/tsc/tsconfig.json +++ b/test-workspace/tsc/tsconfig.json @@ -2,7 +2,7 @@ "include": [], "references": [ { "path": "./failureFixtures/#3632" }, - // { "path": "./failureFixtures/#4569" }, // TODO: not working with --build flag + { "path": "./failureFixtures/#4569" }, { "path": "./failureFixtures/#5071" }, { "path": "./failureFixtures/directives" }, @@ -14,6 +14,10 @@ { "path": "./passedFixtures/#3592" }, { "path": "./passedFixtures/#3819" }, { "path": "./passedFixtures/#4503" }, + { "path": "./passedFixtures/#5106" }, + { "path": "./passedFixtures/#5111" }, + { "path": "./passedFixtures/#5136" }, + { "path": "./passedFixtures/#5338" }, { "path": "./passedFixtures/core#9923" }, { "path": "./passedFixtures/fallthroughAttributes" }, { "path": "./passedFixtures/fallthroughAttributes_strictTemplate" }, From 6d59dee68699b5c36caffed462c96af4ebdb9210 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Jul 2025 22:15:42 +0800 Subject: [PATCH 30/85] chore: update volar to 2.4.22 --- extensions/vscode/package.json | 2 +- packages/component-meta/package.json | 2 +- packages/language-core/package.json | 4 +- packages/language-plugin-pug/package.json | 2 +- packages/language-server/package.json | 4 +- packages/language-service/package.json | 6 +- packages/tsc/package.json | 2 +- packages/typescript-plugin/package.json | 2 +- pnpm-lock.yaml | 160 +++++++++++----------- 9 files changed, 92 insertions(+), 92 deletions(-) diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 62e015bb65..d3fe8c4036 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -471,7 +471,7 @@ "@types/node": "^22.10.4", "@types/semver": "^7.5.3", "@types/vscode": "1.88.0", - "@volar/vscode": "2.4.20", + "@volar/vscode": "2.4.22", "@vscode/vsce": "^3.2.1", "@vue/compiler-sfc": "^3.5.0", "@vue/language-server": "3.0.4", diff --git a/packages/component-meta/package.json b/packages/component-meta/package.json index 504a2f739c..2c5e3c98bc 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.20", + "@volar/typescript": "2.4.22", "@vue/language-core": "3.0.4", "path-browserify": "^1.0.1" }, diff --git a/packages/language-core/package.json b/packages/language-core/package.json index 24a78ae47d..1fa01c4148 100644 --- a/packages/language-core/package.json +++ b/packages/language-core/package.json @@ -13,7 +13,7 @@ "directory": "packages/language-core" }, "dependencies": { - "@volar/language-core": "2.4.20", + "@volar/language-core": "2.4.22", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", @@ -26,7 +26,7 @@ "@types/node": "^22.10.4", "@types/path-browserify": "^1.0.1", "@types/picomatch": "^4.0.0", - "@volar/typescript": "2.4.20", + "@volar/typescript": "2.4.22", "@vue/compiler-sfc": "^3.5.0" }, "peerDependencies": { diff --git a/packages/language-plugin-pug/package.json b/packages/language-plugin-pug/package.json index 290f9f1b81..cbdedd9fa1 100644 --- a/packages/language-plugin-pug/package.json +++ b/packages/language-plugin-pug/package.json @@ -13,7 +13,7 @@ "directory": "packages/language-plugin-pug" }, "dependencies": { - "@volar/source-map": "2.4.20", + "@volar/source-map": "2.4.22", "volar-service-pug": "0.0.65" }, "devDependencies": { diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 2c45282971..97bcf65a75 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -16,7 +16,7 @@ "directory": "packages/language-server" }, "dependencies": { - "@volar/language-server": "2.4.20", + "@volar/language-server": "2.4.22", "@vue/language-core": "3.0.4", "@vue/language-service": "3.0.4", "@vue/typescript-plugin": "3.0.4", @@ -27,6 +27,6 @@ }, "devDependencies": { "@typescript/server-harness": "latest", - "@volar/test-utils": "2.4.20" + "@volar/test-utils": "2.4.22" } } diff --git a/packages/language-service/package.json b/packages/language-service/package.json index 990ea50d2a..3923562bc2 100644 --- a/packages/language-service/package.json +++ b/packages/language-service/package.json @@ -17,7 +17,7 @@ "update-html-data": "node ./scripts/update-html-data.js" }, "dependencies": { - "@volar/language-service": "2.4.20", + "@volar/language-service": "2.4.22", "@vue/language-core": "3.0.4", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1", @@ -34,8 +34,8 @@ "devDependencies": { "@types/node": "^22.10.4", "@types/path-browserify": "^1.0.1", - "@volar/kit": "2.4.20", - "@volar/typescript": "2.4.20", + "@volar/kit": "2.4.22", + "@volar/typescript": "2.4.22", "@vue/compiler-dom": "^3.5.0", "@vue/typescript-plugin": "3.0.4", "vscode-css-languageservice": "^6.3.1" diff --git a/packages/tsc/package.json b/packages/tsc/package.json index 4bb8ee5818..0b546dfc7b 100644 --- a/packages/tsc/package.json +++ b/packages/tsc/package.json @@ -20,7 +20,7 @@ "typescript": ">=5.0.0" }, "dependencies": { - "@volar/typescript": "2.4.20", + "@volar/typescript": "2.4.22", "@vue/language-core": "3.0.4" }, "devDependencies": { diff --git a/packages/typescript-plugin/package.json b/packages/typescript-plugin/package.json index 3c402e5190..1d84ec05bf 100644 --- a/packages/typescript-plugin/package.json +++ b/packages/typescript-plugin/package.json @@ -13,7 +13,7 @@ "directory": "packages/typescript-plugin" }, "dependencies": { - "@volar/typescript": "2.4.20", + "@volar/typescript": "2.4.22", "@vue/language-core": "3.0.4", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef90bc2b68..1a58514491 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,8 +51,8 @@ importers: specifier: 1.88.0 version: 1.88.0 '@volar/vscode': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 '@vscode/vsce': specifier: ^3.2.1 version: 3.3.2 @@ -84,8 +84,8 @@ importers: packages/component-meta: dependencies: '@volar/typescript': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 '@vue/language-core': specifier: 3.0.4 version: link:../language-core @@ -111,8 +111,8 @@ importers: packages/language-core: dependencies: '@volar/language-core': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 '@vue/compiler-dom': specifier: ^3.5.0 version: 3.5.13 @@ -148,8 +148,8 @@ importers: specifier: ^4.0.0 version: 4.0.0 '@volar/typescript': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 '@vue/compiler-sfc': specifier: ^3.5.0 version: 3.5.13 @@ -157,8 +157,8 @@ importers: packages/language-plugin-pug: dependencies: '@volar/source-map': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 volar-service-pug: specifier: 0.0.65 version: 0.0.65 @@ -176,8 +176,8 @@ importers: packages/language-server: dependencies: '@volar/language-server': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 '@vue/language-core': specifier: 3.0.4 version: link:../language-core @@ -198,14 +198,14 @@ importers: specifier: latest version: 0.3.5 '@volar/test-utils': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 packages/language-service: dependencies: '@volar/language-service': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 '@vue/language-core': specifier: 3.0.4 version: link:../language-core @@ -217,25 +217,25 @@ importers: version: 1.0.1 volar-service-css: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.20) + version: 0.0.65(@volar/language-service@2.4.22) volar-service-emmet: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.20) + version: 0.0.65(@volar/language-service@2.4.22) volar-service-html: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.20) + version: 0.0.65(@volar/language-service@2.4.22) volar-service-json: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.20) + version: 0.0.65(@volar/language-service@2.4.22) volar-service-pug: specifier: 0.0.65 version: 0.0.65 volar-service-pug-beautify: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.20) + version: 0.0.65(@volar/language-service@2.4.22) volar-service-typescript: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.20) + version: 0.0.65(@volar/language-service@2.4.22) vscode-html-languageservice: specifier: ^5.2.0 version: 5.4.0 @@ -250,11 +250,11 @@ importers: specifier: ^1.0.1 version: 1.0.3 '@volar/kit': - specifier: 2.4.20 - version: 2.4.20(typescript@5.9.0-dev.20250425) + specifier: 2.4.22 + version: 2.4.22(typescript@5.9.0-dev.20250425) '@volar/typescript': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 '@vue/compiler-dom': specifier: ^3.5.0 version: 3.5.13 @@ -268,8 +268,8 @@ importers: packages/tsc: dependencies: '@volar/typescript': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 '@vue/language-core': specifier: 3.0.4 version: link:../language-core @@ -284,8 +284,8 @@ importers: packages/typescript-plugin: dependencies: '@volar/typescript': - specifier: 2.4.20 - version: 2.4.20 + specifier: 2.4.22 + version: 2.4.22 '@vue/language-core': specifier: 3.0.4 version: link:../language-core @@ -1467,31 +1467,31 @@ packages: '@vitest/utils@3.1.3': resolution: {integrity: sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==} - '@volar/kit@2.4.20': - resolution: {integrity: sha512-zC2uN3veE8uT5v+2FuwK2ocRoDpcuqbJpPNZR6jO/renewVVOleNTFv3gbk0hoG3qnAVENDGTw11bT49A9aefw==} + '@volar/kit@2.4.22': + resolution: {integrity: sha512-o2LhNb2PLCUJ6v2XSqN7m+pJt+SE0QW1U2E52jnS8yZ03ohcGOOuFJdH1VlZgCBk0RlwO4xp0OaDoTtyTvMTrw==} peerDependencies: typescript: '*' - '@volar/language-core@2.4.20': - resolution: {integrity: sha512-dRDF1G33xaAIDqR6+mXUIjXYdu9vzSxlMGfMEwBxQsfY/JMUEXSpLTR057oTKlUQ2nIvCmP9k94A8h8z2VrNSA==} + '@volar/language-core@2.4.22': + resolution: {integrity: sha512-gp4M7Di5KgNyIyO903wTClYBavRt6UyFNpc5LWfyZr1lBsTUY+QrVZfmbNF2aCyfklBOVk9YC4p+zkwoyT7ECg==} - '@volar/language-server@2.4.20': - resolution: {integrity: sha512-fNNFzEad0sO4pVZnpHggglbIeaKjLs4vH1JPPN+zd/4hSEI2u8+Qck10JhswCSO6xFTFbKxVquvWu2U2tT0EHQ==} + '@volar/language-server@2.4.22': + resolution: {integrity: sha512-THIGWcQsEJKZU7SjVKPcy4MIamX4qpusKErj33ru7fi2WcD+FmFjYY/F2LIk/C15xEcb34JT1uZBlbO2dfzYSQ==} - '@volar/language-service@2.4.20': - resolution: {integrity: sha512-LoCD4rEI1Bj5ld6b+2GH1SbDGnoisvJ5skHlrkFEtJWw0T2+bhqGUXwekFudV/bRtp8fPhvD5ZUtjWSW0VRztg==} + '@volar/language-service@2.4.22': + resolution: {integrity: sha512-8TmvOf/6uqaJMBVQIP9kgVpRzMrqLI3nCmWuSIPAldlmwjZTOiN17GA4AL4sTFJUg61xCSyMQWbProNFQ88yew==} - '@volar/source-map@2.4.20': - resolution: {integrity: sha512-mVjmFQH8mC+nUaVwmbxoYUy8cww+abaO8dWzqPUjilsavjxH0jCJ3Mp8HFuHsdewZs2c+SP+EO7hCd8Z92whJg==} + '@volar/source-map@2.4.22': + resolution: {integrity: sha512-L2nVr/1vei0xKRgO2tYVXtJYd09HTRjaZi418e85Q+QdbbqA8h7bBjfNyPPSsjnrOO4l4kaAo78c8SQUAdHvgA==} - '@volar/test-utils@2.4.20': - resolution: {integrity: sha512-xTRrNy7Q9udmJMzmS6VlqbPlm+R2Vnzfu9pN4zGskfuKKxsc6w0dby3y7HcWTv/5a2v15FctnVI99B+9LwhePQ==} + '@volar/test-utils@2.4.22': + resolution: {integrity: sha512-hSImBcIJkKqrE8txM+e4qKCBAwAU7PMlSNqitljuBwE1kztTpv4Szaaa7d5BnpIGlNsXdot0hYhpR8y3+BGJzg==} - '@volar/typescript@2.4.20': - resolution: {integrity: sha512-Oc4DczPwQyXcVbd+5RsNEqX6ia0+w3p+klwdZQ6ZKhFjWoBP9PCPQYlKYRi/tDemWphW93P/Vv13vcE9I9D2GQ==} + '@volar/typescript@2.4.22': + resolution: {integrity: sha512-6ZczlJW1/GWTrNnkmZxJp4qyBt/SGVlcTuCWpI5zLrdPdCZsj66Aff9ZsfFaT3TyjG8zVYgBMYPuCm/eRkpcpQ==} - '@volar/vscode@2.4.20': - resolution: {integrity: sha512-u69RZCsS+isskOihcY1th2B//v60mlVBkk6afvntmd81G0cFDUia1IXUhADWl0s8o4xN+aZIGFpWBq6QudEUCQ==} + '@volar/vscode@2.4.22': + resolution: {integrity: sha512-E5egfejA+3YvPqd37IeC676mJOgalQ/aAzGxVDnChdhR5+U5BtWAcILgIAf+T4/Cq12jPOV7KCn07mZ24DDp9w==} '@vscode/emmet-helper@2.11.0': resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} @@ -4967,8 +4967,8 @@ snapshots: '@clack/prompts': 0.8.2 '@tsslint/config': 1.5.16(typescript@5.8.3) '@tsslint/core': 1.5.16 - '@volar/language-core': 2.4.20 - '@volar/typescript': 2.4.20 + '@volar/language-core': 2.4.22 + '@volar/typescript': 2.4.22 glob: 10.4.5 json5: 2.2.3 typescript: 5.8.3 @@ -5154,24 +5154,24 @@ snapshots: loupe: 3.1.3 tinyrainbow: 2.0.0 - '@volar/kit@2.4.20(typescript@5.9.0-dev.20250425)': + '@volar/kit@2.4.22(typescript@5.9.0-dev.20250425)': dependencies: - '@volar/language-service': 2.4.20 - '@volar/typescript': 2.4.20 + '@volar/language-service': 2.4.22 + '@volar/typescript': 2.4.22 typesafe-path: 0.2.2 typescript: 5.9.0-dev.20250425 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/language-core@2.4.20': + '@volar/language-core@2.4.22': dependencies: - '@volar/source-map': 2.4.20 + '@volar/source-map': 2.4.22 - '@volar/language-server@2.4.20': + '@volar/language-server@2.4.22': dependencies: - '@volar/language-core': 2.4.20 - '@volar/language-service': 2.4.20 - '@volar/typescript': 2.4.20 + '@volar/language-core': 2.4.22 + '@volar/language-service': 2.4.22 + '@volar/typescript': 2.4.22 path-browserify: 1.0.1 request-light: 0.7.0 vscode-languageserver: 9.0.1 @@ -5179,29 +5179,29 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/language-service@2.4.20': + '@volar/language-service@2.4.22': dependencies: - '@volar/language-core': 2.4.20 + '@volar/language-core': 2.4.22 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/source-map@2.4.20': {} + '@volar/source-map@2.4.22': {} - '@volar/test-utils@2.4.20': + '@volar/test-utils@2.4.22': dependencies: - '@volar/language-core': 2.4.20 - '@volar/language-server': 2.4.20 + '@volar/language-core': 2.4.22 + '@volar/language-server': 2.4.22 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/typescript@2.4.20': + '@volar/typescript@2.4.22': dependencies: - '@volar/language-core': 2.4.20 + '@volar/language-core': 2.4.22 path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@volar/vscode@2.4.20': + '@volar/vscode@2.4.22': dependencies: path-browserify: 1.0.1 vscode-languageclient: 9.0.1 @@ -7628,55 +7628,55 @@ snapshots: - supports-color - terser - volar-service-css@0.0.65(@volar/language-service@2.4.20): + volar-service-css@0.0.65(@volar/language-service@2.4.22): dependencies: vscode-css-languageservice: 6.3.5 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.20 + '@volar/language-service': 2.4.22 - volar-service-emmet@0.0.65(@volar/language-service@2.4.20): + volar-service-emmet@0.0.65(@volar/language-service@2.4.22): dependencies: '@emmetio/css-parser': https://codeload.github.com/ramya-rao-a/css-parser/tar.gz/370c480ac103bd17c7bcfb34bf5d577dc40d3660 '@emmetio/html-matcher': 1.3.0 '@vscode/emmet-helper': 2.11.0 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.20 + '@volar/language-service': 2.4.22 - volar-service-html@0.0.65(@volar/language-service@2.4.20): + volar-service-html@0.0.65(@volar/language-service@2.4.22): dependencies: vscode-html-languageservice: 5.4.0 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.20 + '@volar/language-service': 2.4.22 - volar-service-json@0.0.65(@volar/language-service@2.4.20): + volar-service-json@0.0.65(@volar/language-service@2.4.22): dependencies: vscode-json-languageservice: 5.6.1 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.20 + '@volar/language-service': 2.4.22 - volar-service-pug-beautify@0.0.65(@volar/language-service@2.4.20): + volar-service-pug-beautify@0.0.65(@volar/language-service@2.4.22): dependencies: '@johnsoncodehk/pug-beautify': 0.2.2 optionalDependencies: - '@volar/language-service': 2.4.20 + '@volar/language-service': 2.4.22 volar-service-pug@0.0.65: dependencies: - '@volar/language-service': 2.4.20 + '@volar/language-service': 2.4.22 muggle-string: 0.4.1 pug-lexer: 5.0.1 pug-parser: 6.0.0 - volar-service-html: 0.0.65(@volar/language-service@2.4.20) + volar-service-html: 0.0.65(@volar/language-service@2.4.22) vscode-html-languageservice: 5.4.0 vscode-languageserver-textdocument: 1.0.12 - volar-service-typescript@0.0.65(@volar/language-service@2.4.20): + volar-service-typescript@0.0.65(@volar/language-service@2.4.22): dependencies: path-browserify: 1.0.1 semver: 7.7.2 @@ -7685,7 +7685,7 @@ snapshots: vscode-nls: 5.2.0 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.20 + '@volar/language-service': 2.4.22 vscode-css-languageservice@6.3.5: dependencies: From 64f5da224ca4681330ae0af40edfa8e7aa9aa54a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Sun, 27 Jul 2025 23:15:52 +0800 Subject: [PATCH 31/85] fix(language-core): avoid references highlight of unrelated native element tags (#5563) --- packages/language-core/lib/codegen/codeFeatures.ts | 11 +++-------- .../language-core/lib/codegen/template/element.ts | 9 +++++++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/language-core/lib/codegen/codeFeatures.ts b/packages/language-core/lib/codegen/codeFeatures.ts index c7143649c7..72d4f896ad 100644 --- a/packages/language-core/lib/codegen/codeFeatures.ts +++ b/packages/language-core/lib/codegen/codeFeatures.ts @@ -24,13 +24,12 @@ const raw = { navigation: { navigation: true, }, + navigationWithoutHighlight: { + navigation: { shouldHighlight: () => false }, + }, navigationWithoutRename: { navigation: { shouldRename: () => false }, }, - navigationAndCompletion: { - navigation: true, - completion: true, - }, navigationAndAdditionalCompletion: { navigation: true, completion: { isAdditional: true }, @@ -63,10 +62,6 @@ const raw = { verification: true, navigation: true, }, - withoutHighlightAndCompletionAndNavigation: { - semantic: { shouldHighlight: () => false }, - verification: true, - }, withoutSemantic: { verification: true, navigation: true, diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index 55e28e1b0e..c20e0bb5d4 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -301,13 +301,18 @@ export function* generateElement( : undefined; const failedPropExps: FailedPropExpression[] = []; + const features = { + ...codeFeatures.semanticWithoutHighlight, + ...codeFeatures.navigationWithoutHighlight, + }; + yield `__VLS_asFunctionalElement(__VLS_elements`; yield* generatePropertyAccess( options, ctx, node.tag, startTagOffset, - ctx.codeFeatures.withoutHighlightAndCompletion, + features, ); if (endTagOffset !== undefined) { yield `, __VLS_elements`; @@ -316,7 +321,7 @@ export function* generateElement( ctx, node.tag, endTagOffset, - ctx.codeFeatures.withoutHighlightAndCompletion, + features, ); } yield `)(`; From aa3531f6e619e253b17592694b0ef5b9f765f229 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Jul 2025 23:38:43 +0800 Subject: [PATCH 32/85] fix(language-core): tolerance for incomplete root template tag close #4893 --- .../language-core/lib/plugins/file-vue.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/language-core/lib/plugins/file-vue.ts b/packages/language-core/lib/plugins/file-vue.ts index 892ab1b5f8..4edc60baea 100644 --- a/packages/language-core/lib/plugins/file-vue.ts +++ b/packages/language-core/lib/plugins/file-vue.ts @@ -19,7 +19,24 @@ const plugin: VueLanguagePlugin = ({ vueCompilerOptions }) => { if (languageId !== 'vue') { return; } - return parse(content); + const sfc = parse(content); + for (const error of sfc.errors) { + // Handle 'Element is missing end tag.' error, see #4893 + if ( + 'code' in error && error.code === 24 && sfc.descriptor.template + && error.loc?.start.line === sfc.descriptor.template.loc.start.line + ) { + const template = sfc.descriptor.template; + const templateText = template.content; + const endTagOffset = templateText.lastIndexOf('<'); + const endTagText = templateText.slice(endTagOffset).trimEnd(); + if (''.startsWith(endTagText)) { + sfc.descriptor.template.loc.end.offset = template.loc.start.offset + endTagOffset; + template.content = templateText.slice(0, endTagOffset); + } + } + } + return sfc; }, updateSFC(sfc, change) { From b7a9bbb25ec843e9b1020044407cebf3792434ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Mon, 28 Jul 2025 01:59:24 +0800 Subject: [PATCH 33/85] refactor(language-core): decouple the usage of code features with template context (#5564) --- .../language-core/lib/codegen/script/index.ts | 9 ++-- .../lib/codegen/template/context.ts | 6 --- .../lib/codegen/template/element.ts | 26 ++++++------ .../lib/codegen/template/elementDirectives.ts | 23 +++++----- .../lib/codegen/template/elementEvents.ts | 13 +++--- .../lib/codegen/template/elementProps.ts | 33 +++++++-------- .../lib/codegen/template/index.ts | 20 ++++----- .../lib/codegen/template/interpolation.ts | 3 +- .../lib/codegen/template/slotOutlet.ts | 13 +++--- .../codegen/template/styleScopedClasses.ts | 7 ++-- .../lib/codegen/template/templateChild.ts | 3 +- .../lib/codegen/template/vFor.ts | 7 ++-- .../language-core/lib/codegen/template/vIf.ts | 3 +- .../lib/codegen/template/vSlot.ts | 16 +++---- packages/language-core/lib/plugins/vue-tsx.ts | 42 +++++++++---------- 15 files changed, 109 insertions(+), 115 deletions(-) diff --git a/packages/language-core/lib/codegen/script/index.ts b/packages/language-core/lib/codegen/script/index.ts index 19fcb2a789..c1c374c543 100644 --- a/packages/language-core/lib/codegen/script/index.ts +++ b/packages/language-core/lib/codegen/script/index.ts @@ -26,9 +26,10 @@ export interface ScriptCodegenOptions { templateRefNames: Set; } -export function* generateScript(options: ScriptCodegenOptions): Generator { - const ctx = createScriptCodegenContext(options); - +export function* generateScript( + options: ScriptCodegenOptions, + ctx: ScriptCodegenContext = createScriptCodegenContext(options), +): Generator { yield* generateGlobalTypesPath(options); if (options.sfc.script?.src) { @@ -148,8 +149,6 @@ export function* generateScript(options: ScriptCodegenOptions): Generator`, ); @@ -469,7 +469,7 @@ function* generateElementReference( ctx, content, startOffset, - ctx.codeFeatures.navigation, + codeFeatures.navigation, ); yield `} */${endOfLine}`; diff --git a/packages/language-core/lib/codegen/template/elementDirectives.ts b/packages/language-core/lib/codegen/template/elementDirectives.ts index 02bf57f3c7..9deaa1491e 100644 --- a/packages/language-core/lib/codegen/template/elementDirectives.ts +++ b/packages/language-core/lib/codegen/template/elementDirectives.ts @@ -41,9 +41,9 @@ export function* generateElementDirectives( yield* wrapWith( prop.loc.start.offset, prop.loc.end.offset, - ctx.codeFeatures.verification, + codeFeatures.verification, `__VLS_asFunctionalDirective(`, - ...generateIdentifier(options, ctx, prop), + ...generateIdentifier(options, prop), `)(null!, { ...__VLS_directiveBindingRestFields, `, ...generateArg(options, ctx, prop), ...generateModifiers(options, ctx, prop), @@ -56,23 +56,22 @@ export function* generateElementDirectives( function* generateIdentifier( options: TemplateCodegenOptions, - ctx: TemplateCodegenContext, prop: CompilerDOM.DirectiveNode, ): Generator { const rawName = 'v-' + prop.name; yield* wrapWith( prop.loc.start.offset, prop.loc.start.offset + rawName.length, - ctx.codeFeatures.verification, + codeFeatures.verification, `__VLS_directives.`, ...generateCamelized( rawName, 'template', prop.loc.start.offset, - ctx.resolveCodeFeatures({ + { ...codeFeatures.withoutHighlightAndCompletion, verification: options.vueCompilerOptions.checkUnknownDirectives && !builtInDirectives.has(prop.name), - }), + }, ), ); } @@ -92,7 +91,7 @@ function* generateArg( yield* wrapWith( startOffset, startOffset + arg.content.length, - ctx.codeFeatures.verification, + codeFeatures.verification, `arg`, ); yield `: `; @@ -100,7 +99,7 @@ function* generateArg( yield* generateStringLiteralKey( arg.content, startOffset, - ctx.codeFeatures.all, + codeFeatures.all, ); } else { @@ -108,7 +107,7 @@ function* generateArg( options, ctx, 'template', - ctx.codeFeatures.all, + codeFeatures.all, arg.content, startOffset, `(`, @@ -135,7 +134,7 @@ export function* generateModifiers( yield* wrapWith( startOffset, endOffset, - ctx.codeFeatures.verification, + codeFeatures.verification, propertyName, ); yield `: { `; @@ -145,7 +144,7 @@ export function* generateModifiers( ctx, mod.content, mod.loc.start.offset, - ctx.codeFeatures.withoutHighlightAndNavigation, + codeFeatures.withoutHighlightAndNavigation, ); yield `: true, `; } @@ -165,7 +164,7 @@ function* generateValue( yield* wrapWith( exp.loc.start.offset, exp.loc.end.offset, - ctx.codeFeatures.verification, + codeFeatures.verification, `value`, ); yield `: `; diff --git a/packages/language-core/lib/codegen/template/elementEvents.ts b/packages/language-core/lib/codegen/template/elementEvents.ts index 132fbe65a7..b38f1334c5 100644 --- a/packages/language-core/lib/codegen/template/elementEvents.ts +++ b/packages/language-core/lib/codegen/template/elementEvents.ts @@ -62,12 +62,12 @@ export function* generateElementEvents( yield `const ${ctx.getInternalVariable()}: __VLS_NormalizeComponentEvent = (${newLine}`; if (prop.name === 'on') { yield `{ `; - yield* generateEventArg(options, ctx, source, start!, emitPrefix.slice(0, -1), ctx.codeFeatures.navigation); + yield* generateEventArg(options, source, start!, emitPrefix.slice(0, -1), codeFeatures.navigation); yield `: {} as any } as typeof ${emitsVar},${newLine}`; } yield `{ `; if (prop.name === 'on') { - yield* generateEventArg(options, ctx, source, start!, propPrefix.slice(0, -1)); + yield* generateEventArg(options, source, start!, propPrefix.slice(0, -1)); yield `: `; yield* generateEventExpression(options, ctx, prop); } @@ -82,19 +82,18 @@ export function* generateElementEvents( export function* generateEventArg( options: TemplateCodegenOptions, - ctx: TemplateCodegenContext, name: string, start: number, directive = 'on', features?: VueCodeInformation, ): Generator { - features ??= ctx.resolveCodeFeatures({ + features ??= { ...codeFeatures.semanticWithoutHighlight, ...codeFeatures.navigationWithoutRename, ...options.vueCompilerOptions.checkUnknownEvents ? codeFeatures.verification : codeFeatures.doNotReportTs2353AndTs2561, - }); + }; if (directive.length) { name = capitalize(name); @@ -159,7 +158,7 @@ export function* generateEventExpression( ].join('\n\n'), }); } - return ctx.codeFeatures.all; + return codeFeatures.all; }, prop.exp.content, prop.exp.loc.start.offset, @@ -192,7 +191,7 @@ export function* generateModelEventExpression( options, ctx, 'template', - ctx.codeFeatures.verification, + codeFeatures.verification, prop.exp.content, prop.exp.loc.start.offset, ); diff --git a/packages/language-core/lib/codegen/template/elementProps.ts b/packages/language-core/lib/codegen/template/elementProps.ts index 57dc63eb40..4aa75ecbe8 100644 --- a/packages/language-core/lib/codegen/template/elementProps.ts +++ b/packages/language-core/lib/codegen/template/elementProps.ts @@ -46,7 +46,7 @@ export function* generateElementProps( ) { if (!isComponent) { yield `...{ `; - yield* generateEventArg(options, ctx, prop.arg.loc.source, prop.arg.loc.start.offset); + yield* generateEventArg(options, prop.arg.loc.source, prop.arg.loc.start.offset); yield `: `; yield* generateEventExpression(options, ctx, prop); yield `},`; @@ -113,7 +113,7 @@ export function* generateElementProps( const shouldSpread = propName === 'style' || propName === 'class'; const shouldCamelize = isComponent && getShouldCamelize(options, prop, propName); - const codeInfo = getPropsCodeInfo(ctx, strictPropsCheck); + const features = getPropsCodeFeatures(strictPropsCheck); if (shouldSpread) { yield `...{ `; @@ -121,7 +121,7 @@ export function* generateElementProps( const codes = [...wrapWith( prop.loc.start.offset, prop.loc.end.offset, - ctx.codeFeatures.verification, + codeFeatures.verification, ...( prop.arg ? generateObjectProperty( @@ -129,13 +129,13 @@ export function* generateElementProps( ctx, propName, prop.arg.loc.start.offset, - codeInfo, + features, shouldCamelize, ) : wrapWith( prop.loc.start.offset, prop.loc.start.offset + 'v-model'.length, - ctx.codeFeatures.withoutHighlightAndCompletion, + codeFeatures.withoutHighlightAndCompletion, propName, ) ), @@ -143,7 +143,7 @@ export function* generateElementProps( ...wrapWith( prop.arg?.loc.start.offset ?? prop.loc.start.offset, prop.arg?.loc.end.offset ?? prop.loc.end.offset, - ctx.codeFeatures.verification, + codeFeatures.verification, ...generatePropExp( options, ctx, @@ -200,7 +200,7 @@ export function* generateElementProps( const shouldSpread = prop.name === 'style' || prop.name === 'class'; const shouldCamelize = isComponent && getShouldCamelize(options, prop, prop.name); - const codeInfo = getPropsCodeInfo(ctx, strictPropsCheck); + const features = getPropsCodeFeatures(strictPropsCheck); if (shouldSpread) { yield `...{ `; @@ -208,19 +208,19 @@ export function* generateElementProps( const codes = [...wrapWith( prop.loc.start.offset, prop.loc.end.offset, - ctx.codeFeatures.verification, + codeFeatures.verification, ...generateObjectProperty( options, ctx, prop.name, prop.loc.start.offset, - codeInfo, + features, shouldCamelize, ), `: `, ...( prop.value - ? generateAttrValue(prop.value, ctx.codeFeatures.withoutNavigation) + ? generateAttrValue(prop.value, codeFeatures.withoutNavigation) : [`true`] ), )]; @@ -250,7 +250,7 @@ export function* generateElementProps( const codes = [...wrapWith( prop.exp.loc.start.offset, prop.exp.loc.end.offset, - ctx.codeFeatures.verification, + codeFeatures.verification, `...`, ...generatePropExp( options, @@ -281,8 +281,8 @@ export function* generatePropExp( ): Generator { const isShorthand = prop.arg?.loc.start.offset === prop.exp?.loc.start.offset; const features = isShorthand - ? ctx.codeFeatures.withoutHighlightAndCompletion - : ctx.codeFeatures.all; + ? codeFeatures.withoutHighlightAndCompletion + : codeFeatures.all; if (exp && exp.constType !== CompilerDOM.ConstantTypes.CAN_STRINGIFY) { // style='z-index: 2' will compile to {'z-index':'2'} if (!isShorthand) { // vue 3.4+ @@ -372,16 +372,15 @@ function getShouldCamelize( && !options.vueCompilerOptions.htmlAttributes.some(pattern => isMatch(propName, pattern)); } -function getPropsCodeInfo( - ctx: TemplateCodegenContext, +function getPropsCodeFeatures( strictPropsCheck: boolean, ): VueCodeInformation { - return ctx.resolveCodeFeatures({ + return { ...codeFeatures.withoutHighlightAndCompletion, ...strictPropsCheck ? codeFeatures.verification : codeFeatures.doNotReportTs2353AndTs2561, - }); + }; } function getModelPropName(node: CompilerDOM.ElementNode, vueCompilerOptions: VueCompilerOptions) { diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index c545c258e5..ace26767bb 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -2,9 +2,10 @@ 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 { codeFeatures } from '../codeFeatures'; import { endOfLine, newLine } from '../utils'; import { wrapWith } from '../utils/wrapWith'; -import { createTemplateCodegenContext, type TemplateCodegenContext } from './context'; +import type { TemplateCodegenContext } from './context'; import { generateObjectProperty } from './objectProperty'; import { generateStyleScopedClassReferences } from './styleScopedClasses'; import { generateTemplateChild, getVForNode } from './templateChild'; @@ -25,9 +26,10 @@ export interface TemplateCodegenOptions { selfComponentName?: string; } -export function* generateTemplate(options: TemplateCodegenOptions): Generator { - const ctx = createTemplateCodegenContext(options, options.template.ast); - +export function* generateTemplate( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext, +): Generator { if (options.slotsAssignName) { ctx.addLocalVariable(options.slotsAssignName); } @@ -68,8 +70,6 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator {}`); slotBlockVars.push(...collectBindingNames(options.ts, slotAst, slotAst)); - yield* generateSlotParameters(options, ctx, slotAst, slotDir.exp, slotVar); + yield* generateSlotParameters(options, slotAst, slotDir.exp, slotVar); } for (const varName of slotBlockVars) { @@ -96,7 +97,7 @@ export function* generateVSlot( ? 'v-slot:'.length : 0 ), - ctx.codeFeatures.completion, + codeFeatures.completion, ]; yield `'/* empty slot name completion */]${endOfLine}`; } @@ -106,7 +107,6 @@ export function* generateVSlot( function* generateSlotParameters( options: TemplateCodegenOptions, - ctx: TemplateCodegenContext, ast: ts.SourceFile, exp: CompilerDOM.SimpleExpressionNode, slotVar: string, @@ -152,7 +152,7 @@ function* generateSlotParameters( yield* wrapWith( exp.loc.start.offset, exp.loc.end.offset, - ctx.codeFeatures.verification, + codeFeatures.verification, `(`, ...types.flatMap(type => type ? [`_: `, type, `, `] : `_, `), `) => [] as any`, @@ -165,7 +165,7 @@ function* generateSlotParameters( ast.text.slice(start, end), 'template', startOffset + start, - ctx.codeFeatures.all, + codeFeatures.all, ]; } } diff --git a/packages/language-core/lib/plugins/vue-tsx.ts b/packages/language-core/lib/plugins/vue-tsx.ts index 8f8315ebc1..1f4d8d7214 100644 --- a/packages/language-core/lib/plugins/vue-tsx.ts +++ b/packages/language-core/lib/plugins/vue-tsx.ts @@ -1,8 +1,10 @@ import { camelize, capitalize } from '@vue/shared'; import { computed } from 'alien-signals'; import * as path from 'path-browserify'; -import { generateScript } from '../codegen/script'; -import { generateTemplate } from '../codegen/template'; +import { generateScript, type ScriptCodegenOptions } from '../codegen/script'; +import { createScriptCodegenContext } from '../codegen/script/context'; +import { generateTemplate, type TemplateCodegenOptions } from '../codegen/template'; +import { createTemplateCodegenContext } from '../codegen/template/context'; import { parseScriptRanges } from '../parsers/scriptRanges'; import { parseScriptSetupRanges } from '../parsers/scriptSetupRanges'; import { parseVueCompilerOptions } from '../parsers/vueCompilerOptions'; @@ -179,8 +181,7 @@ function createTsx( return; } - const codes: Code[] = []; - const codegen = generateTemplate({ + const options: TemplateCodegenOptions = { ts, compilerOptions: ctx.compilerOptions, vueCompilerOptions: getResolvedOptions(), @@ -194,24 +195,26 @@ function createTsx( propsAssignName: getSetupPropsAssignName(), inheritAttrs: getSetupInheritAttrs(), selfComponentName: getComponentSelfName(), - }); + }; + const context = createTemplateCodegenContext(options, sfc.template.ast); + const codegen = generateTemplate(options, context); - let current = codegen.next(); - while (!current.done) { - const code = current.value; + const codes: Code[] = []; + for (const code of codegen) { + if (typeof code === 'object') { + code[3] = context.resolveCodeFeatures(code[3]); + } codes.push(code); - current = codegen.next(); } return { - ...current.value, + ...context, codes, }; }); const getGeneratedScript = computed(() => { - const codes: Code[] = []; - const codegen = generateScript({ + const options: ScriptCodegenOptions = { ts, compilerOptions: ctx.compilerOptions, vueCompilerOptions: getResolvedOptions(), @@ -223,18 +226,13 @@ function createTsx( templateCodegen: getGeneratedTemplate(), destructuredPropNames: getSetupDestructuredPropNames(), templateRefNames: getSetupTemplateRefNames(), - }); - - let current = codegen.next(); - while (!current.done) { - const code = current.value; - codes.push(code); - current = codegen.next(); - } + }; + const context = createScriptCodegenContext(options); + const codegen = generateScript(options, context); return { - ...current.value, - codes, + ...context, + codes: [...codegen], }; }); From 0ceba6ec40aee6581f6c828513d388d5970ebf0b Mon Sep 17 00:00:00 2001 From: KazariEX Date: Mon, 28 Jul 2025 02:43:59 +0800 Subject: [PATCH 34/85] fix(language-core): enable navigation code feature on directive modifiers --- packages/language-core/lib/codegen/codeFeatures.ts | 5 ----- .../language-core/lib/codegen/template/elementDirectives.ts | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/language-core/lib/codegen/codeFeatures.ts b/packages/language-core/lib/codegen/codeFeatures.ts index 72d4f896ad..2d576b926e 100644 --- a/packages/language-core/lib/codegen/codeFeatures.ts +++ b/packages/language-core/lib/codegen/codeFeatures.ts @@ -52,11 +52,6 @@ const raw = { navigation: true, completion: true, }, - withoutHighlightAndNavigation: { - semantic: { shouldHighlight: () => false }, - verification: true, - completion: true, - }, withoutHighlightAndCompletion: { semantic: { shouldHighlight: () => false }, verification: true, diff --git a/packages/language-core/lib/codegen/template/elementDirectives.ts b/packages/language-core/lib/codegen/template/elementDirectives.ts index 9deaa1491e..2dce0a93e6 100644 --- a/packages/language-core/lib/codegen/template/elementDirectives.ts +++ b/packages/language-core/lib/codegen/template/elementDirectives.ts @@ -144,7 +144,7 @@ export function* generateModifiers( ctx, mod.content, mod.loc.start.offset, - codeFeatures.withoutHighlightAndNavigation, + codeFeatures.withoutHighlight, ); yield `: true, `; } From 053aa3c4302904bd2c2bfe02a9b79edc58e65547 Mon Sep 17 00:00:00 2001 From: KazariEX Date: Mon, 28 Jul 2025 03:07:00 +0800 Subject: [PATCH 35/85] refactor(language-core): re-export `createTemplateCodegenContext` --- packages/language-core/lib/codegen/script/context.ts | 7 ------- packages/language-core/lib/codegen/script/index.ts | 6 ++++-- packages/language-core/lib/codegen/template/index.ts | 2 ++ packages/language-core/lib/plugins/vue-tsx.ts | 6 ++---- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/language-core/lib/codegen/script/context.ts b/packages/language-core/lib/codegen/script/context.ts index 51a1375e6d..844f8b44e9 100644 --- a/packages/language-core/lib/codegen/script/context.ts +++ b/packages/language-core/lib/codegen/script/context.ts @@ -2,13 +2,6 @@ import type { InlayHintInfo } from '../inlayHints'; import { getLocalTypesGenerator } from '../localTypes'; import type { ScriptCodegenOptions } from './index'; -export interface HelperType { - name: string; - used?: boolean; - generated?: boolean; - code: string; -} - export type ScriptCodegenContext = ReturnType; export function createScriptCodegenContext(options: ScriptCodegenOptions) { diff --git a/packages/language-core/lib/codegen/script/index.ts b/packages/language-core/lib/codegen/script/index.ts index c1c374c543..69cf33c303 100644 --- a/packages/language-core/lib/codegen/script/index.ts +++ b/packages/language-core/lib/codegen/script/index.ts @@ -7,11 +7,13 @@ import { codeFeatures } from '../codeFeatures'; import type { TemplateCodegenContext } from '../template/context'; import { endOfLine, generateSfcBlockSection, newLine } from '../utils'; import { generateComponentSelf } from './componentSelf'; -import { createScriptCodegenContext, type ScriptCodegenContext } from './context'; +import { type ScriptCodegenContext } from './context'; import { generateScriptSetup, generateScriptSetupImports } from './scriptSetup'; import { generateSrc } from './src'; import { generateTemplate } from './template'; +export * from './context'; + export interface ScriptCodegenOptions { ts: typeof ts; compilerOptions: ts.CompilerOptions; @@ -28,7 +30,7 @@ export interface ScriptCodegenOptions { export function* generateScript( options: ScriptCodegenOptions, - ctx: ScriptCodegenContext = createScriptCodegenContext(options), + ctx: ScriptCodegenContext, ): Generator { yield* generateGlobalTypesPath(options); diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index ace26767bb..e21ae2f952 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -10,6 +10,8 @@ import { generateObjectProperty } from './objectProperty'; import { generateStyleScopedClassReferences } from './styleScopedClasses'; import { generateTemplateChild, getVForNode } from './templateChild'; +export * from './context'; + export interface TemplateCodegenOptions { ts: typeof ts; compilerOptions: ts.CompilerOptions; diff --git a/packages/language-core/lib/plugins/vue-tsx.ts b/packages/language-core/lib/plugins/vue-tsx.ts index 1f4d8d7214..eaad1aca98 100644 --- a/packages/language-core/lib/plugins/vue-tsx.ts +++ b/packages/language-core/lib/plugins/vue-tsx.ts @@ -1,10 +1,8 @@ import { camelize, capitalize } from '@vue/shared'; import { computed } from 'alien-signals'; import * as path from 'path-browserify'; -import { generateScript, type ScriptCodegenOptions } from '../codegen/script'; -import { createScriptCodegenContext } from '../codegen/script/context'; -import { generateTemplate, type TemplateCodegenOptions } from '../codegen/template'; -import { createTemplateCodegenContext } from '../codegen/template/context'; +import { createScriptCodegenContext, generateScript, type ScriptCodegenOptions } from '../codegen/script'; +import { createTemplateCodegenContext, generateTemplate, type TemplateCodegenOptions } from '../codegen/template'; import { parseScriptRanges } from '../parsers/scriptRanges'; import { parseScriptSetupRanges } from '../parsers/scriptSetupRanges'; import { parseVueCompilerOptions } from '../parsers/vueCompilerOptions'; From 5a79d397f3ba76aaa079f38fcbf2a4eb458422d4 Mon Sep 17 00:00:00 2001 From: KazariEX Date: Mon, 28 Jul 2025 20:21:17 +0800 Subject: [PATCH 36/85] chore: split changelogs --- CHANGELOG.md | 4826 +----------------------------------- changelogs/CHANGELOG-v0.md | 2616 +++++++++++++++++++ changelogs/CHANGELOG-v1.md | 1154 +++++++++ changelogs/CHANGELOG-v2.md | 1053 ++++++++ 4 files changed, 4830 insertions(+), 4819 deletions(-) create mode 100644 changelogs/CHANGELOG-v0.md create mode 100644 changelogs/CHANGELOG-v1.md create mode 100644 changelogs/CHANGELOG-v2.md diff --git a/CHANGELOG.md b/CHANGELOG.md index bddba05d3e..27c46883d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -162,4828 +162,16 @@ - refactor(vscode): remove split editor feature (#5446) - refactor(vscode): rename configuration keys from `complete` to `suggest` for clarity -## 2.2.10 official (2025-04-22) +## Previous Changelogs -## Bug Fixes +### 2.x.x (2024/3/2 - 2025-04-22) -- fix(language-core): generate condition guards for model events (#5225) - Thanks to @KazariEX! -- fix(language-core): prevent global types generation in declaration files (#5239) - Thanks to @KazariEX! -- fix(language-core): prevent eager inference of slot props from generics (#5247) - Thanks to @KazariEX! -- fix(typescript-plugin): prevent highlighting native element tags with same name as components (#5253) - Thanks to @KazariEX! - -## 2.2.8 official, 2.2.9 insiders (2025-03-02) - -### Bug Fixes - -- revert "fix(language-core): validate `v-model` variable against model type" - -## 2.2.6 official, 2.2.7 insiders (2025-03-01) - -### Features - -- feat(language-core): infer prop JSDoc from `defineModel`'s leading comments (#5211) - Thanks to @KazariEX! - -### Bug Fixes - -- fix(language-core): map camelized prop name correctly (#5207) - Thanks to @KazariEX! -- fix(component-meta): resolve `defineModel` options to collect `default` value (#5209) - Thanks to @KazariEX! -- fix(language-core): avoid duplicate generation of `defineExpose`'s codes - Thanks to @KazariEX! -- fix(language-core): generate camelized prop name for `defineModel` (#5213) - Thanks to @KazariEX! -- fix(language-core): validate `v-model` variable against model type (#5214) - Thanks to @KazariEX! -- fix(language-core): use keywords instead of semicolons to separate script sections (#5217) - Thanks to @KazariEX! - -### Other Changes - -- ci: auto close issues with `can't reproduce` label - Thanks to @KazariEX! -- refactor(language-core): defer the calculation of `linkedCodeMappings` offsets (#5220) - Thanks to @KazariEX! - -## 2.2.4 official, 2.2.5 insiders (2025-02-22) - -### Features - -- feat(language-service): map sfc compiler errors outside the template inner content (#5045) - Thanks to @KazariEX! -- feat(language-core): introduce options to control type inference of `$attrs`, `$el`, `$refs` and `$slots` (#5135) - Thanks to @KazariEX! -- feat(language-core): enhance single root nodes collection (#4819) - Thanks to @KazariEX! - -### Bug Fixes - -- fix(language-core): move `generateSfcBlockSection` to the end to fix missing comma errors (#5184) - Thanks to @zhiyuanzmj! -- fix(language-core): handle edge case of default slot name mismatch - Thanks to @KazariEX! -- fix(language-core): combine dollar variable keys from the upper level interface - Thanks to @KazariEX! -- fix(language-core): hoist the variables that may cause `TS4081` (#5192) - Thanks to @KazariEX! -- fix(language-core): adjust regex match for `@vue-generic` to improve offset calculation (#5193) - Thanks to @Gehbt! -- fix(language-core): correct codegen of native element refs - Thanks to @KazariEX! -- fix(language-core): ignore latex block content (#5151) - Thanks to @KazariEX! -- fix(language-core): do not emit `undefined` for model with default value (#5198) - Thanks to @RylanBueckert-Broadsign! -- fix(language-service): typescript-semantic renaming first in style blocks (#4685) - Thanks to @KazariEX! -- fix(typescript-plugin): prevent removed components from appearing in the completion list - Thanks to @KazariEX! - -### Other Changes - -- refactor(language-core): drop invalid `v-scope` implemention - Thanks to @KazariEX! -- refactor(language-core): improve type declaration of `v-for` - Thanks to @KazariEX! -- test: enable `declaration` to track more errors - Thanks to @KazariEX! -- refactor(language-core): remove semantic highlight of style module names - Thanks to @KazariEX! -- chore(language-core): add docs for `@vue-expect-error` support (#5176) - Thanks to @machty! -- ci: upload extension as artifact for each commit - Thanks to @KazariEX! - -## 2.2.2 official, 2.2.3 insiders (2025-02-15) - -### Features - -- feat(language-core): navigation support for `$attrs`, `$slots`, `$refs` and `$el` in the template (#5056) - Thanks to @KazariEX! -- feat(language-service): support global directives completion (#4989) - Thanks to @KazariEX! -- feat(language-core): type support of `useAttrs` (#5106) - Thanks to @KazariEX! -- feat(language-core): add options for fine-grained configuration of `strictTemplates` (#5138) -- feat(language-service): display deprecated info of props in completion (#5134) - Thanks to @KazariEX! -- feat(component-meta): collect destructured props defaults (#5101) - Thanks to @Akryum! -- feat(language-core): add `checkUnknownDirectives` option (#5141) - Thanks to @KazariEX! -- feat(language-core): support ` From 56f12671bf617f6994218d5c9165efbf9610f11f Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 20 Aug 2025 12:46:50 +0800 Subject: [PATCH 59/85] fix(language-core): remove the non-strict `configFileName` default value (#5606) --- packages/language-core/lib/utils/ts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-core/lib/utils/ts.ts b/packages/language-core/lib/utils/ts.ts index 5c26a694fb..2ab88ee0ab 100644 --- a/packages/language-core/lib/utils/ts.ts +++ b/packages/language-core/lib/utils/ts.ts @@ -17,7 +17,7 @@ export function createParsedCommandLineByJson( }, rootDir: string, json: any, - configFileName = rootDir + '/jsconfig.json', + configFileName?: string, ): ParsedCommandLine { const proxyHost = proxyParseConfigHostForExtendConfigPaths(parseConfigHost); ts.parseJsonConfigFileContent(json, proxyHost.host, rootDir, {}, configFileName); From b3a39de86d46d96dc35e8b0e6f2049724b041c7a Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 20 Aug 2025 14:21:30 +0800 Subject: [PATCH 60/85] fix(language-core): don't look for input files during evaluation of vueCompilerOptions close #5598 --- packages/component-meta/lib/base.ts | 53 +++++++-- packages/language-core/lib/utils/ts.ts | 147 +++++++++---------------- 2 files changed, 99 insertions(+), 101 deletions(-) diff --git a/packages/component-meta/lib/base.ts b/packages/component-meta/lib/base.ts index 98856c2cc6..b27ac10ec8 100644 --- a/packages/component-meta/lib/base.ts +++ b/packages/component-meta/lib/base.ts @@ -27,7 +27,24 @@ export function createCheckerByJsonConfigBase( rootDir = rootDir.replace(windowsPathReg, '/'); return baseCreate( ts, - () => vue.createParsedCommandLineByJson(ts, ts.sys, rootDir, json), + () => { + const commandLine = vue.createParsedCommandLineByJson(ts, ts.sys, rootDir, json); + const { fileNames } = ts.parseJsonConfigFileContent( + json, + ts.sys, + rootDir, + {}, + undefined, + undefined, + vue.getAllExtensions(commandLine.vueOptions) + .map(extension => ({ + extension: extension.slice(1), + isMixedContent: true, + scriptKind: ts.ScriptKind.Deferred, + })), + ); + return [commandLine, fileNames]; + }, checkerOptions, rootDir, path.join(rootDir, 'jsconfig.json.global.vue'), @@ -42,25 +59,45 @@ export function createCheckerBase( tsconfig = tsconfig.replace(windowsPathReg, '/'); return baseCreate( ts, - () => vue.createParsedCommandLine(ts, ts.sys, tsconfig), + () => { + const commandLine = vue.createParsedCommandLine(ts, ts.sys, tsconfig); + const { fileNames } = ts.parseJsonSourceFileConfigFileContent( + ts.readJsonConfigFile(tsconfig, ts.sys.readFile), + ts.sys, + path.dirname(tsconfig), + {}, + tsconfig, + undefined, + vue.getAllExtensions(commandLine.vueOptions) + .map(extension => ({ + extension: extension.slice(1), + isMixedContent: true, + scriptKind: ts.ScriptKind.Deferred, + })), + ); + return [commandLine, fileNames]; + }, checkerOptions, path.dirname(tsconfig), tsconfig + '.global.vue', ); } -export function baseCreate( +function baseCreate( ts: typeof import('typescript'), - getCommandLine: () => vue.ParsedCommandLine, + getConfigAndFiles: () => [ + commandLine: vue.ParsedCommandLine, + fileNames: string[], + ], checkerOptions: MetaCheckerOptions, rootPath: string, globalComponentName: string, ) { - let commandLine = getCommandLine(); + let [commandLine, _fileNames] = getConfigAndFiles(); /** * Used to lookup if a file is referenced. */ - let fileNames = new Set(commandLine.fileNames.map(path => path.replace(windowsPathReg, '/'))); + let fileNames = new Set(_fileNames.map(path => path.replace(windowsPathReg, '/'))); let projectVersion = 0; vue.writeGlobalTypes(commandLine.vueOptions, ts.sys.writeFile); @@ -172,8 +209,8 @@ export function baseCreate( projectVersion++; }, reload() { - commandLine = getCommandLine(); - fileNames = new Set(commandLine.fileNames.map(path => path.replace(windowsPathReg, '/'))); + [commandLine, _fileNames] = getConfigAndFiles(); + fileNames = new Set(_fileNames.map(path => path.replace(windowsPathReg, '/'))); this.clearCache(); }, clearCache() { diff --git a/packages/language-core/lib/utils/ts.ts b/packages/language-core/lib/utils/ts.ts index 2ab88ee0ab..ad116a047d 100644 --- a/packages/language-core/lib/utils/ts.ts +++ b/packages/language-core/lib/utils/ts.ts @@ -2,31 +2,41 @@ import { camelize, NOOP as noop } from '@vue/shared'; import { posix as path } from 'path-browserify'; import type * as ts from 'typescript'; import { generateGlobalTypes, getGlobalTypesFileName } from '../codegen/globalTypes'; -import { getAllExtensions } from '../languagePlugin'; import type { RawVueCompilerOptions, VueCompilerOptions, VueLanguagePlugin } from '../types'; import { hyphenateTag } from './shared'; -export type ParsedCommandLine = ts.ParsedCommandLine & { +interface ParseConfigHost extends Omit {} + +export interface ParsedCommandLine extends Omit { vueOptions: VueCompilerOptions; -}; +} export function createParsedCommandLineByJson( ts: typeof import('typescript'), - parseConfigHost: ts.ParseConfigHost & { - writeFile?(path: string, data: string): void; - }, + host: ParseConfigHost, rootDir: string, json: any, configFileName?: string, ): ParsedCommandLine { - const proxyHost = proxyParseConfigHostForExtendConfigPaths(parseConfigHost); - ts.parseJsonConfigFileContent(json, proxyHost.host, rootDir, {}, configFileName); - - const resolver = new CompilerOptionsResolver(parseConfigHost.fileExists); + const extendedPaths = new Set(); + const proxyHost = { + ...host, + readFile(fileName: string) { + if (!fileName.endsWith('/package.json')) { + extendedPaths.add(fileName); + } + return host.readFile(fileName); + }, + readDirectory() { + return []; + }, + }; + const parsed = ts.parseJsonConfigFileContent(json, proxyHost, rootDir, {}, configFileName); + const resolver = new CompilerOptionsResolver(host.fileExists); - for (const extendPath of proxyHost.extendConfigPaths.reverse()) { + for (const extendPath of [...extendedPaths].reverse()) { try { - const configFile = ts.readJsonConfigFile(extendPath, proxyHost.host.readFile); + const configFile = ts.readJsonConfigFile(extendPath, host.readFile); const obj = ts.convertToObject(configFile, []); const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {}; resolver.addConfig(rawOptions, path.dirname(configFile.fileName)); @@ -37,49 +47,44 @@ export function createParsedCommandLineByJson( // ensure the rootDir is added to the config roots resolver.addConfig({}, rootDir); - const resolvedVueOptions = resolver.build(); - const parsed = ts.parseJsonConfigFileContent( - json, - proxyHost.host, - rootDir, - {}, - configFileName, - undefined, - getAllExtensions(resolvedVueOptions) - .map(extension => ({ - extension: extension.slice(1), - isMixedContent: true, - scriptKind: ts.ScriptKind.Deferred, - })), - ); - - // fix https://github.com/vuejs/language-tools/issues/1786 - // https://github.com/microsoft/TypeScript/issues/30457 - // patching ts server broke with outDir + rootDir + composite/incremental - parsed.options.outDir = undefined; - return { ...parsed, - vueOptions: resolvedVueOptions, + vueOptions: resolver.build(), }; } export function createParsedCommandLine( ts: typeof import('typescript'), - parseConfigHost: ts.ParseConfigHost, - tsConfigPath: string, + host: ParseConfigHost, + configFileName: string, ): ParsedCommandLine { try { - const rootDir = path.dirname(tsConfigPath); - const proxyHost = proxyParseConfigHostForExtendConfigPaths(parseConfigHost); - const config = ts.readJsonConfigFile(tsConfigPath, proxyHost.host.readFile); - ts.parseJsonSourceFileConfigFileContent(config, proxyHost.host, rootDir, {}, tsConfigPath); - - const resolver = new CompilerOptionsResolver(parseConfigHost.fileExists); + const extendedPaths = new Set(); + const proxyHost = { + ...host, + readFile(fileName: string) { + if (!fileName.endsWith('/package.json')) { + extendedPaths.add(fileName); + } + return host.readFile(fileName); + }, + readDirectory() { + return []; + }, + }; + const config = ts.readJsonConfigFile(configFileName, proxyHost.readFile); + const parsed = ts.parseJsonSourceFileConfigFileContent( + config, + proxyHost, + path.dirname(configFileName), + {}, + configFileName, + ); + const resolver = new CompilerOptionsResolver(host.fileExists); - for (const extendPath of proxyHost.extendConfigPaths.reverse()) { + for (const extendPath of [...extendedPaths].reverse()) { try { - const configFile = ts.readJsonConfigFile(extendPath, proxyHost.host.readFile); + const configFile = ts.readJsonConfigFile(extendPath, host.readFile); const obj = ts.convertToObject(configFile, []); const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {}; resolver.addConfig(rawOptions, path.dirname(configFile.fileName)); @@ -87,61 +92,17 @@ export function createParsedCommandLine( catch {} } - const resolvedVueOptions = resolver.build(); - const parsed = ts.parseJsonSourceFileConfigFileContent( - config, - proxyHost.host, - path.dirname(tsConfigPath), - {}, - tsConfigPath, - undefined, - getAllExtensions(resolvedVueOptions) - .map(extension => ({ - extension: extension.slice(1), - isMixedContent: true, - scriptKind: ts.ScriptKind.Deferred, - })), - ); - - // fix https://github.com/vuejs/language-tools/issues/1786 - // https://github.com/microsoft/TypeScript/issues/30457 - // patching ts server broke with outDir + rootDir + composite/incremental - parsed.options.outDir = undefined; - return { ...parsed, - vueOptions: resolvedVueOptions, - }; - } - catch { - // console.warn('Failed to resolve tsconfig path:', tsConfigPath, err); - return { - fileNames: [], - options: {}, - vueOptions: getDefaultCompilerOptions(), - errors: [], + vueOptions: resolver.build(), }; } -} + catch {} -function proxyParseConfigHostForExtendConfigPaths(parseConfigHost: ts.ParseConfigHost) { - const extendConfigPaths: string[] = []; - const host = new Proxy(parseConfigHost, { - get(target, key) { - if (key === 'readFile') { - return (fileName: string) => { - if (!fileName.endsWith('/package.json') && !extendConfigPaths.includes(fileName)) { - extendConfigPaths.push(fileName); - } - return target.readFile(fileName); - }; - } - return target[key as keyof typeof target]; - }, - }); return { - host, - extendConfigPaths, + options: {}, + errors: [], + vueOptions: getDefaultCompilerOptions(), }; } From c742a1c8d29365c7b103ba22716aa8c4a5980537 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 20 Aug 2025 14:44:22 +0800 Subject: [PATCH 61/85] fix(vscode): Improved reliability of handling extension activation contention (#5588) --- extensions/vscode/index.ts | 9 ++------- extensions/vscode/languages/stub.json | 1 - extensions/vscode/package.json | 9 +-------- 3 files changed, 3 insertions(+), 16 deletions(-) delete mode 100644 extensions/vscode/languages/stub.json diff --git a/extensions/vscode/index.ts b/extensions/vscode/index.ts index a98f7ad987..2691282006 100644 --- a/extensions/vscode/index.ts +++ b/extensions/vscode/index.ts @@ -58,7 +58,7 @@ export const { activate, deactivate } = defineExtension(() => { if (needRestart) { vscode.window.showInformationMessage( - 'Please restart the extension host to activate Vue support in remote environments.', + 'Please restart the extension host to activate Vue support.', 'Restart Extension Host', 'Reload Window', ).then(action => { @@ -230,12 +230,7 @@ try { } if (tsExtension.isActive) { - if (!vscode.env.remoteName) { - vscode.commands.executeCommand('workbench.action.restartExtensionHost'); - } - else { - needRestart = true; - } + needRestart = true; } } catch {} diff --git a/extensions/vscode/languages/stub.json b/extensions/vscode/languages/stub.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/extensions/vscode/languages/stub.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 8668a57acd..7650cbab1f 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -22,9 +22,7 @@ "vscode": "^1.88.0" }, "activationEvents": [ - "onLanguage:vue", - "onLanguage:markdown", - "onLanguage:html" + "onLanguage" ], "main": "./dist/extension.js", "browser": "./web.js", @@ -67,11 +65,6 @@ { "id": "jade", "configuration": "./languages/sfc-template-language-configuration.json" - }, - { - "id": "plaintext", - "configuration": "./languages/stub.json", - "//": "Make sure Vue extension is loaded immediately when VSCode starts." } ], "typescriptServerPlugins": [ From 83b8d8be8feca98ef19693c091bbc225bc4fed34 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 20 Aug 2025 14:57:44 +0800 Subject: [PATCH 62/85] refactor(vscode): hoist tsExtension check --- extensions/vscode/index.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/extensions/vscode/index.ts b/extensions/vscode/index.ts index 2691282006..5a6bcc57e0 100644 --- a/extensions/vscode/index.ts +++ b/extensions/vscode/index.ts @@ -172,9 +172,12 @@ function launch(context: vscode.ExtensionContext) { return client; } -try { +const tsExtension = vscode.extensions.getExtension('vscode.typescript-language-features')!; +if (tsExtension.isActive) { + needRestart = true; +} +else { const fs = require('node:fs'); - const tsExtension = vscode.extensions.getExtension('vscode.typescript-language-features')!; const readFileSync = fs.readFileSync; const extensionJsPath = require.resolve('./dist/extension.js', { paths: [tsExtension.extensionPath], @@ -228,9 +231,4 @@ try { const patchedModule = require(extensionJsPath); Object.assign(loadedModule.exports, patchedModule); } - - if (tsExtension.isActive) { - needRestart = true; - } } -catch {} From 4488f64f1e5ffc3858fe216677161b3adb26361c Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 20 Aug 2025 18:54:49 +0800 Subject: [PATCH 63/85] feat(lint): enable `eqeqeq` rule --- packages/typescript-plugin/index.ts | 4 ++-- tsslint.config.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/typescript-plugin/index.ts b/packages/typescript-plugin/index.ts index a098844a1c..08b4f9463d 100644 --- a/packages/typescript-plugin/index.ts +++ b/packages/typescript-plugin/index.ts @@ -72,11 +72,11 @@ export = createLanguageServicePlugin( projectService.logger.info('Vue: called handler processing ' + info.project.projectKind); const session = info.session; - if (session == undefined) { + if (!session) { projectService.logger.info('Vue: there is no session in info.'); return; } - if (session.addProtocolHandler == undefined) { + if (!session.addProtocolHandler) { // addProtocolHandler was introduced in TS 4.4 or 4.5 in 2021, see https://github.com/microsoft/TypeScript/issues/43893 projectService.logger.info('Vue: there is no addProtocolHandler method.'); return; diff --git a/tsslint.config.ts b/tsslint.config.ts index f4853121d2..39ef54c370 100644 --- a/tsslint.config.ts +++ b/tsslint.config.ts @@ -87,6 +87,7 @@ export default defineConfig({ // Project-specific rules ...await defineRules({ 'curly': true, + 'eqeqeq': true, 'no-unused-expressions': true, 'require-await': true, '@typescript-eslint/consistent-type-imports': [{ From 0ed384f9fbe67910cbf48c28f7d9f0cf2939f76c Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 20 Aug 2025 21:27:59 +0800 Subject: [PATCH 64/85] chore: update volar to 2.4.23 --- CHANGELOG.md | 2 + extensions/vscode/package.json | 2 +- packages/component-meta/package.json | 2 +- packages/language-core/package.json | 4 +- packages/language-plugin-pug/package.json | 2 +- packages/language-server/package.json | 4 +- packages/language-service/package.json | 6 +- packages/tsc/package.json | 2 +- packages/typescript-plugin/package.json | 2 +- pnpm-lock.yaml | 160 +++++++++++----------- 10 files changed, 94 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 210fc85a43..738b8ace05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 3.0.6 (2025-08-20) + ## 3.0.5 (2025-08-01) ### Features diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 7650cbab1f..4a5dcc127c 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -464,7 +464,7 @@ "@types/node": "^22.10.4", "@types/semver": "^7.5.3", "@types/vscode": "1.88.0", - "@volar/vscode": "2.4.22", + "@volar/vscode": "2.4.23", "@vscode/vsce": "^3.2.1", "@vue/compiler-sfc": "^3.5.0", "@vue/language-server": "3.0.5", diff --git a/packages/component-meta/package.json b/packages/component-meta/package.json index 2d221fe28a..f45f45c5a4 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.22", + "@volar/typescript": "2.4.23", "@vue/language-core": "3.0.5", "path-browserify": "^1.0.1", "vue-component-type-helpers": "3.0.5" diff --git a/packages/language-core/package.json b/packages/language-core/package.json index 2aac9a59c3..582df84a40 100644 --- a/packages/language-core/package.json +++ b/packages/language-core/package.json @@ -13,7 +13,7 @@ "directory": "packages/language-core" }, "dependencies": { - "@volar/language-core": "2.4.22", + "@volar/language-core": "2.4.23", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", @@ -26,7 +26,7 @@ "@types/node": "^22.10.4", "@types/path-browserify": "^1.0.1", "@types/picomatch": "^4.0.0", - "@volar/typescript": "2.4.22", + "@volar/typescript": "2.4.23", "@vue/compiler-sfc": "^3.5.0" }, "peerDependencies": { diff --git a/packages/language-plugin-pug/package.json b/packages/language-plugin-pug/package.json index e5c46cebac..6ad1968f59 100644 --- a/packages/language-plugin-pug/package.json +++ b/packages/language-plugin-pug/package.json @@ -13,7 +13,7 @@ "directory": "packages/language-plugin-pug" }, "dependencies": { - "@volar/source-map": "2.4.22", + "@volar/source-map": "2.4.23", "volar-service-pug": "0.0.65" }, "devDependencies": { diff --git a/packages/language-server/package.json b/packages/language-server/package.json index c32fe15bb7..f8da3a90a1 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -16,7 +16,7 @@ "directory": "packages/language-server" }, "dependencies": { - "@volar/language-server": "2.4.22", + "@volar/language-server": "2.4.23", "@vue/language-core": "3.0.5", "@vue/language-service": "3.0.5", "@vue/typescript-plugin": "3.0.5", @@ -27,6 +27,6 @@ }, "devDependencies": { "@typescript/server-harness": "latest", - "@volar/test-utils": "2.4.22" + "@volar/test-utils": "2.4.23" } } diff --git a/packages/language-service/package.json b/packages/language-service/package.json index aafc264dfd..6afbbc5f43 100644 --- a/packages/language-service/package.json +++ b/packages/language-service/package.json @@ -17,7 +17,7 @@ "update-html-data": "node ./scripts/update-html-data.js" }, "dependencies": { - "@volar/language-service": "2.4.22", + "@volar/language-service": "2.4.23", "@vue/language-core": "3.0.5", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1", @@ -34,8 +34,8 @@ "devDependencies": { "@types/node": "^22.10.4", "@types/path-browserify": "^1.0.1", - "@volar/kit": "2.4.22", - "@volar/typescript": "2.4.22", + "@volar/kit": "2.4.23", + "@volar/typescript": "2.4.23", "@vue/compiler-dom": "^3.5.0", "@vue/typescript-plugin": "3.0.5", "vscode-css-languageservice": "^6.3.1" diff --git a/packages/tsc/package.json b/packages/tsc/package.json index 60cd2d74b3..384e323b4c 100644 --- a/packages/tsc/package.json +++ b/packages/tsc/package.json @@ -20,7 +20,7 @@ "typescript": ">=5.0.0" }, "dependencies": { - "@volar/typescript": "2.4.22", + "@volar/typescript": "2.4.23", "@vue/language-core": "3.0.5" }, "devDependencies": { diff --git a/packages/typescript-plugin/package.json b/packages/typescript-plugin/package.json index 87fae1942a..ebdd41bea5 100644 --- a/packages/typescript-plugin/package.json +++ b/packages/typescript-plugin/package.json @@ -13,7 +13,7 @@ "directory": "packages/typescript-plugin" }, "dependencies": { - "@volar/typescript": "2.4.22", + "@volar/typescript": "2.4.23", "@vue/language-core": "3.0.5", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index abd6dc0465..6c5c72a3c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,8 +48,8 @@ importers: specifier: 1.88.0 version: 1.88.0 '@volar/vscode': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 '@vscode/vsce': specifier: ^3.2.1 version: 3.3.2 @@ -81,8 +81,8 @@ importers: packages/component-meta: dependencies: '@volar/typescript': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 '@vue/language-core': specifier: 3.0.5 version: link:../language-core @@ -108,8 +108,8 @@ importers: packages/language-core: dependencies: '@volar/language-core': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 '@vue/compiler-dom': specifier: ^3.5.0 version: 3.5.13 @@ -145,8 +145,8 @@ importers: specifier: ^4.0.0 version: 4.0.0 '@volar/typescript': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 '@vue/compiler-sfc': specifier: ^3.5.0 version: 3.5.13 @@ -154,8 +154,8 @@ importers: packages/language-plugin-pug: dependencies: '@volar/source-map': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 volar-service-pug: specifier: 0.0.65 version: 0.0.65 @@ -173,8 +173,8 @@ importers: packages/language-server: dependencies: '@volar/language-server': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 '@vue/language-core': specifier: 3.0.5 version: link:../language-core @@ -195,14 +195,14 @@ importers: specifier: latest version: 0.3.5 '@volar/test-utils': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 packages/language-service: dependencies: '@volar/language-service': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 '@vue/language-core': specifier: 3.0.5 version: link:../language-core @@ -214,25 +214,25 @@ importers: version: 1.0.1 volar-service-css: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.22) + version: 0.0.65(@volar/language-service@2.4.23) volar-service-emmet: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.22) + version: 0.0.65(@volar/language-service@2.4.23) volar-service-html: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.22) + version: 0.0.65(@volar/language-service@2.4.23) volar-service-json: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.22) + version: 0.0.65(@volar/language-service@2.4.23) volar-service-pug: specifier: 0.0.65 version: 0.0.65 volar-service-pug-beautify: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.22) + version: 0.0.65(@volar/language-service@2.4.23) volar-service-typescript: specifier: 0.0.65 - version: 0.0.65(@volar/language-service@2.4.22) + version: 0.0.65(@volar/language-service@2.4.23) vscode-html-languageservice: specifier: ^5.2.0 version: 5.4.0 @@ -247,11 +247,11 @@ importers: specifier: ^1.0.1 version: 1.0.3 '@volar/kit': - specifier: 2.4.22 - version: 2.4.22(typescript@5.9.2) + specifier: 2.4.23 + version: 2.4.23(typescript@5.9.2) '@volar/typescript': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 '@vue/compiler-dom': specifier: ^3.5.0 version: 3.5.13 @@ -265,8 +265,8 @@ importers: packages/tsc: dependencies: '@volar/typescript': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 '@vue/language-core': specifier: 3.0.5 version: link:../language-core @@ -281,8 +281,8 @@ importers: packages/typescript-plugin: dependencies: '@volar/typescript': - specifier: 2.4.22 - version: 2.4.22 + specifier: 2.4.23 + version: 2.4.23 '@vue/language-core': specifier: 3.0.5 version: link:../language-core @@ -1290,34 +1290,34 @@ packages: '@vitest/utils@3.1.3': resolution: {integrity: sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==} - '@volar/kit@2.4.22': - resolution: {integrity: sha512-o2LhNb2PLCUJ6v2XSqN7m+pJt+SE0QW1U2E52jnS8yZ03ohcGOOuFJdH1VlZgCBk0RlwO4xp0OaDoTtyTvMTrw==} + '@volar/kit@2.4.23': + resolution: {integrity: sha512-YuUIzo9zwC2IkN7FStIcVl1YS9w5vkSFEZfPvnu0IbIMaR9WHhc9ZxvlT+91vrcSoRY469H2jwbrGqpG7m1KaQ==} peerDependencies: typescript: '*' - '@volar/language-core@2.4.22': - resolution: {integrity: sha512-gp4M7Di5KgNyIyO903wTClYBavRt6UyFNpc5LWfyZr1lBsTUY+QrVZfmbNF2aCyfklBOVk9YC4p+zkwoyT7ECg==} + '@volar/language-core@2.4.23': + resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} '@volar/language-hub@0.0.1': resolution: {integrity: sha512-2eOUnlMKTyjtlXIVd+6pfAtcuVugxCOgpNgcLWmlPuncQTG5C1E5mTDL/PUMw7aEnLySUOtMTIp8lT3vk/7w6Q==} - '@volar/language-server@2.4.22': - resolution: {integrity: sha512-THIGWcQsEJKZU7SjVKPcy4MIamX4qpusKErj33ru7fi2WcD+FmFjYY/F2LIk/C15xEcb34JT1uZBlbO2dfzYSQ==} + '@volar/language-server@2.4.23': + resolution: {integrity: sha512-k0iO+tybMGMMyrNdWOxgFkP0XJTdbH0w+WZlM54RzJU3WZSjHEupwL30klpM7ep4FO6qyQa03h+VcGHD4Q8gEg==} - '@volar/language-service@2.4.22': - resolution: {integrity: sha512-8TmvOf/6uqaJMBVQIP9kgVpRzMrqLI3nCmWuSIPAldlmwjZTOiN17GA4AL4sTFJUg61xCSyMQWbProNFQ88yew==} + '@volar/language-service@2.4.23': + resolution: {integrity: sha512-h5mU9DZ/6u3LCB9xomJtorNG6awBNnk9VuCioGsp6UtFiM8amvS5FcsaC3dabdL9zO0z+Gq9vIEMb/5u9K6jGQ==} - '@volar/source-map@2.4.22': - resolution: {integrity: sha512-L2nVr/1vei0xKRgO2tYVXtJYd09HTRjaZi418e85Q+QdbbqA8h7bBjfNyPPSsjnrOO4l4kaAo78c8SQUAdHvgA==} + '@volar/source-map@2.4.23': + resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} - '@volar/test-utils@2.4.22': - resolution: {integrity: sha512-hSImBcIJkKqrE8txM+e4qKCBAwAU7PMlSNqitljuBwE1kztTpv4Szaaa7d5BnpIGlNsXdot0hYhpR8y3+BGJzg==} + '@volar/test-utils@2.4.23': + resolution: {integrity: sha512-ZF3Ejtv6mfwXHuR6aZGsh1ksvAdSZUfWKGacXa9S+Mnc3MpSPgxzHN3PwdXGt26HfRDWGuykJc8y6lVs20dDKw==} - '@volar/typescript@2.4.22': - resolution: {integrity: sha512-6ZczlJW1/GWTrNnkmZxJp4qyBt/SGVlcTuCWpI5zLrdPdCZsj66Aff9ZsfFaT3TyjG8zVYgBMYPuCm/eRkpcpQ==} + '@volar/typescript@2.4.23': + resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} - '@volar/vscode@2.4.22': - resolution: {integrity: sha512-E5egfejA+3YvPqd37IeC676mJOgalQ/aAzGxVDnChdhR5+U5BtWAcILgIAf+T4/Cq12jPOV7KCn07mZ24DDp9w==} + '@volar/vscode@2.4.23': + resolution: {integrity: sha512-rLghFmq5+pEpEkuMZtnFUVZFagYxas9q9U4U8ErcJ5Qv4bZ7AZ2oj5Wz4gx0O79ZgdOblDZIgttokw2Pc+VtnQ==} '@vscode/emmet-helper@2.11.0': resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} @@ -4678,9 +4678,9 @@ snapshots: '@clack/prompts': 0.8.2 '@tsslint/config': 2.0.1(typescript@5.9.2) '@tsslint/core': 2.0.1 - '@volar/language-core': 2.4.22 + '@volar/language-core': 2.4.23 '@volar/language-hub': 0.0.1 - '@volar/typescript': 2.4.22 + '@volar/typescript': 2.4.23 glob: 10.4.5 json5: 2.2.3 typescript: 5.9.2 @@ -4881,26 +4881,26 @@ snapshots: loupe: 3.1.3 tinyrainbow: 2.0.0 - '@volar/kit@2.4.22(typescript@5.9.2)': + '@volar/kit@2.4.23(typescript@5.9.2)': dependencies: - '@volar/language-service': 2.4.22 - '@volar/typescript': 2.4.22 + '@volar/language-service': 2.4.23 + '@volar/typescript': 2.4.23 typesafe-path: 0.2.2 typescript: 5.9.2 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/language-core@2.4.22': + '@volar/language-core@2.4.23': dependencies: - '@volar/source-map': 2.4.22 + '@volar/source-map': 2.4.23 '@volar/language-hub@0.0.1': {} - '@volar/language-server@2.4.22': + '@volar/language-server@2.4.23': dependencies: - '@volar/language-core': 2.4.22 - '@volar/language-service': 2.4.22 - '@volar/typescript': 2.4.22 + '@volar/language-core': 2.4.23 + '@volar/language-service': 2.4.23 + '@volar/typescript': 2.4.23 path-browserify: 1.0.1 request-light: 0.7.0 vscode-languageserver: 9.0.1 @@ -4908,29 +4908,29 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/language-service@2.4.22': + '@volar/language-service@2.4.23': dependencies: - '@volar/language-core': 2.4.22 + '@volar/language-core': 2.4.23 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/source-map@2.4.22': {} + '@volar/source-map@2.4.23': {} - '@volar/test-utils@2.4.22': + '@volar/test-utils@2.4.23': dependencies: - '@volar/language-core': 2.4.22 - '@volar/language-server': 2.4.22 + '@volar/language-core': 2.4.23 + '@volar/language-server': 2.4.23 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/typescript@2.4.22': + '@volar/typescript@2.4.23': dependencies: - '@volar/language-core': 2.4.22 + '@volar/language-core': 2.4.23 path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@volar/vscode@2.4.22': + '@volar/vscode@2.4.23': dependencies: path-browserify: 1.0.1 vscode-languageclient: 9.0.1 @@ -7312,55 +7312,55 @@ snapshots: - supports-color - terser - volar-service-css@0.0.65(@volar/language-service@2.4.22): + volar-service-css@0.0.65(@volar/language-service@2.4.23): dependencies: vscode-css-languageservice: 6.3.5 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.22 + '@volar/language-service': 2.4.23 - volar-service-emmet@0.0.65(@volar/language-service@2.4.22): + volar-service-emmet@0.0.65(@volar/language-service@2.4.23): dependencies: '@emmetio/css-parser': https://codeload.github.com/ramya-rao-a/css-parser/tar.gz/370c480ac103bd17c7bcfb34bf5d577dc40d3660 '@emmetio/html-matcher': 1.3.0 '@vscode/emmet-helper': 2.11.0 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.22 + '@volar/language-service': 2.4.23 - volar-service-html@0.0.65(@volar/language-service@2.4.22): + volar-service-html@0.0.65(@volar/language-service@2.4.23): dependencies: vscode-html-languageservice: 5.4.0 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.22 + '@volar/language-service': 2.4.23 - volar-service-json@0.0.65(@volar/language-service@2.4.22): + volar-service-json@0.0.65(@volar/language-service@2.4.23): dependencies: vscode-json-languageservice: 5.6.1 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.22 + '@volar/language-service': 2.4.23 - volar-service-pug-beautify@0.0.65(@volar/language-service@2.4.22): + volar-service-pug-beautify@0.0.65(@volar/language-service@2.4.23): dependencies: '@johnsoncodehk/pug-beautify': 0.2.2 optionalDependencies: - '@volar/language-service': 2.4.22 + '@volar/language-service': 2.4.23 volar-service-pug@0.0.65: dependencies: - '@volar/language-service': 2.4.22 + '@volar/language-service': 2.4.23 muggle-string: 0.4.1 pug-lexer: 5.0.1 pug-parser: 6.0.0 - volar-service-html: 0.0.65(@volar/language-service@2.4.22) + volar-service-html: 0.0.65(@volar/language-service@2.4.23) vscode-html-languageservice: 5.4.0 vscode-languageserver-textdocument: 1.0.12 - volar-service-typescript@0.0.65(@volar/language-service@2.4.22): + volar-service-typescript@0.0.65(@volar/language-service@2.4.23): dependencies: path-browserify: 1.0.1 semver: 7.7.2 @@ -7369,7 +7369,7 @@ snapshots: vscode-nls: 5.2.0 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.22 + '@volar/language-service': 2.4.23 vscode-css-languageservice@6.3.5: dependencies: From bec4f08a15b6d933974c4c411d712506066a73cb Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 20 Aug 2025 23:02:23 +0800 Subject: [PATCH 65/85] v3.0.6 (#5608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 山吹色御守 <85992002+KazariEX@users.noreply.github.com> --- CHANGELOG.md | 20 ++++++ extensions/vscode/index.ts | 2 +- extensions/vscode/lib/welcome.ts | 66 +++++++++++++++++--- extensions/vscode/package.json | 6 +- lerna.json | 2 +- packages/component-meta/package.json | 6 +- packages/component-type-helpers/package.json | 2 +- packages/language-core/package.json | 2 +- packages/language-plugin-pug/package.json | 4 +- packages/language-server/package.json | 8 +-- packages/language-service/package.json | 6 +- packages/tsc/package.json | 4 +- packages/typescript-plugin/package.json | 4 +- pnpm-lock.yaml | 26 ++++---- test-workspace/package.json | 4 +- 15 files changed, 115 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 738b8ace05..d79290bccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ ## 3.0.6 (2025-08-20) +### Bug Fixes + +- fix(language-core): wrap `:class` expression with parens - Thanks to @KazariEX! +- fix(vscode): revert Vue 2 versions in `target` option (#5583) - Thanks to @gxres042! +- fix(language-service): skip document highlight from tsserver within element tags (#5584) - Thanks to @KazariEX! +- fix(component-meta): re-export `vue-component-type-helpers` to `lib/helpers` (#5600) +- fix(language-core): remove the non-strict `configFileName` default value (#5606) +- fix(language-core): don't look for input files during evaluation of vueCompilerOptions (#5598) +- fix(vscode): Improved reliability of handling extension activation contention (#5588) +- chore: update volar to 2.4.23 + - Support `js/ts.hover.maximumLength` and `typescript.experimental.expandableHover` (#5577) + +### Other Changes + +- feat(lint): update tsslint config (#5602) +- refactor(language-core): generate setup returns on demand - Thanks to @KazariEX! +- chore(language-service): remove `exclude` config suggestion from global types error message (#5579) - Thanks to @Ciallo-Chiaki +- chore(vscode): update extension display name "Vue.js" (#5582) +- chore: update `vue-component-type-helpers` to current version (#5589) - Thanks to @kingyue737! + ## 3.0.5 (2025-08-01) ### Features diff --git a/extensions/vscode/index.ts b/extensions/vscode/index.ts index 5a6bcc57e0..2a2c8aee63 100644 --- a/extensions/vscode/index.ts +++ b/extensions/vscode/index.ts @@ -98,9 +98,9 @@ export const { activate, deactivate } = defineExtension(() => { activateAutoInsertion(selectors, client); activateDocumentDropEdit(selectors, client); - activateWelcome(); }, { immediate: true }); + activateWelcome(context); useCommand('vue.action.restartServer', async () => { await executeCommand('typescript.restartTsServer'); await client?.stop(); diff --git a/extensions/vscode/lib/welcome.ts b/extensions/vscode/lib/welcome.ts index a3b9be9e0f..782a6ef45c 100644 --- a/extensions/vscode/lib/welcome.ts +++ b/extensions/vscode/lib/welcome.ts @@ -1,9 +1,11 @@ -import { extensionContext, useCommand } from 'reactive-vscode'; +import { useCommand } from 'reactive-vscode'; import * as vscode from 'vscode'; +const welcomeVersion = '3.0.6'; + let panel: vscode.WebviewPanel | undefined; -export function activate() { +export function activate(context: vscode.ExtensionContext) { useCommand('vue.welcome', () => { if (panel) { panel.reveal(vscode.ViewColumn.One); @@ -17,12 +19,15 @@ export function activate() { { enableScripts: true }, ); - panel.webview.html = getWelcomeHtml(); + panel.webview.html = getWelcomeHtml(context); panel.webview.onDidReceiveMessage(message => { switch (message.command) { case 'verifySponsor': vscode.commands.executeCommand('vue.action.verify'); break; + case 'toggleShowUpdates': + context.globalState.update('vue.showUpdates', message.value); + break; } }); @@ -30,11 +35,18 @@ export function activate() { panel = undefined; }); }); -} -function getWelcomeHtml() { - const version = extensionContext.value?.extension.packageJSON.version; + if ( + context.globalState.get('vue.showUpdates', true) + && context.globalState.get('vue-welcome') !== welcomeVersion + ) { + context.globalState.update('vue-welcome', welcomeVersion); + vscode.commands.executeCommand('vue.welcome'); + } +} +function getWelcomeHtml(context: vscode.ExtensionContext) { + const version = context.extension.packageJSON.version; return /* HTML */ ` @@ -48,6 +60,9 @@ function getWelcomeHtml() { function verifySponsor() { vscode.postMessage({ command: 'verifySponsor' }); } + function toggleShowUpdates(value) { + vscode.postMessage({ command: 'toggleShowUpdates', value }); + }