Skip to content

Commit d007a9c

Browse files
authored
feat(language-service): support tsconfig path alias resolution for document links (#5562)
1 parent 23a14ce commit d007a9c

8 files changed

Lines changed: 118 additions & 14 deletions

File tree

packages/language-server/lib/server.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ export function startServer(ts: typeof import('typescript')) {
126126
isRefAtPosition(...args) {
127127
return sendTsServerRequest('_vue:isRefAtPosition', args);
128128
},
129+
resolveModuleName(...args) {
130+
return sendTsServerRequest('_vue:resolveModuleName', args);
131+
},
129132
getDocumentHighlights(fileName, position) {
130133
return sendTsServerRequest(
131134
'_vue:documentHighlights-full',

packages/language-service/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ export function createVueLanguageServicePlugins(
4141
}),
4242
) {
4343
return [
44-
createCssPlugin(),
4544
createJsonPlugin(),
4645
createPugFormatPlugin(),
4746
createVueAutoSpacePlugin(),
@@ -65,6 +64,7 @@ export function createVueLanguageServicePlugins(
6564
createVueInlayHintsPlugin(ts),
6665

6766
// type aware plugins
67+
createCssPlugin(client),
6868
createTypescriptSemanticTokensPlugin(client),
6969
createVueAutoDotValuePlugin(ts, client),
7070
createVueComponentSemanticTokensPlugin(client),

packages/language-service/lib/plugins/css.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
11
import type { LanguageServicePlugin, TextDocument, VirtualCode } from '@volar/language-service';
22
import { isRenameEnabled } from '@vue/language-core';
3-
import { create as baseCreate, type Provide } from 'volar-service-css';
3+
import { create as baseCreate, type Provide, resolveReference } from 'volar-service-css';
44
import type * as css from 'vscode-css-languageservice';
5-
import { resolveEmbeddedCode } from '../utils';
5+
import { createReferenceResolver, resolveEmbeddedCode } from '../utils';
66

7-
export function create(): LanguageServicePlugin {
8-
const base = baseCreate({ scssDocumentSelector: ['scss', 'postcss'] });
7+
export function create(
8+
{ resolveModuleName }: import('@vue/typescript-plugin/lib/requests').Requests,
9+
): LanguageServicePlugin {
10+
const baseService = baseCreate({
11+
getDocumentContext(context) {
12+
return {
13+
resolveReference: createReferenceResolver(context, resolveReference, resolveModuleName) as any,
14+
};
15+
},
16+
scssDocumentSelector: ['scss', 'postcss'],
17+
});
918
return {
10-
...base,
19+
...baseService,
1120
create(context) {
12-
const baseInstance = base.create(context);
21+
const baseServiceInstance = baseService.create(context);
1322
const {
1423
'css/languageService': getCssLs,
1524
'css/stylesheet': getStylesheet,
16-
} = baseInstance.provide as Provide;
25+
} = baseServiceInstance.provide as Provide;
1726

1827
return {
19-
...baseInstance,
28+
...baseServiceInstance,
2029
async provideDiagnostics(document, token) {
21-
let diagnostics = await baseInstance.provideDiagnostics?.(document, token) ?? [];
30+
let diagnostics = await baseServiceInstance.provideDiagnostics?.(document, token) ?? [];
2231
if (document.languageId === 'postcss') {
2332
diagnostics = diagnostics.filter(diag =>
2433
diag.code !== 'css-semicolonexpected'

packages/language-service/lib/plugins/vue-template.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ import {
1313
tsCodegen,
1414
type VueVirtualCode,
1515
} from '@vue/language-core';
16-
import { camelize, capitalize } from '@vue/shared';
16+
import { camelize, capitalize, isPromise } from '@vue/shared';
1717
import type { ComponentPropInfo } from '@vue/typescript-plugin/lib/requests/getComponentProps';
18-
import { create as createHtmlService } from 'volar-service-html';
18+
import { create as createHtmlService, resolveReference } from 'volar-service-html';
1919
import { create as createPugService } from 'volar-service-pug';
2020
import * as html from 'vscode-html-languageservice';
2121
import { URI, Utils } from 'vscode-uri';
2222
import { loadModelModifiersData, loadTemplateData } from '../data';
2323
import { AttrNameCasing, checkCasing, TagNameCasing } from '../nameCasing';
24-
import { resolveEmbeddedCode } from '../utils';
24+
import { createReferenceResolver, resolveEmbeddedCode } from '../utils';
2525

2626
const specialTags = new Set([
2727
'slot',
@@ -45,11 +45,12 @@ export function create(
4545
languageId: 'html' | 'jade',
4646
{
4747
getComponentNames,
48-
getElementAttrs,
4948
getComponentProps,
5049
getComponentEvents,
5150
getComponentDirectives,
5251
getComponentSlots,
52+
getElementAttrs,
53+
resolveModuleName,
5354
}: import('@vue/typescript-plugin/lib/requests').Requests,
5455
): LanguageServicePlugin {
5556
let customData: html.IHTMLDataProvider[] = [];
@@ -78,6 +79,11 @@ export function create(
7879
: createHtmlService({
7980
documentSelector: ['html', 'markdown'],
8081
useDefaultDataProvider: false,
82+
getDocumentContext(context) {
83+
return {
84+
resolveReference: createReferenceResolver(context, resolveReference, resolveModuleName) as any,
85+
};
86+
},
8187
getCustomData() {
8288
return [
8389
...customData,
@@ -362,6 +368,25 @@ export function create(
362368

363369
return baseServiceInstance.provideHover?.(document, position, token);
364370
},
371+
372+
async provideDocumentLinks(document, token) {
373+
if (document.languageId !== languageId) {
374+
return;
375+
}
376+
const info = resolveEmbeddedCode(context, document.uri);
377+
if (info?.code.id !== 'template') {
378+
return;
379+
}
380+
381+
const documentLinks = await baseServiceInstance.provideDocumentLinks?.(document, token) ?? [];
382+
for (const link of documentLinks) {
383+
if (link.target && isPromise(link.target)) {
384+
link.target = await link.target;
385+
}
386+
}
387+
388+
return documentLinks;
389+
},
365390
};
366391

367392
async function runWithVueData<T>(sourceDocumentUri: URI, root: VueVirtualCode, fn: () => T) {

packages/language-service/lib/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,23 @@ export function resolveEmbeddedCode(
1919
root: sourceScript.generated!.root as VueVirtualCode,
2020
};
2121
}
22+
23+
export function createReferenceResolver(
24+
context: LanguageServiceContext,
25+
resolveReference: typeof import('volar-service-html').resolveReference,
26+
resolveModuleName: import('@vue/typescript-plugin/lib/requests').Requests['resolveModuleName'],
27+
) {
28+
return async (ref: string, base: string) => {
29+
let uri = URI.parse(base);
30+
const decoded = context.decodeEmbeddedDocumentUri(uri);
31+
if (decoded) {
32+
uri = decoded[0];
33+
}
34+
35+
let moduleName: string | null | undefined;
36+
if (!ref.startsWith('./') && !ref.startsWith('../')) {
37+
moduleName = await resolveModuleName(uri.fsPath, ref);
38+
}
39+
return moduleName ?? resolveReference(ref, uri, context.env.workspaceFolders);
40+
};
41+
}

packages/typescript-plugin/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { getElementAttrs } from './lib/requests/getElementAttrs';
1313
import { getElementNames } from './lib/requests/getElementNames';
1414
import { getImportPathForFile } from './lib/requests/getImportPathForFile';
1515
import { isRefAtPosition } from './lib/requests/isRefAtPosition';
16+
import { resolveModuleName } from './lib/requests/resolveModuleName';
1617

1718
export = createLanguageServicePlugin(
1819
(ts, info) => {
@@ -170,6 +171,10 @@ export = createLanguageServicePlugin(
170171
const { project } = getProject(fileName);
171172
return createResponse(getElementNames(ts, project.getLanguageService().getProgram()!));
172173
});
174+
session.addProtocolHandler('_vue:resolveModuleName', request => {
175+
const [fileName, moduleName]: Parameters<Requests['resolveModuleName']> = request.arguments;
176+
return createResponse(resolveModuleName(ts, info.languageServiceHost, fileName, moduleName));
177+
});
173178

174179
projectService.logger.info('Vue specific commands are successfully added.');
175180

packages/typescript-plugin/lib/requests/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export interface Requests {
4040
getElementNames(
4141
fileName: string,
4242
): Response<ReturnType<typeof import('./getElementNames.js')['getElementNames']>>;
43+
resolveModuleName(
44+
fileName: string,
45+
moduleName: string,
46+
): Response<ReturnType<typeof import('./resolveModuleName.js')['resolveModuleName']>>;
4347
getDocumentHighlights(
4448
fileName: string,
4549
position: number,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type * as ts from 'typescript';
2+
3+
export function resolveModuleName(
4+
ts: typeof import('typescript'),
5+
languageServiceHost: ts.LanguageServiceHost,
6+
fileName: string,
7+
moduleName: string,
8+
): string | undefined {
9+
const compilerOptions = languageServiceHost.getCompilationSettings();
10+
11+
const ext = moduleName.split('.').pop();
12+
const result = ts.resolveModuleName(
13+
moduleName,
14+
fileName,
15+
{
16+
...compilerOptions,
17+
allowArbitraryExtensions: true,
18+
},
19+
{
20+
fileExists(fileName) {
21+
fileName = transformFileName(fileName, ext);
22+
return languageServiceHost.fileExists(fileName);
23+
},
24+
} as ts.ModuleResolutionHost,
25+
);
26+
27+
const resolveFileName = result.resolvedModule?.resolvedFileName;
28+
if (resolveFileName) {
29+
return transformFileName(resolveFileName, ext);
30+
}
31+
}
32+
33+
function transformFileName(fileName: string, ext: string | undefined) {
34+
if (ext && fileName.endsWith(`.d.${ext}.ts`)) {
35+
return fileName.slice(0, -`.d.${ext}.ts`.length) + `.${ext}`;
36+
}
37+
return fileName;
38+
}

0 commit comments

Comments
 (0)