Skip to content

Commit 82ff71d

Browse files
committed
fix(language-server): make sure vueCompilerOptions are consistent between language server and ts plugin
1 parent 1b25cb6 commit 82ff71d

File tree

4 files changed

+146
-150
lines changed

4 files changed

+146
-150
lines changed

extensions/vscode/src/nodeClientMain.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,17 @@ export const { activate, deactivate } = defineExtension(async () => {
9191
if (!tsserver) {
9292
return;
9393
}
94-
const res = await tsserver.executeImpl(command, args, {
95-
isAsync: true,
96-
expectsResult: true,
97-
lowPriority: true,
98-
requireSemantic: true,
99-
})[0];
100-
return res.body;
94+
try {
95+
const res = await tsserver.executeImpl(command, args, {
96+
isAsync: true,
97+
expectsResult: true,
98+
lowPriority: true,
99+
requireSemantic: true,
100+
})[0];
101+
return res.body;
102+
} catch {
103+
// noop
104+
}
101105
});
102106

103107
return client;

packages/language-server/lib/hybridModeProject.ts

Lines changed: 0 additions & 80 deletions
This file was deleted.

packages/language-server/node.ts

Lines changed: 113 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import type { LanguageServer } from '@volar/language-server';
2+
import { createLanguageServiceEnvironment } from '@volar/language-server/lib/project/simpleProject';
13
import { createConnection, createServer, loadTsdkByPath } from '@volar/language-server/node';
2-
import { createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core';
3-
import { getHybridModeLanguageServicePlugins } from '@vue/language-service';
4-
import { createHybridModeProject } from './lib/hybridModeProject';
4+
import { createLanguage, createParsedCommandLine, createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core';
5+
import { createLanguageService, createUriMap, getHybridModeLanguageServicePlugins, LanguageService } from '@vue/language-service';
6+
import type * as ts from 'typescript';
7+
import { URI } from 'vscode-uri';
58
import type { VueInitializationOptions } from './lib/types';
69

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

2225
const { typescript: ts } = loadTsdkByPath(options.typescript.tsdk, params.locale);
26+
const tsconfigProjects = createUriMap<LanguageService>();
27+
28+
server.fileWatcher.onDidChangeWatchedFiles(({ changes }) => {
29+
for (const change of changes) {
30+
const changeUri = URI.parse(change.uri);
31+
if (tsconfigProjects.has(changeUri)) {
32+
tsconfigProjects.get(changeUri)!.dispose();
33+
tsconfigProjects.delete(changeUri);
34+
}
35+
}
36+
});
37+
38+
let simpleLs: LanguageService | undefined;
39+
2340
return server.initialize(
2441
params,
25-
createHybridModeProject(
26-
() => {
27-
const commandLine = {
28-
vueOptions: getDefaultCompilerOptions(),
29-
options: ts.getDefaultCompilerOptions(),
30-
};
31-
return {
32-
languagePlugins: [
33-
createVueLanguagePlugin(
34-
ts,
35-
commandLine.options,
36-
commandLine.vueOptions,
37-
uri => uri.fsPath.replace(/\\/g, '/')
38-
),
39-
],
40-
setup({ project }) {
41-
project.vue = { compilerOptions: commandLine.vueOptions };
42-
},
43-
};
44-
}
45-
),
42+
{
43+
setup() { },
44+
async getLanguageService(uri) {
45+
if (uri.scheme === 'file' && options.typescript.requestForwardingCommand) {
46+
const fileName = uri.fsPath.replace(/\\/g, '/');
47+
const projectInfo = await sendTsRequest<ts.server.protocol.ProjectInfo>(
48+
ts.server.protocol.CommandTypes.ProjectInfo,
49+
{
50+
file: fileName,
51+
needFileNameList: false,
52+
} satisfies ts.server.protocol.ProjectInfoRequestArgs
53+
);
54+
if (projectInfo) {
55+
const { configFileName } = projectInfo;
56+
let ls = tsconfigProjects.get(URI.file(configFileName));
57+
if (!ls) {
58+
ls = createLs(server, configFileName);
59+
tsconfigProjects.set(URI.file(configFileName), ls);
60+
}
61+
return ls;
62+
}
63+
}
64+
return simpleLs ??= createLs(server, undefined);
65+
},
66+
getExistingLanguageServices() {
67+
return Promise.all([
68+
...tsconfigProjects.values(),
69+
simpleLs,
70+
].filter(promise => !!promise));
71+
},
72+
reload() {
73+
for (const ls of [
74+
...tsconfigProjects.values(),
75+
simpleLs,
76+
]) {
77+
ls?.dispose();
78+
}
79+
tsconfigProjects.clear();
80+
simpleLs = undefined;
81+
},
82+
},
4683
getHybridModeLanguageServicePlugins(ts, options.typescript.requestForwardingCommand ? {
4784
collectExtractProps(...args) {
48-
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:collectExtractProps', args]);
85+
return sendTsRequest('vue:collectExtractProps', args);
4986
},
5087
getComponentDirectives(...args) {
51-
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentDirectives', args]);
88+
return sendTsRequest('vue:getComponentDirectives', args);
5289
},
5390
getComponentEvents(...args) {
54-
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentEvents', args]);
91+
return sendTsRequest('vue:getComponentEvents', args);
5592
},
5693
getComponentNames(...args) {
57-
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentNames', args]);
94+
return sendTsRequest('vue:getComponentNames', args);
5895
},
5996
getComponentProps(...args) {
60-
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentProps', args]);
97+
return sendTsRequest('vue:getComponentProps', args);
6198
},
6299
getElementAttrs(...args) {
63-
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getElementAttrs', args]);
100+
return sendTsRequest('vue:getElementAttrs', args);
64101
},
65102
getElementNames(...args) {
66-
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getElementNames', args]);
103+
return sendTsRequest('vue:getElementNames', args);
67104
},
68105
getImportPathForFile(...args) {
69-
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getImportPathForFile', args]);
106+
return sendTsRequest('vue:getImportPathForFile', args);
70107
},
71108
getPropertiesAtLocation(...args) {
72-
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getPropertiesAtLocation', args]);
109+
return sendTsRequest('vue:getPropertiesAtLocation', args);
73110
},
74111
getQuickInfoAtPosition(...args) {
75-
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getQuickInfoAtPosition', args]);
112+
return sendTsRequest('vue:getQuickInfoAtPosition', args);
76113
},
77114
} : undefined)
78115
);
116+
117+
function sendTsRequest<T>(command: string, args: any): Promise<T | null> {
118+
return connection.sendRequest<T>(options.typescript.requestForwardingCommand!, [command, args]);
119+
}
120+
121+
function createLs(server: LanguageServer, tsconfig: string | undefined) {
122+
const commonLine = tsconfig
123+
? createParsedCommandLine(ts, ts.sys, tsconfig)
124+
: {
125+
options: ts.getDefaultCompilerOptions(),
126+
vueOptions: getDefaultCompilerOptions(),
127+
};
128+
const language = createLanguage<URI>(
129+
[
130+
{
131+
getLanguageId: uri => server.documents.get(uri)?.languageId,
132+
},
133+
createVueLanguagePlugin(
134+
ts,
135+
commonLine.options,
136+
commonLine.vueOptions,
137+
uri => uri.fsPath.replace(/\\/g, '/')
138+
),
139+
],
140+
createUriMap(),
141+
uri => {
142+
const document = server.documents.get(uri);
143+
if (document) {
144+
language.scripts.set(uri, document.getSnapshot(), document.languageId);
145+
}
146+
else {
147+
language.scripts.delete(uri);
148+
}
149+
}
150+
);
151+
return createLanguageService(
152+
language,
153+
server.languageServicePlugins,
154+
createLanguageServiceEnvironment(server, [...server.workspaceFolders.all]),
155+
{ vue: { compilerOptions: commonLine.vueOptions } }
156+
);
157+
}
79158
});
80159

81160
connection.onInitialized(server.initialized);

packages/typescript-plugin/index.ts

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import { getQuickInfoAtPosition } from './lib/requests/getQuickInfoAtPosition';
1515
import type { RequestContext } from './lib/requests/types';
1616

1717
const windowsPathReg = /\\/g;
18-
const project2Language = new Map<ts.server.Project, [vue.Language, ts.LanguageServiceHost, ts.LanguageService]>();
19-
const plugin = createLanguageServicePlugin(
18+
const project2Service = new WeakMap<ts.server.Project, [vue.Language, ts.LanguageServiceHost, ts.LanguageService]>();
19+
20+
export = createLanguageServicePlugin(
2021
(ts, info) => {
2122
const vueOptions = getVueCompilerOptions();
2223
const languagePlugin = vue.createVueLanguagePlugin<string>(
@@ -31,7 +32,7 @@ const plugin = createLanguageServicePlugin(
3132
return {
3233
languagePlugins: [languagePlugin],
3334
setup: language => {
34-
project2Language.set(info.project, [language, info.languageServiceHost, info.languageService]);
35+
project2Service.set(info.project, [language, info.languageServiceHost, info.languageService]);
3536

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

@@ -131,33 +132,25 @@ const plugin = createLanguageServicePlugin(
131132
}
132133

133134
function getRequestContext(fileName: string): RequestContext {
134-
for (const [project, [language, languageServiceHost, languageService]] of project2Language) {
135-
if (project.projectKind === 1 satisfies ts.server.ProjectKind.Configured && project.containsFile(ts.server.toNormalizedPath(fileName))) {
136-
return {
137-
typescript: ts,
138-
languageService: languageService,
139-
languageServiceHost: languageServiceHost,
140-
language: language,
141-
isTsPlugin: true,
142-
getFileId: (fileName: string) => fileName,
143-
};
144-
}
145-
}
146-
for (const [project, [language, languageServiceHost, languageService]] of project2Language) {
147-
if (project.projectKind === 0 satisfies ts.server.ProjectKind.Inferred) {
148-
return {
149-
typescript: ts,
150-
languageService: languageService,
151-
languageServiceHost: languageServiceHost,
152-
language: language,
153-
isTsPlugin: true,
154-
getFileId: (fileName: string) => fileName,
155-
};
156-
}
135+
const fileAndProject = (info.session as any).getFileAndProject({
136+
file: fileName,
137+
projectFileName: undefined,
138+
}) as {
139+
file: ts.server.NormalizedPath;
140+
project: ts.server.Project;
141+
};
142+
const service = project2Service.get(fileAndProject.project);
143+
if (!service) {
144+
throw 'No RequestContext';
157145
}
158-
throw 'No RequestContext';
146+
return {
147+
typescript: ts,
148+
languageService: service[2],
149+
languageServiceHost: service[1],
150+
language: service[0],
151+
isTsPlugin: true,
152+
getFileId: (fileName: string) => fileName,
153+
};
159154
}
160155
}
161156
);
162-
163-
export = plugin;

0 commit comments

Comments
 (0)