Skip to content

Commit a7ac323

Browse files
committed
feat(typescript-plugin): skip declaration files in goto components definition (#5221)
1 parent 2ca7bb6 commit a7ac323

File tree

2 files changed

+119
-3
lines changed

2 files changed

+119
-3
lines changed

packages/language-service/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export function getFullLanguageServicePlugins(
8383
languageService.getCompletionsAtPosition = proxy.getCompletionsAtPosition;
8484
languageService.getCompletionEntryDetails = proxy.getCompletionEntryDetails;
8585
languageService.getCodeFixesAtPosition = proxy.getCodeFixesAtPosition;
86+
languageService.getDefinitionAndBoundSpan = proxy.getDefinitionAndBoundSpan;
8687
languageService.getQuickInfoAtPosition = proxy.getQuickInfoAtPosition;
8788
}
8889
return created;

packages/typescript-plugin/lib/common.ts

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export function proxyLanguageServiceForVue<T>(
1919
case 'getCompletionsAtPosition': return getCompletionsAtPosition(vueOptions, target[p]);
2020
case 'getCompletionEntryDetails': return getCompletionEntryDetails(language, asScriptId, target[p]);
2121
case 'getCodeFixesAtPosition': return getCodeFixesAtPosition(target[p]);
22+
case 'getDefinitionAndBoundSpan': return getDefinitionAndBoundSpan(ts, language, languageService, vueOptions, asScriptId, target[p]);
2223
case 'getQuickInfoAtPosition': return getQuickInfoAtPosition(ts, target, target[p]);
2324
// TS plugin only
2425
case 'getEncodedSemanticClassifications': return getEncodedSemanticClassifications(ts, language, target, asScriptId, target[p]);
@@ -91,7 +92,11 @@ function getCompletionsAtPosition(vueOptions: VueCompilerOptions, getCompletions
9192
};
9293
}
9394

94-
function getCompletionEntryDetails<T>(language: Language<T>, asScriptId: (fileName: string) => T, getCompletionEntryDetails: ts.LanguageService['getCompletionEntryDetails']): ts.LanguageService['getCompletionEntryDetails'] {
95+
function getCompletionEntryDetails<T>(
96+
language: Language<T>,
97+
asScriptId: (fileName: string) => T,
98+
getCompletionEntryDetails: ts.LanguageService['getCompletionEntryDetails']
99+
): ts.LanguageService['getCompletionEntryDetails'] {
95100
return (...args) => {
96101
const details = getCompletionEntryDetails(...args);
97102
// modify import statement
@@ -132,7 +137,9 @@ function getCompletionEntryDetails<T>(language: Language<T>, asScriptId: (fileNa
132137
};
133138
}
134139

135-
function getCodeFixesAtPosition(getCodeFixesAtPosition: ts.LanguageService['getCodeFixesAtPosition']): ts.LanguageService['getCodeFixesAtPosition'] {
140+
function getCodeFixesAtPosition(
141+
getCodeFixesAtPosition: ts.LanguageService['getCodeFixesAtPosition']
142+
): ts.LanguageService['getCodeFixesAtPosition'] {
136143
return (...args) => {
137144
let result = getCodeFixesAtPosition(...args);
138145
// filter __VLS_
@@ -141,7 +148,115 @@ function getCodeFixesAtPosition(getCodeFixesAtPosition: ts.LanguageService['getC
141148
};
142149
}
143150

144-
function getQuickInfoAtPosition(ts: typeof import('typescript'), languageService: ts.LanguageService, getQuickInfoAtPosition: ts.LanguageService['getQuickInfoAtPosition']): ts.LanguageService['getQuickInfoAtPosition'] {
151+
function getDefinitionAndBoundSpan<T>(
152+
ts: typeof import('typescript'),
153+
language: Language<T>,
154+
languageService: ts.LanguageService,
155+
vueOptions: VueCompilerOptions,
156+
asScriptId: (fileName: string) => T,
157+
getDefinitionAndBoundSpan: ts.LanguageService['getDefinitionAndBoundSpan']
158+
): ts.LanguageService['getDefinitionAndBoundSpan'] {
159+
return (fileName, position) => {
160+
const result = getDefinitionAndBoundSpan(fileName, position);
161+
if (!result?.definitions?.length) {
162+
return result;
163+
}
164+
165+
const program = languageService.getProgram()!;
166+
const sourceScript = language.scripts.get(asScriptId(fileName));
167+
if (!sourceScript?.generated) {
168+
return result;
169+
}
170+
171+
const root = sourceScript.generated.root;
172+
if (!(root instanceof VueVirtualCode)) {
173+
return result;
174+
}
175+
176+
if (
177+
!root.sfc.template
178+
|| position < root.sfc.template.startTagEnd
179+
|| position > root.sfc.template.endTagStart
180+
) {
181+
return result;
182+
}
183+
184+
const definitions = new Set<ts.DefinitionInfo>(result.definitions);
185+
const skippedDefinitions: ts.DefinitionInfo[] = [];
186+
187+
for (const definition of result.definitions) {
188+
if (vueOptions.extensions.some(ext => definition.fileName.endsWith(ext))) {
189+
continue;
190+
}
191+
192+
const sourceFile = program.getSourceFile(definition.fileName);
193+
if (!sourceFile) {
194+
continue;
195+
}
196+
197+
visit(sourceFile, definition, sourceFile);
198+
}
199+
200+
for (const definition of skippedDefinitions) {
201+
definitions.delete(definition);
202+
}
203+
204+
return {
205+
definitions: [...definitions],
206+
textSpan: result.textSpan,
207+
};
208+
209+
function visit(
210+
node: ts.Node,
211+
definition: ts.DefinitionInfo,
212+
sourceFile: ts.SourceFile
213+
) {
214+
if (ts.isPropertySignature(node) && node.type) {
215+
proxy(node.name, node.type, definition, sourceFile);
216+
}
217+
else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.type && !node.initializer) {
218+
proxy(node.name, node.type, definition, sourceFile);
219+
}
220+
else {
221+
ts.forEachChild(node, child => visit(child, definition, sourceFile));
222+
}
223+
}
224+
225+
function proxy(
226+
name: ts.PropertyName,
227+
type: ts.TypeNode,
228+
definition: ts.DefinitionInfo,
229+
sourceFile: ts.SourceFile
230+
) {
231+
const { textSpan, fileName } = definition;
232+
const start = name.getStart(sourceFile);
233+
const end = name.getEnd();
234+
235+
if (start !== textSpan.start || end - start !== textSpan.length) {
236+
return;
237+
}
238+
239+
if (!ts.isIndexedAccessTypeNode(type)) {
240+
return;
241+
}
242+
243+
const pos = type.indexType.getStart(sourceFile);
244+
const res = getDefinitionAndBoundSpan(fileName, pos);
245+
if (res?.definitions?.length) {
246+
for (const definition of res.definitions) {
247+
definitions.add(definition);
248+
}
249+
skippedDefinitions.push(definition);
250+
}
251+
}
252+
}
253+
}
254+
255+
function getQuickInfoAtPosition(
256+
ts: typeof import('typescript'),
257+
languageService: ts.LanguageService,
258+
getQuickInfoAtPosition: ts.LanguageService['getQuickInfoAtPosition']
259+
): ts.LanguageService['getQuickInfoAtPosition'] {
145260
return (...args) => {
146261
const result = getQuickInfoAtPosition(...args);
147262
if (result && result.documentation?.length === 1 && result.documentation[0].text.startsWith('__VLS_emit,')) {

0 commit comments

Comments
 (0)