Skip to content

Commit

Permalink
fix(language-server): make sure vueCompilerOptions are consistent bet…
Browse files Browse the repository at this point in the history
…ween language server and ts plugin
  • Loading branch information
johnsoncodehk committed Mar 7, 2025
1 parent 1b25cb6 commit 82ff71d
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 150 deletions.
18 changes: 11 additions & 7 deletions extensions/vscode/src/nodeClientMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,17 @@ export const { activate, deactivate } = defineExtension(async () => {
if (!tsserver) {
return;
}
const res = await tsserver.executeImpl(command, args, {
isAsync: true,
expectsResult: true,
lowPriority: true,
requireSemantic: true,
})[0];
return res.body;
try {
const res = await tsserver.executeImpl(command, args, {
isAsync: true,
expectsResult: true,
lowPriority: true,
requireSemantic: true,
})[0];
return res.body;
} catch {
// noop
}
});

return client;
Expand Down
80 changes: 0 additions & 80 deletions packages/language-server/lib/hybridModeProject.ts

This file was deleted.

147 changes: 113 additions & 34 deletions packages/language-server/node.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { LanguageServer } from '@volar/language-server';
import { createLanguageServiceEnvironment } from '@volar/language-server/lib/project/simpleProject';
import { createConnection, createServer, loadTsdkByPath } from '@volar/language-server/node';
import { createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core';
import { getHybridModeLanguageServicePlugins } from '@vue/language-service';
import { createHybridModeProject } from './lib/hybridModeProject';
import { createLanguage, createParsedCommandLine, createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core';
import { createLanguageService, createUriMap, getHybridModeLanguageServicePlugins, LanguageService } from '@vue/language-service';
import type * as ts from 'typescript';
import { URI } from 'vscode-uri';
import type { VueInitializationOptions } from './lib/types';

const connection = createConnection();
Expand All @@ -20,62 +23,138 @@ connection.onInitialize(params => {
}

const { typescript: ts } = loadTsdkByPath(options.typescript.tsdk, params.locale);
const tsconfigProjects = createUriMap<LanguageService>();

server.fileWatcher.onDidChangeWatchedFiles(({ changes }) => {
for (const change of changes) {
const changeUri = URI.parse(change.uri);
if (tsconfigProjects.has(changeUri)) {
tsconfigProjects.get(changeUri)!.dispose();
tsconfigProjects.delete(changeUri);
}
}
});

let simpleLs: LanguageService | undefined;

return server.initialize(
params,
createHybridModeProject(
() => {
const commandLine = {
vueOptions: getDefaultCompilerOptions(),
options: ts.getDefaultCompilerOptions(),
};
return {
languagePlugins: [
createVueLanguagePlugin(
ts,
commandLine.options,
commandLine.vueOptions,
uri => uri.fsPath.replace(/\\/g, '/')
),
],
setup({ project }) {
project.vue = { compilerOptions: commandLine.vueOptions };
},
};
}
),
{
setup() { },
async getLanguageService(uri) {
if (uri.scheme === 'file' && options.typescript.requestForwardingCommand) {
const fileName = uri.fsPath.replace(/\\/g, '/');
const projectInfo = await sendTsRequest<ts.server.protocol.ProjectInfo>(
ts.server.protocol.CommandTypes.ProjectInfo,
{
file: fileName,
needFileNameList: false,
} satisfies ts.server.protocol.ProjectInfoRequestArgs
);
if (projectInfo) {
const { configFileName } = projectInfo;
let ls = tsconfigProjects.get(URI.file(configFileName));
if (!ls) {
ls = createLs(server, configFileName);
tsconfigProjects.set(URI.file(configFileName), ls);
}
return ls;
}
}
return simpleLs ??= createLs(server, undefined);
},
getExistingLanguageServices() {
return Promise.all([
...tsconfigProjects.values(),
simpleLs,
].filter(promise => !!promise));
},
reload() {
for (const ls of [
...tsconfigProjects.values(),
simpleLs,
]) {
ls?.dispose();
}
tsconfigProjects.clear();
simpleLs = undefined;
},
},
getHybridModeLanguageServicePlugins(ts, options.typescript.requestForwardingCommand ? {
collectExtractProps(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:collectExtractProps', args]);
return sendTsRequest('vue:collectExtractProps', args);
},
getComponentDirectives(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentDirectives', args]);
return sendTsRequest('vue:getComponentDirectives', args);
},
getComponentEvents(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentEvents', args]);
return sendTsRequest('vue:getComponentEvents', args);
},
getComponentNames(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentNames', args]);
return sendTsRequest('vue:getComponentNames', args);
},
getComponentProps(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentProps', args]);
return sendTsRequest('vue:getComponentProps', args);
},
getElementAttrs(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getElementAttrs', args]);
return sendTsRequest('vue:getElementAttrs', args);
},
getElementNames(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getElementNames', args]);
return sendTsRequest('vue:getElementNames', args);
},
getImportPathForFile(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getImportPathForFile', args]);
return sendTsRequest('vue:getImportPathForFile', args);
},
getPropertiesAtLocation(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getPropertiesAtLocation', args]);
return sendTsRequest('vue:getPropertiesAtLocation', args);
},
getQuickInfoAtPosition(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getQuickInfoAtPosition', args]);
return sendTsRequest('vue:getQuickInfoAtPosition', args);
},
} : undefined)
);

function sendTsRequest<T>(command: string, args: any): Promise<T | null> {
return connection.sendRequest<T>(options.typescript.requestForwardingCommand!, [command, args]);
}

function createLs(server: LanguageServer, tsconfig: string | undefined) {
const commonLine = tsconfig
? createParsedCommandLine(ts, ts.sys, tsconfig)
: {
options: ts.getDefaultCompilerOptions(),
vueOptions: getDefaultCompilerOptions(),
};
const language = createLanguage<URI>(
[
{
getLanguageId: uri => server.documents.get(uri)?.languageId,
},
createVueLanguagePlugin(
ts,
commonLine.options,
commonLine.vueOptions,
uri => uri.fsPath.replace(/\\/g, '/')
),
],
createUriMap(),
uri => {
const document = server.documents.get(uri);
if (document) {
language.scripts.set(uri, document.getSnapshot(), document.languageId);
}
else {
language.scripts.delete(uri);
}
}
);
return createLanguageService(
language,
server.languageServicePlugins,
createLanguageServiceEnvironment(server, [...server.workspaceFolders.all]),
{ vue: { compilerOptions: commonLine.vueOptions } }
);
}
});

connection.onInitialized(server.initialized);
Expand Down
51 changes: 22 additions & 29 deletions packages/typescript-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import { getQuickInfoAtPosition } from './lib/requests/getQuickInfoAtPosition';
import type { RequestContext } from './lib/requests/types';

const windowsPathReg = /\\/g;
const project2Language = new Map<ts.server.Project, [vue.Language, ts.LanguageServiceHost, ts.LanguageService]>();
const plugin = createLanguageServicePlugin(
const project2Service = new WeakMap<ts.server.Project, [vue.Language, ts.LanguageServiceHost, ts.LanguageService]>();

export = createLanguageServicePlugin(
(ts, info) => {
const vueOptions = getVueCompilerOptions();
const languagePlugin = vue.createVueLanguagePlugin<string>(
Expand All @@ -31,7 +32,7 @@ const plugin = createLanguageServicePlugin(
return {
languagePlugins: [languagePlugin],
setup: language => {
project2Language.set(info.project, [language, info.languageServiceHost, info.languageService]);
project2Service.set(info.project, [language, info.languageServiceHost, info.languageService]);

info.languageService = proxyLanguageServiceForVue(ts, language, info.languageService, vueOptions, fileName => fileName);

Expand Down Expand Up @@ -131,33 +132,25 @@ const plugin = createLanguageServicePlugin(
}

function getRequestContext(fileName: string): RequestContext {
for (const [project, [language, languageServiceHost, languageService]] of project2Language) {
if (project.projectKind === 1 satisfies ts.server.ProjectKind.Configured && project.containsFile(ts.server.toNormalizedPath(fileName))) {
return {
typescript: ts,
languageService: languageService,
languageServiceHost: languageServiceHost,
language: language,
isTsPlugin: true,
getFileId: (fileName: string) => fileName,
};
}
}
for (const [project, [language, languageServiceHost, languageService]] of project2Language) {
if (project.projectKind === 0 satisfies ts.server.ProjectKind.Inferred) {
return {
typescript: ts,
languageService: languageService,
languageServiceHost: languageServiceHost,
language: language,
isTsPlugin: true,
getFileId: (fileName: string) => fileName,
};
}
const fileAndProject = (info.session as any).getFileAndProject({
file: fileName,
projectFileName: undefined,
}) as {
file: ts.server.NormalizedPath;
project: ts.server.Project;
};
const service = project2Service.get(fileAndProject.project);
if (!service) {
throw 'No RequestContext';
}
throw 'No RequestContext';
return {
typescript: ts,
languageService: service[2],
languageServiceHost: service[1],
language: service[0],
isTsPlugin: true,
getFileId: (fileName: string) => fileName,
};
}
}
);

export = plugin;

0 comments on commit 82ff71d

Please sign in to comment.