Skip to content

Commit 07db66a

Browse files
committed
fix(language-service): properly handle promise when resolving CSS links
close #5785
1 parent 52e3d5e commit 07db66a

File tree

3 files changed

+105
-39
lines changed

3 files changed

+105
-39
lines changed

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

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,50 @@ import type { LanguageServicePlugin, TextDocument, VirtualCode } from '@volar/la
22
import { isRenameEnabled } from '@vue/language-core';
33
import { create as baseCreate, type Provide, resolveReference } from 'volar-service-css';
44
import type * as css from 'vscode-css-languageservice';
5-
import { createReferenceResolver, resolveEmbeddedCode } from '../utils';
5+
import { URI } from 'vscode-uri';
6+
import { resolveEmbeddedCode } from '../utils';
67

78
export function create(
89
{ resolveModuleName }: import('@vue/typescript-plugin/lib/requests').Requests,
910
): LanguageServicePlugin {
11+
let modulePathCache:
12+
| Map<string, Promise<string | null | undefined> | string | null | undefined>
13+
| undefined;
14+
1015
const baseService = baseCreate({
1116
getDocumentContext(context) {
1217
return {
13-
resolveReference: createReferenceResolver(context, resolveReference, resolveModuleName) as any,
18+
resolveReference(ref, base) {
19+
let baseUri = URI.parse(base);
20+
const decoded = context.decodeEmbeddedDocumentUri(baseUri);
21+
if (decoded) {
22+
baseUri = decoded[0];
23+
}
24+
if (
25+
modulePathCache
26+
&& baseUri.scheme === 'file'
27+
&& !ref.startsWith('./')
28+
&& !ref.startsWith('../')
29+
) {
30+
const map = modulePathCache;
31+
if (!map.has(ref)) {
32+
const fileName = baseUri.fsPath.replace(/\\/g, '/');
33+
const promise = resolveModuleName(fileName, ref);
34+
map.set(ref, promise);
35+
if (promise instanceof Promise) {
36+
promise.then(res => map.set(ref, res));
37+
}
38+
}
39+
const cached = modulePathCache.get(ref);
40+
if (cached instanceof Promise) {
41+
throw cached;
42+
}
43+
if (cached) {
44+
return cached;
45+
}
46+
}
47+
return resolveReference(ref, baseUri, context.env.workspaceFolders);
48+
},
1449
};
1550
},
1651
scssDocumentSelector: ['scss', 'postcss'],
@@ -57,6 +92,24 @@ export function create(
5792
return cssLs.prepareRename(document, position, stylesheet);
5893
});
5994
},
95+
async provideDocumentLinks(document, token) {
96+
modulePathCache = new Map();
97+
while (true) {
98+
try {
99+
const result = await baseServiceInstance.provideDocumentLinks?.(document, token);
100+
modulePathCache = undefined;
101+
return result;
102+
}
103+
catch (e) {
104+
if (e instanceof Promise) {
105+
await e;
106+
}
107+
else {
108+
throw e;
109+
}
110+
}
111+
}
112+
},
60113
};
61114

62115
function isWithinNavigationVirtualCode(

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

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
tsCodegen,
1414
type VueVirtualCode,
1515
} from '@vue/language-core';
16-
import { camelize, capitalize, isPromise } from '@vue/shared';
16+
import { camelize, capitalize } from '@vue/shared';
1717
import type { ComponentPropInfo } from '@vue/typescript-plugin/lib/requests/getComponentProps';
1818
import { create as createHtmlService, resolveReference } from 'volar-service-html';
1919
import { create as createPugService } from 'volar-service-pug';
@@ -22,7 +22,7 @@ import { URI, Utils } from 'vscode-uri';
2222
import { loadModelModifiersData, loadTemplateData } from '../data';
2323
import { format } from '../htmlFormatter';
2424
import { AttrNameCasing, getAttrNameCasing, getTagNameCasing, TagNameCasing } from '../nameCasing';
25-
import { createReferenceResolver, resolveEmbeddedCode } from '../utils';
25+
import { resolveEmbeddedCode } from '../utils';
2626

2727
const specialTags = new Set([
2828
'slot',
@@ -64,6 +64,9 @@ export function create(
6464
): LanguageServicePlugin {
6565
let customData: html.IHTMLDataProvider[] = [];
6666
let extraCustomData: html.IHTMLDataProvider[] = [];
67+
let modulePathCache:
68+
| Map<string, Promise<string | null | undefined> | string | null | undefined>
69+
| undefined;
6770

6871
const onDidChangeCustomDataListeners = new Set<() => void>();
6972
const onDidChangeCustomData = (listener: () => void): Disposable => {
@@ -90,7 +93,37 @@ export function create(
9093
useDefaultDataProvider: false,
9194
getDocumentContext(context) {
9295
return {
93-
resolveReference: createReferenceResolver(context, resolveReference, resolveModuleName) as any,
96+
resolveReference(ref, base) {
97+
let baseUri = URI.parse(base);
98+
const decoded = context.decodeEmbeddedDocumentUri(baseUri);
99+
if (decoded) {
100+
baseUri = decoded[0];
101+
}
102+
if (
103+
modulePathCache
104+
&& baseUri.scheme === 'file'
105+
&& !ref.startsWith('./')
106+
&& !ref.startsWith('../')
107+
) {
108+
const map = modulePathCache;
109+
if (!map.has(ref)) {
110+
const fileName = baseUri.fsPath.replace(/\\/g, '/');
111+
const promise = resolveModuleName(fileName, ref);
112+
map.set(ref, promise);
113+
if (promise instanceof Promise) {
114+
promise.then(res => map.set(ref, res));
115+
}
116+
}
117+
const cached = modulePathCache.get(ref);
118+
if (cached instanceof Promise) {
119+
throw cached;
120+
}
121+
if (cached) {
122+
return cached;
123+
}
124+
}
125+
return resolveReference(ref, baseUri, context.env.workspaceFolders);
126+
},
94127
};
95128
},
96129
getCustomData() {
@@ -418,22 +451,22 @@ export function create(
418451
},
419452

420453
async provideDocumentLinks(document, token) {
421-
if (document.languageId !== languageId) {
422-
return;
423-
}
424-
const info = resolveEmbeddedCode(context, document.uri);
425-
if (info?.code.id !== 'template') {
426-
return;
427-
}
428-
429-
const documentLinks = await baseServiceInstance.provideDocumentLinks?.(document, token) ?? [];
430-
for (const link of documentLinks) {
431-
if (link.target && isPromise(link.target)) {
432-
link.target = await link.target;
454+
modulePathCache = new Map();
455+
while (true) {
456+
try {
457+
const result = await baseServiceInstance.provideDocumentLinks?.(document, token);
458+
modulePathCache = undefined;
459+
return result;
460+
}
461+
catch (e) {
462+
if (e instanceof Promise) {
463+
await e;
464+
}
465+
else {
466+
throw e;
467+
}
433468
}
434469
}
435-
436-
return documentLinks;
437470
},
438471
};
439472

packages/language-service/lib/utils.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,3 @@ 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-
}

0 commit comments

Comments
 (0)