diff --git a/packages/component-meta/src/base.ts b/packages/component-meta/src/base.ts index 82acc2c724..039b3694e1 100644 --- a/packages/component-meta/src/base.ts +++ b/packages/component-meta/src/base.ts @@ -153,8 +153,12 @@ export function baseCreate( host.getCompilationSettings(), vueCompilerOptions, ); - const project = vue.createTypeScriptProject(host, vueLanguages, vue.resolveCommonLanguageId); - const tsLsHost = createLanguageServiceHost(project.typescript!.projectHost, project.fileProvider, ts, ts.sys); + const fileNameResolutionHost = { + fileNameToId: (fileName: string) => fileName, + idToFileName: (id: string) => id, + }; + const project = vue.createTypeScriptProject(host, vueLanguages, fileNameResolutionHost.fileNameToId, vue.resolveCommonLanguageId); + const tsLsHost = createLanguageServiceHost(project.typescript!.projectHost, project.fileProvider, fileNameResolutionHost, ts, ts.sys); const tsLs = ts.createLanguageService(tsLsHost); decorateLanguageService(project.fileProvider, tsLs, false); @@ -297,7 +301,7 @@ ${vueCompilerOptions.target < 3 ? vue2TypeHelpersCode : typeHelpersCode} const printer = ts.createPrinter(checkerOptions.printer); const snapshot = host.getScriptSnapshot(componentPath)!; - const vueSourceFile = project.fileProvider.getSource(componentPath)?.root; + const vueSourceFile = project.fileProvider.getSourceFile(componentPath)?.root; const vueDefaults = vueSourceFile && exportName === 'default' ? (vueSourceFile instanceof vue.VueFile ? readVueComponentDefaultProps(vueSourceFile, printer, ts, vueCompilerOptions) : {}) : {}; diff --git a/packages/language-core/src/languageModule.ts b/packages/language-core/src/languageModule.ts index 975a88992b..5460f1d1ff 100644 --- a/packages/language-core/src/languageModule.ts +++ b/packages/language-core/src/languageModule.ts @@ -68,18 +68,15 @@ export function createVueLanguage( } return { - createVirtualFile(fileName, snapshot, languageId) { - if ( - (languageId && allowLanguageIds.has(languageId)) - || (!languageId && vueCompilerOptions.extensions.some(ext => fileName.endsWith(ext))) - ) { - if (fileRegistry.has(fileName)) { - const reusedVueFile = fileRegistry.get(fileName)!; + createVirtualFile(id, languageId, snapshot) { + if (allowLanguageIds.has(languageId)) { + if (fileRegistry.has(id)) { + const reusedVueFile = fileRegistry.get(id)!; reusedVueFile.update(snapshot); return reusedVueFile; } - const vueFile = new VueFile(fileName, languageId, snapshot, vueCompilerOptions, plugins, ts, codegenStack); - fileRegistry.set(fileName, vueFile); + const vueFile = new VueFile(id, languageId, snapshot, vueCompilerOptions, plugins, ts, codegenStack); + fileRegistry.set(id, vueFile); return vueFile; } }, diff --git a/packages/language-core/src/virtualFile/computedFiles.ts b/packages/language-core/src/virtualFile/computedFiles.ts index c6827ea754..d1f50250bb 100644 --- a/packages/language-core/src/virtualFile/computedFiles.ts +++ b/packages/language-core/src/virtualFile/computedFiles.ts @@ -1,10 +1,10 @@ import { VirtualFile, resolveCommonLanguageId } from '@volar/language-core'; import { buildMappings, buildStacks, toString } from '@volar/source-map'; +import { computed } from 'computeds'; import * as muggle from 'muggle-string'; import type * as ts from 'typescript/lib/tsserverlibrary'; import type { Sfc, SfcBlock, VueLanguagePlugin } from '../types'; import { VueEmbeddedFile } from './embeddedFile'; -import { computed } from 'computeds'; export function computedFiles( plugins: ReturnType[], @@ -50,8 +50,11 @@ export function computedFiles( for (const { file, snapshot, mappings, codegenStacks } of remain) { embeddedFiles.push({ - ...file, + id: file.fileName, languageId: resolveCommonLanguageId(file.fileName), + kind: file.kind, + capabilities: file.capabilities, + mirrorBehaviorMappings: file.mirrorBehaviorMappings, snapshot, mappings, codegenStacks, @@ -67,8 +70,11 @@ export function computedFiles( const { file, snapshot, mappings, codegenStacks } = remain[i]; if (!file.parentFileName) { embeddedFiles.push({ - ...file, + id: file.fileName, languageId: resolveCommonLanguageId(file.fileName), + kind: file.kind, + capabilities: file.capabilities, + mirrorBehaviorMappings: file.mirrorBehaviorMappings, snapshot, mappings, codegenStacks, @@ -80,8 +86,11 @@ export function computedFiles( const parent = findParentStructure(file.parentFileName, embeddedFiles); if (parent) { parent.embeddedFiles.push({ - ...file, + id: file.fileName, languageId: resolveCommonLanguageId(file.fileName), + kind: file.kind, + capabilities: file.capabilities, + mirrorBehaviorMappings: file.mirrorBehaviorMappings, snapshot, mappings, codegenStacks, @@ -92,12 +101,12 @@ export function computedFiles( } } } - function findParentStructure(fileName: string, current: VirtualFile[]): VirtualFile | undefined { + function findParentStructure(id: string, current: VirtualFile[]): VirtualFile | undefined { for (const child of current) { - if (child.fileName === fileName) { + if (child.id === id) { return child; } - let parent = findParentStructure(fileName, child.embeddedFiles); + let parent = findParentStructure(id, child.embeddedFiles); if (parent) { return parent; } diff --git a/packages/language-core/src/virtualFile/vueFile.ts b/packages/language-core/src/virtualFile/vueFile.ts index b1f027a65b..35e65e59e4 100644 --- a/packages/language-core/src/virtualFile/vueFile.ts +++ b/packages/language-core/src/virtualFile/vueFile.ts @@ -18,10 +18,10 @@ export class VueFile implements VirtualFile { // computeds - getVueSfc = computedVueSfc(this.plugins, this.fileName, () => this._snapshot()); - sfc = computedSfc(this.ts, this.plugins, this.fileName, () => this._snapshot(), this.getVueSfc); + getVueSfc = computedVueSfc(this.plugins, this.id, () => this._snapshot()); + sfc = computedSfc(this.ts, this.plugins, this.id, () => this._snapshot(), this.getVueSfc); getMappings = computedMappings(() => this._snapshot(), this.sfc); - getEmbeddedFiles = computedFiles(this.plugins, this.fileName, this.sfc, this.codegenStack); + getEmbeddedFiles = computedFiles(this.plugins, this.id, this.sfc, this.codegenStack); // others @@ -31,14 +31,14 @@ export class VueFile implements VirtualFile { get embeddedFiles() { return this.getEmbeddedFiles(); } - get mainScriptName() { - let res: string = ''; + get mainTsFile() { + let result: VirtualFile | undefined; forEachEmbeddedFile(this, file => { - if (file.kind === FileKind.TypeScriptHostFile && file.fileName.replace(this.fileName, '').match(jsxReg)) { - res = file.fileName; + if (file.kind === FileKind.TypeScriptHostFile && file.id.substring(this.id.length).match(jsxReg)) { + result = file; } }); - return res; + return result; } get snapshot() { return this._snapshot(); @@ -48,7 +48,7 @@ export class VueFile implements VirtualFile { } constructor( - public fileName: string, + public id: string, public languageId: string, public initSnapshot: ts.IScriptSnapshot, public vueCompilerOptions: VueCompilerOptions, diff --git a/packages/language-service/src/helpers.ts b/packages/language-service/src/helpers.ts index 34cfe02890..8d3dc7c852 100644 --- a/packages/language-service/src/helpers.ts +++ b/packages/language-service/src/helpers.ts @@ -1,15 +1,16 @@ -import * as vue from '@vue/language-core'; -import type { CompilerDOM } from '@vue/language-core'; import * as embedded from '@volar/language-core'; -import { computed } from 'computeds'; +import type { CompilerDOM } from '@vue/language-core'; +import * as vue from '@vue/language-core'; import { sharedTypes } from '@vue/language-core'; import { camelize, capitalize } from '@vue/shared'; - +import { computed } from 'computeds'; import type * as ts from 'typescript/lib/tsserverlibrary'; +import type { ServiceEnvironment } from './types'; export function getPropsByTag( ts: typeof import('typescript/lib/tsserverlibrary'), tsLs: ts.LanguageService, + env: ServiceEnvironment, sourceFile: embedded.VirtualFile, tag: string, vueCompilerOptions: vue.VueCompilerOptions, @@ -17,7 +18,7 @@ export function getPropsByTag( ) { const checker = tsLs.getProgram()!.getTypeChecker(); - const components = getVariableType(ts, tsLs, sourceFile, '__VLS_components'); + const components = getVariableType(ts, tsLs, env, sourceFile, '__VLS_components'); if (!components) return []; @@ -83,13 +84,14 @@ export function getPropsByTag( export function getEventsOfTag( ts: typeof import('typescript/lib/tsserverlibrary'), tsLs: ts.LanguageService, + env: ServiceEnvironment, sourceFile: embedded.VirtualFile, tag: string, vueCompilerOptions: vue.VueCompilerOptions, ) { const checker = tsLs.getProgram()!.getTypeChecker(); - const components = getVariableType(ts, tsLs, sourceFile, '__VLS_components'); + const components = getVariableType(ts, tsLs, env, sourceFile, '__VLS_components'); if (!components) return []; @@ -149,9 +151,10 @@ export function getEventsOfTag( export function getTemplateCtx( ts: typeof import('typescript/lib/tsserverlibrary'), tsLs: ts.LanguageService, + env: ServiceEnvironment, sourceFile: embedded.VirtualFile, ) { - return getVariableType(ts, tsLs, sourceFile, '__VLS_ctx') + return getVariableType(ts, tsLs, env, sourceFile, '__VLS_ctx') ?.type ?.getProperties() .map(c => c.name); @@ -160,10 +163,11 @@ export function getTemplateCtx( export function getComponentNames( ts: typeof import('typescript/lib/tsserverlibrary'), tsLs: ts.LanguageService, + env: ServiceEnvironment, sourceFile: embedded.VirtualFile, vueCompilerOptions: vue.VueCompilerOptions, ) { - return getVariableType(ts, tsLs, sourceFile, '__VLS_components') + return getVariableType(ts, tsLs, env, sourceFile, '__VLS_components') ?.type ?.getProperties() .map(c => c.name) @@ -207,6 +211,7 @@ export function getElementAttrs( function getVariableType( ts: typeof import('typescript/lib/tsserverlibrary'), tsLs: ts.LanguageService, + env: ServiceEnvironment, sourceFile: embedded.VirtualFile, name: string, ) { @@ -215,16 +220,11 @@ function getVariableType( return; } - let file: embedded.VirtualFile | undefined; - let tsSourceFile: ts.SourceFile | undefined; + const file = sourceFile.mainTsFile; - embedded.forEachEmbeddedFile(sourceFile, embedded => { - if (embedded.fileName === sourceFile.mainScriptName) { - file = embedded; - } - }); + let tsSourceFile: ts.SourceFile | undefined; - if (file && (tsSourceFile = tsLs.getProgram()?.getSourceFile(file.fileName))) { + if (file && (tsSourceFile = tsLs.getProgram()?.getSourceFile(env.uriToFileName(file.id)))) { const node = searchVariableDeclarationNode(ts, tsSourceFile, name); const checker = tsLs.getProgram()?.getTypeChecker(); diff --git a/packages/language-service/src/ideFeatures/dragImport.ts b/packages/language-service/src/ideFeatures/dragImport.ts index 1c4a9f1050..41ddc6203e 100644 --- a/packages/language-service/src/ideFeatures/dragImport.ts +++ b/packages/language-service/src/ideFeatures/dragImport.ts @@ -23,7 +23,7 @@ export function getDragImportEdits( const newName = capitalize(camelize(baseName)); const document = ctx!.getTextDocument(uri)!; - const [vueFile] = ctx!.documents.getVirtualFileByUri(document.uri) as [VueFile, any]; + const [vueFile] = ctx!.project.fileProvider.getVirtualFile(document.uri) as [VueFile, any]; const { sfc } = vueFile; const script = sfc.scriptSetup ?? sfc.script; diff --git a/packages/language-service/src/ideFeatures/nameCasing.ts b/packages/language-service/src/ideFeatures/nameCasing.ts index 516ffff682..b49f91ca0a 100644 --- a/packages/language-service/src/ideFeatures/nameCasing.ts +++ b/packages/language-service/src/ideFeatures/nameCasing.ts @@ -13,7 +13,7 @@ export async function convertTagName( vueCompilerOptions: VueCompilerOptions, ) { - const rootFile = context.documents.getSourceByUri(uri)?.root; + const rootFile = context.project.fileProvider.getSourceFile(uri)?.root; if (!(rootFile instanceof VueFile)) return; @@ -23,9 +23,9 @@ export async function convertTagName( const languageService = context.inject('typescript/languageService'); const template = desc.template; - const document = context.documents.getDocumentByFileName(rootFile.snapshot, rootFile.fileName, rootFile.languageId); + const document = context.documents.getDocumentByUri(rootFile.id, rootFile.languageId, rootFile.snapshot); const edits: vscode.TextEdit[] = []; - const components = getComponentNames(ts, languageService, rootFile, vueCompilerOptions); + const components = getComponentNames(ts, languageService, context.env, rootFile, vueCompilerOptions); const tags = getTemplateTagsAndAttrs(rootFile); for (const [tagName, { offsets }] of tags) { @@ -56,7 +56,7 @@ export async function convertAttrName( vueCompilerOptions: VueCompilerOptions, ) { - const rootFile = context.documents.getSourceByUri(uri)?.root; + const rootFile = context.project.fileProvider.getSourceFile(uri)?.root; if (!(rootFile instanceof VueFile)) return; @@ -66,15 +66,15 @@ export async function convertAttrName( const languageService = context.inject('typescript/languageService'); const template = desc.template; - const document = context.documents.getDocumentByFileName(rootFile.snapshot, rootFile.fileName, rootFile.languageId); + const document = context.documents.getDocumentByUri(rootFile.id, rootFile.languageId, rootFile.snapshot); const edits: vscode.TextEdit[] = []; - const components = getComponentNames(ts, languageService, rootFile, vueCompilerOptions); + const components = getComponentNames(ts, languageService, context.env, rootFile, vueCompilerOptions); const tags = getTemplateTagsAndAttrs(rootFile); for (const [tagName, { attrs }] of tags) { const componentName = components.find(component => component === tagName || hyphenateTag(component) === tagName); if (componentName) { - const props = getPropsByTag(ts, languageService, rootFile, componentName, vueCompilerOptions); + const props = getPropsByTag(ts, languageService, context.env, rootFile, componentName, vueCompilerOptions); for (const [attrName, { offsets }] of attrs) { const propName = props.find(prop => prop === attrName || hyphenateAttr(prop) === attrName); if (propName) { @@ -128,7 +128,7 @@ export function detect( attr: AttrNameCasing[], } { - const rootFile = context.documents.getSourceByUri(uri)?.root; + const rootFile = context.project.fileProvider.getSourceFile(uri)?.root; if (!(rootFile instanceof VueFile)) { return { tag: [], @@ -169,7 +169,7 @@ export function detect( } function getTagNameCase(file: VirtualFile): TagNameCasing[] { - const components = getComponentNames(ts, languageService, file, vueCompilerOptions); + const components = getComponentNames(ts, languageService, context.env, file, vueCompilerOptions); const tagNames = getTemplateTagsAndAttrs(file); const result: TagNameCasing[] = []; diff --git a/packages/language-service/src/languageService.ts b/packages/language-service/src/languageService.ts index 9af52c2f83..7f70b65e3a 100644 --- a/packages/language-service/src/languageService.ts +++ b/packages/language-service/src/languageService.ts @@ -89,19 +89,26 @@ export function resolveServices( // handle component auto-import patch let casing: Awaited> | undefined; - for (const [_, map] of ctx.documents.getMapsByVirtualFileUri(document.uri)) { - const virtualFile = ctx.documents.getSourceByUri(map.sourceFileDocument.uri)?.root; - if (virtualFile instanceof VueFile) { - const isAutoImport = !!map.toSourcePosition(position, data => typeof data.completion === 'object' && !!data.completion.autoImportOnly); - if (isAutoImport) { - const source = ctx.documents.getVirtualFileByUri(document.uri)[1]; - for (const item of result.items) { - item.data.__isComponentAutoImport = true; - } + const [virtualFile, sourceFile] = ctx.project.fileProvider.getVirtualFile(document.uri); + + if (virtualFile && sourceFile) { + + for (const map of ctx.documents.getMapsByVirtualFile(virtualFile)) { + + const sourceVirtualFile = ctx.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + + if (sourceVirtualFile instanceof VueFile) { + + const isAutoImport = !!map.toSourcePosition(position, data => typeof data.completion === 'object' && !!data.completion.autoImportOnly); + if (isAutoImport) { + + for (const item of result.items) { + item.data.__isComponentAutoImport = true; + } + + // fix #2458 + casing ??= await getNameCasing(ts, ctx, sourceFile.id, vueCompilerOptions); - // fix #2458 - if (source) { - casing ??= await getNameCasing(ts, ctx, ctx.env.fileNameToUri(source.fileName), vueCompilerOptions); if (casing.tag === TagNameCasing.Kebab) { for (const item of result.items) { item.filterText = hyphenateTag(item.filterText ?? item.label); @@ -151,9 +158,9 @@ export function resolveServices( 'import ' + newName + ' from ', ); item.textEdit.newText = newName; - const source = ctx.documents.getVirtualFileByUri(itemData.uri)[1]; - if (source) { - const casing = await getNameCasing(ts, ctx, ctx.env.fileNameToUri(source.fileName), vueCompilerOptions); + const [_, sourceFile] = ctx.project.fileProvider.getVirtualFile(itemData.uri); + if (sourceFile) { + const casing = await getNameCasing(ts, ctx, sourceFile.id, vueCompilerOptions); if (casing.tag === TagNameCasing.Kebab) { item.textEdit.newText = hyphenateTag(item.textEdit.newText); } @@ -176,7 +183,7 @@ export function resolveServices( const componentName = newName ?? item.textEdit.newText; const optionEdit = ExtractComponentService.createAddComponentToOptionEdit(ts, ast, componentName); if (optionEdit) { - const textDoc = ctx.documents.getDocumentByFileName(virtualFile.snapshot, virtualFile.fileName, virtualFile.languageId); + const textDoc = ctx.documents.getDocumentByUri(virtualFile.id, virtualFile.languageId, virtualFile.snapshot); item.additionalTextEdits.push({ range: { start: textDoc.positionAt(optionEdit.range.start), diff --git a/packages/language-service/src/plugins/vue-autoinsert-parentheses.ts b/packages/language-service/src/plugins/vue-autoinsert-parentheses.ts index fba00b5f71..f6e134a86f 100644 --- a/packages/language-service/src/plugins/vue-autoinsert-parentheses.ts +++ b/packages/language-service/src/plugins/vue-autoinsert-parentheses.ts @@ -24,8 +24,8 @@ const plugin: Service = (context, modules) => { if (!isCharacterTyping(document, options_2)) return; - const [virtualFile] = context.documents.getVirtualFileByUri(document.uri); - if (!virtualFile?.fileName.endsWith('.template_format.ts')) + const [virtualFile] = context.project.fileProvider.getVirtualFile(document.uri); + if (!virtualFile?.id.endsWith('.template_format.ts')) return; const offset = document.offsetAt(position); @@ -33,7 +33,7 @@ const plugin: Service = (context, modules) => { for (const mappedRange of virtualFile.mappings) { if (mappedRange.generatedRange[1] === offset) { const text = document.getText().substring(mappedRange.generatedRange[0], mappedRange.generatedRange[1]); - const ast = ts.createSourceFile(virtualFile.fileName, text, ts.ScriptTarget.Latest); + const ast = ts.createSourceFile(context.env.uriToFileName(virtualFile.id), text, ts.ScriptTarget.Latest); if (ast.statements.length === 1) { const statement = ast.statements[0]; if ( diff --git a/packages/language-service/src/plugins/vue-codelens-references.ts b/packages/language-service/src/plugins/vue-codelens-references.ts index e8a247335d..bded680896 100644 --- a/packages/language-service/src/plugins/vue-codelens-references.ts +++ b/packages/language-service/src/plugins/vue-codelens-references.ts @@ -1,5 +1,5 @@ import { Service } from '@volar/language-service'; -import { VueFile } from '@vue/language-core'; +import { SourceFile, VueFile } from '@vue/language-core'; import type * as vscode from 'vscode-languageserver-protocol'; export const create = function (): Service { @@ -13,11 +13,11 @@ export const create = function (): Service { provideReferencesCodeLensRanges(document) { - return worker(document.uri, async () => { + return worker(document.uri, async (_, sourceFile) => { const result: vscode.Range[] = []; - for (const [_, map] of context.documents.getMapsBySourceFileUri(document.uri)?.maps ?? []) { + for (const [_, map] of context.documents.getMapsBySourceFile(sourceFile) ?? []) { for (const mapping of map.map.mappings) { if (!mapping.data.referencesCodeLens) @@ -38,7 +38,7 @@ export const create = function (): Service { await worker(document.uri, async (vueFile) => { - const document = context.documents.getDocumentByFileName(vueFile.snapshot, vueFile.fileName, vueFile.languageId); + const document = context.documents.getDocumentByUri(vueFile.id, vueFile.languageId, vueFile.snapshot); const offset = document.offsetAt(range.start); const blocks = [ vueFile.sfc.script, @@ -58,13 +58,13 @@ export const create = function (): Service { }, }; - function worker(uri: string, callback: (vueSourceFile: VueFile) => T) { + function worker(uri: string, callback: (vueFile: VueFile, sourceFile: SourceFile) => T) { - const [virtualFile] = context!.documents.getVirtualFileByUri(uri); - if (!(virtualFile instanceof VueFile)) + const [virtualFile, sourceFile] = context!.project.fileProvider.getVirtualFile(uri); + if (!(virtualFile instanceof VueFile) || !sourceFile) return; - return callback(virtualFile); + return callback(virtualFile, sourceFile); } }; }; diff --git a/packages/language-service/src/plugins/vue-extract-file.ts b/packages/language-service/src/plugins/vue-extract-file.ts index 615640b970..63a544d133 100644 --- a/packages/language-service/src/plugins/vue-extract-file.ts +++ b/packages/language-service/src/plugins/vue-extract-file.ts @@ -32,7 +32,7 @@ export const create = function (): Service { return; } - const [vueFile] = ctx!.documents.getVirtualFileByUri(document.uri); + const [vueFile] = ctx!.project.fileProvider.getVirtualFile(document.uri); if (!vueFile || !(vueFile instanceof VueFile)) return; @@ -64,7 +64,7 @@ export const create = function (): Service { const { uri, range, newName } = codeAction.data as ActionData; const document = ctx!.getTextDocument(uri)!; const [startOffset, endOffset]: [number, number] = range; - const [vueFile] = ctx!.documents.getVirtualFileByUri(document.uri) as [VueFile, any]; + const [vueFile] = ctx!.project.fileProvider.getVirtualFile(document.uri) as [VueFile, any]; const { sfc } = vueFile; const script = sfc.scriptSetup ?? sfc.script; @@ -77,8 +77,10 @@ export const create = function (): Service { const languageService = ctx!.inject('typescript/languageService'); const languageServiceHost = ctx!.inject('typescript/languageServiceHost'); - const sourceFile = languageService.getProgram()!.getSourceFile(vueFile.mainScriptName)!; - const sourceFileKind = languageServiceHost.getScriptKind?.(vueFile.mainScriptName); + const tsScriptUri = vueFile.mainTsFile!.id; + const tsScriptName = ctx!.env.uriToFileName(tsScriptUri); + const sourceFile = languageService.getProgram()!.getSourceFile(tsScriptName)!; + const sourceFileKind = languageServiceHost.getScriptKind?.(tsScriptName); const toExtract = collectExtractProps(); const initialIndentSetting = await ctx!.env.getConfiguration!('volar.format.initialIndent') as Record; const newUri = document.uri.substring(0, document.uri.lastIndexOf('/') + 1) + `${newName}.vue`; @@ -177,7 +179,8 @@ export const create = function (): Service { model: boolean; }>(); const checker = languageService.getProgram()!.getTypeChecker(); - const maps = [...ctx!.documents.getMapsByVirtualFileName(vueFile.mainScriptName)]; + const [virtualFile] = ctx!.project.fileProvider.getVirtualFile(tsScriptUri); + const maps = virtualFile ? [...ctx!.documents.getMapsByVirtualFile(virtualFile)] : []; sourceFile.forEachChild(function visit(node) { if ( @@ -187,7 +190,7 @@ export const create = function (): Service { && ts.isIdentifier(node.name) ) { const { name } = node; - for (const [_, map] of maps) { + for (const map of maps) { const source = map.map.toSourceOffset(name.getEnd()); if (source && source[0] >= sfc.template!.startTagEnd + templateCodeRange![0] && source[0] <= sfc.template!.startTagEnd + templateCodeRange![1] && source[1].data.semanticTokens) { if (!result.has(name.text)) { diff --git a/packages/language-service/src/plugins/vue-template.ts b/packages/language-service/src/plugins/vue-template.ts index 1d465e73af..bc41d886af 100644 --- a/packages/language-service/src/plugins/vue-template.ts +++ b/packages/language-service/src/plugins/vue-template.ts @@ -75,10 +75,14 @@ export const create = (options: { if (!options.isSupportedDocument(document)) return; - for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) { - const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root; - if (virtualFile && virtualFile instanceof VueFile) { - await provideHtmlData(map, virtualFile); + const [virtualFile] = _context.project.fileProvider.getVirtualFile(document.uri); + + if (virtualFile) { + for (const map of _context.documents.getMapsByVirtualFile(virtualFile)) { + const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + if (sourceVirtualFile instanceof VueFile) { + await provideHtmlData(map, sourceVirtualFile); + } } } @@ -86,10 +90,12 @@ export const create = (options: { if (!htmlComplete) return; - for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) { - const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root; - if (virtualFile && virtualFile instanceof VueFile) { - afterHtmlCompletion(htmlComplete, map, virtualFile); + if (virtualFile) { + for (const map of _context.documents.getMapsByVirtualFile(virtualFile)) { + const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + if (sourceVirtualFile instanceof VueFile) { + afterHtmlCompletion(htmlComplete, map, sourceVirtualFile); + } } } @@ -107,15 +113,20 @@ export const create = (options: { const languageService = _context.inject('typescript/languageService'); const result: vscode.InlayHint[] = []; + const [virtualFile] = _context.project.fileProvider.getVirtualFile(document.uri); + if (!virtualFile) + return; + + for (const map of _context.documents.getMapsByVirtualFile(virtualFile)) { - for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) { - const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root; + const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; const scanner = options.getScanner(htmlOrPugService, document); - if (virtualFile && virtualFile instanceof VueFile && scanner) { + + if (sourceVirtualFile instanceof VueFile && scanner) { // visualize missing required props const casing = await getNameCasing(ts, _context, map.sourceFileDocument.uri, options.vueCompilerOptions); - const components = getComponentNames(ts, languageService, virtualFile, options.vueCompilerOptions); + const components = getComponentNames(ts, languageService, _context!.env, sourceVirtualFile, options.vueCompilerOptions); const componentProps: Record = {}; let token: html.TokenType; let current: { @@ -132,7 +143,7 @@ export const create = (options: { : components.find(component => component === tagName || hyphenateTag(component) === tagName); const checkTag = tagName.indexOf('.') >= 0 ? tagName : component; if (checkTag) { - componentProps[checkTag] ??= getPropsByTag(ts, languageService, virtualFile, checkTag, options.vueCompilerOptions, true); + componentProps[checkTag] ??= getPropsByTag(ts, languageService, _context!.env, sourceVirtualFile, checkTag, options.vueCompilerOptions, true); current = { unburnedRequiredProps: [...componentProps[checkTag]], labelOffset: scanner.getTokenOffset() + scanner.getTokenLength(), @@ -213,7 +224,7 @@ export const create = (options: { if (!options.isSupportedDocument(document)) return; - if (_context.documents.isVirtualFileUri(document.uri)) + if (_context.project.fileProvider.getVirtualFile(document.uri)[0]) options.updateCustomData(htmlOrPugService, []); return htmlOrPugService.provideHover?.(document, position, token); @@ -225,15 +236,19 @@ export const create = (options: { return; const originalResult = await htmlOrPugService.provideDiagnostics?.(document, token); + const [virtualFile] = _context.project.fileProvider.getVirtualFile(document.uri); + + if (!virtualFile) + return; - for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) { + for (const map of _context.documents.getMapsByVirtualFile(virtualFile)) { - const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root; - if (!virtualFile || !(virtualFile instanceof VueFile)) + const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + if (!(sourceVirtualFile instanceof VueFile)) continue; const templateErrors: vscode.Diagnostic[] = []; - const { template } = virtualFile.sfc; + const { template } = sourceVirtualFile.sfc; if (template) { @@ -284,14 +299,17 @@ export const create = (options: { return; const languageService = _context.inject('typescript/languageService'); + const [virtualFile] = _context.project.fileProvider.getVirtualFile(document.uri); + if (!virtualFile) + return; - for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) { + for (const map of _context.documents.getMapsByVirtualFile(virtualFile)) { - const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root; - if (!virtualFile || !(virtualFile instanceof VueFile)) + const sourceFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + if (!(sourceFile instanceof VueFile)) continue; - const templateScriptData = getComponentNames(ts, languageService, virtualFile, options.vueCompilerOptions); + const templateScriptData = getComponentNames(ts, languageService, _context!.env, sourceFile, options.vueCompilerOptions); const components = new Set([ ...templateScriptData, ...templateScriptData.map(hyphenateTag), @@ -367,7 +385,7 @@ export const create = (options: { isApplicable: () => true, provideTags: () => { - const components = getComponentNames(ts, languageService, vueSourceFile, options.vueCompilerOptions) + const components = getComponentNames(ts, languageService, _context!.env, vueSourceFile, options.vueCompilerOptions) .filter(name => name !== 'Transition' && name !== 'TransitionGroup' @@ -410,8 +428,8 @@ export const create = (options: { provideAttributes: (tag) => { const attrs = getElementAttrs(ts, languageService, languageServiceHost, tag); - const props = new Set(getPropsByTag(ts, languageService, vueSourceFile, tag, options.vueCompilerOptions)); - const events = getEventsOfTag(ts, languageService, vueSourceFile, tag, options.vueCompilerOptions); + const props = new Set(getPropsByTag(ts, languageService, _context!.env, vueSourceFile, tag, options.vueCompilerOptions)); + const events = getEventsOfTag(ts, languageService, _context!.env, vueSourceFile, tag, options.vueCompilerOptions); const attributes: html.IAttributeData[] = []; const _tsCodegen = tsCodegen.get(vueSourceFile.sfc); @@ -419,7 +437,7 @@ export const create = (options: { let ctxVars = [ ..._tsCodegen.scriptRanges()?.bindings.map(binding => vueSourceFile.sfc.script!.content.substring(binding.start, binding.end)) ?? [], ..._tsCodegen.scriptSetupRanges()?.bindings.map(binding => vueSourceFile.sfc.scriptSetup!.content.substring(binding.start, binding.end)) ?? [], - ...getTemplateCtx(ts, languageService, vueSourceFile) ?? [], + ...getTemplateCtx(ts, languageService, _context!.env, vueSourceFile) ?? [], ]; ctxVars = [...new Set(ctxVars)]; const dirs = ctxVars.map(hyphenateAttr).filter(v => v.startsWith('v-')); @@ -535,7 +553,7 @@ export const create = (options: { const languageService = _context!.inject('typescript/languageService'); const replacement = getReplacement(completionList, map.sourceFileDocument); - const componentNames = new Set(getComponentNames(ts, languageService, vueSourceFile, options.vueCompilerOptions).map(hyphenateTag)); + const componentNames = new Set(getComponentNames(ts, languageService, _context!.env, vueSourceFile, options.vueCompilerOptions).map(hyphenateTag)); if (replacement) { diff --git a/packages/language-service/src/plugins/vue-toggle-v-bind-codeaction.ts b/packages/language-service/src/plugins/vue-toggle-v-bind-codeaction.ts index f59ae11d8d..16fb334f34 100644 --- a/packages/language-service/src/plugins/vue-toggle-v-bind-codeaction.ts +++ b/packages/language-service/src/plugins/vue-toggle-v-bind-codeaction.ts @@ -14,15 +14,16 @@ export const create = function (): Service { return { provideCodeActions(document, range, _context) { + const startOffset = document.offsetAt(range.start); const endOffset = document.offsetAt(range.end); + const [virtualFile] = ctx!.project.fileProvider.getVirtualFile(document.uri); - const [vueFile] = ctx!.documents.getVirtualFileByUri(document.uri); - if (!vueFile || !(vueFile instanceof VueFile)) { + if (!(virtualFile instanceof VueFile)) { return; } - const { template } = vueFile.sfc; + const { template } = virtualFile.sfc; if (!template?.ast) return; diff --git a/packages/language-service/src/plugins/vue-twoslash-queries.ts b/packages/language-service/src/plugins/vue-twoslash-queries.ts index 51c511ff09..bc1057fe09 100644 --- a/packages/language-service/src/plugins/vue-twoslash-queries.ts +++ b/packages/language-service/src/plugins/vue-twoslash-queries.ts @@ -29,13 +29,13 @@ const plugin: Service = (context: ServiceContext { - if (embedded.kind === FileKind.TypeScriptHostFile) { - for (const [_, map] of context.documents.getMapsByVirtualFileName(embedded.fileName)) { + forEachEmbeddedFile(vueFile, (virtualFile) => { + if (virtualFile.kind === FileKind.TypeScriptHostFile) { + for (const map of context.documents.getMapsByVirtualFile(virtualFile)) { for (const [pointerPosition, hoverOffset] of hoverOffsets) { for (const [tsOffset, mapping] of map.map.toGeneratedOffsets(hoverOffset)) { if (mapping.data.hover) { - const quickInfo = languageService.getQuickInfoAtPosition(embedded.fileName, tsOffset); + const quickInfo = languageService.getQuickInfoAtPosition(context.env.uriToFileName(virtualFile.id), tsOffset); if (quickInfo) { inlayHints.push({ position: { line: pointerPosition.line, character: pointerPosition.character + 2 }, @@ -59,7 +59,7 @@ const plugin: Service = (context: ServiceContext(uri: string, callback: (vueSourceFile: vue.VueFile) => T) { - const [virtualFile] = context!.documents.getVirtualFileByUri(uri); + const [virtualFile] = context!.project.fileProvider.getVirtualFile(uri); if (!(virtualFile instanceof vue.VueFile)) return; diff --git a/packages/language-service/src/plugins/vue-visualize-hidden-callback-param.ts b/packages/language-service/src/plugins/vue-visualize-hidden-callback-param.ts index 1aca2da3e4..8edb3f1104 100644 --- a/packages/language-service/src/plugins/vue-visualize-hidden-callback-param.ts +++ b/packages/language-service/src/plugins/vue-visualize-hidden-callback-param.ts @@ -12,11 +12,14 @@ const plugin: Service = (context) => { const settings: Record = {}; const result: vscode.InlayHint[] = []; - const [file] = context.documents.getVirtualFileByUri(document.uri); - if (file) { + const [vitualFile] = context.project.fileProvider.getVirtualFile(document.uri); + + if (vitualFile) { + const start = document.offsetAt(range.start); const end = document.offsetAt(range.end); - for (const mapping of file.mappings) { + + for (const mapping of vitualFile.mappings) { const hint: { setting: string; diff --git a/packages/language-service/src/plugins/vue.ts b/packages/language-service/src/plugins/vue.ts index a9b14dd2c9..4338436fb8 100644 --- a/packages/language-service/src/plugins/vue.ts +++ b/packages/language-service/src/plugins/vue.ts @@ -38,11 +38,16 @@ export const create = (): Service => (context: ServiceContext { + if (!vueSourceFile.mainTsFile) { + return; + } + const result: vscode.Diagnostic[] = []; const sfc = vueSourceFile.sfc; const program = context.inject('typescript/languageService').getProgram(); + const tsFileName = context.env.uriToFileName(vueSourceFile.mainTsFile.id); - if (program && !program.getSourceFile(vueSourceFile.mainScriptName)) { + if (program && !program.getSourceFile(tsFileName)) { for (const script of [sfc.script, sfc.scriptSetup]) { if (!script || script.content === '') @@ -53,7 +58,7 @@ export const create = (): Service => (context: ServiceContext / "allowJs": true / jsconfig.json.`, + message: `Virtual script ${JSON.stringify(tsFileName)} not found, may missing