Skip to content

Commit 2ec87e5

Browse files
committed
refactor(typescript-plugin): move functions into postprocessLanguageService
1 parent 732cb86 commit 2ec87e5

File tree

1 file changed

+173
-183
lines changed

1 file changed

+173
-183
lines changed

packages/typescript-plugin/lib/common.ts

Lines changed: 173 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,13 @@ export function postprocessLanguageService<T>(
189189
const getProxyMethod = (target: ts.LanguageService, p: string | symbol): Function | undefined => {
190190
switch (p) {
191191
case 'getCompletionsAtPosition':
192-
return getCompletionsAtPosition(ts, language, asScriptId, vueOptions, target[p]);
192+
return getCompletionsAtPosition(target[p]);
193193
case 'getCompletionEntryDetails':
194-
return getCompletionEntryDetails(language, target[p]);
194+
return getCompletionEntryDetails(target[p]);
195195
case 'getCodeFixesAtPosition':
196196
return getCodeFixesAtPosition(target[p]);
197197
case 'getDefinitionAndBoundSpan':
198-
return getDefinitionAndBoundSpan(ts, language, asScriptId, languageService, vueOptions, target[p]);
198+
return getDefinitionAndBoundSpan(target[p]);
199199
}
200200
};
201201

@@ -214,44 +214,178 @@ export function postprocessLanguageService<T>(
214214
return Reflect.set(target, p, value, receiver);
215215
},
216216
});
217-
}
218217

219-
function getCompletionsAtPosition<T>(
220-
ts: typeof import('typescript'),
221-
language: Language<T>,
222-
asScriptId: (fileName: string) => T,
223-
vueOptions: VueCompilerOptions,
224-
getCompletionsAtPosition: ts.LanguageService['getCompletionsAtPosition'],
225-
): ts.LanguageService['getCompletionsAtPosition'] {
226-
return (filePath, position, options, formattingSettings) => {
227-
const fileName = filePath.replace(windowsPathReg, '/');
228-
const result = getCompletionsAtPosition(fileName, position, options, formattingSettings);
229-
if (result) {
230-
resolveCompletionResult(
231-
ts,
232-
language,
233-
asScriptId,
234-
vueOptions,
235-
fileName,
236-
position,
237-
result,
238-
);
239-
}
240-
return result;
241-
};
242-
}
218+
function getCompletionsAtPosition(
219+
getCompletionsAtPosition: ts.LanguageService['getCompletionsAtPosition'],
220+
): ts.LanguageService['getCompletionsAtPosition'] {
221+
return (filePath, position, options, formattingSettings) => {
222+
const fileName = filePath.replace(windowsPathReg, '/');
223+
const result = getCompletionsAtPosition(fileName, position, options, formattingSettings);
224+
if (result) {
225+
resolveCompletionResult(
226+
ts,
227+
language,
228+
asScriptId,
229+
vueOptions,
230+
fileName,
231+
position,
232+
result,
233+
);
234+
}
235+
return result;
236+
};
237+
}
243238

244-
function getCompletionEntryDetails<T>(
245-
language: Language<T>,
246-
getCompletionEntryDetails: ts.LanguageService['getCompletionEntryDetails'],
247-
): ts.LanguageService['getCompletionEntryDetails'] {
248-
return (...args) => {
249-
const details = getCompletionEntryDetails(...args);
250-
if (details) {
251-
resolveCompletionEntryDetails(language, details, args[6]);
252-
}
253-
return details;
254-
};
239+
function getCompletionEntryDetails(
240+
getCompletionEntryDetails: ts.LanguageService['getCompletionEntryDetails'],
241+
): ts.LanguageService['getCompletionEntryDetails'] {
242+
return (...args) => {
243+
const details = getCompletionEntryDetails(...args);
244+
if (details) {
245+
resolveCompletionEntryDetails(language, details, args[6]);
246+
}
247+
return details;
248+
};
249+
}
250+
251+
function getCodeFixesAtPosition(
252+
getCodeFixesAtPosition: ts.LanguageService['getCodeFixesAtPosition'],
253+
): ts.LanguageService['getCodeFixesAtPosition'] {
254+
return (...args) => {
255+
let result = getCodeFixesAtPosition(...args);
256+
// filter __VLS_
257+
result = result.filter(entry => !entry.description.includes('__VLS_'));
258+
return result;
259+
};
260+
}
261+
262+
function getDefinitionAndBoundSpan(
263+
getDefinitionAndBoundSpan: ts.LanguageService['getDefinitionAndBoundSpan'],
264+
): ts.LanguageService['getDefinitionAndBoundSpan'] {
265+
return (fileName, position) => {
266+
const result = getDefinitionAndBoundSpan(fileName, position);
267+
268+
const program = languageService.getProgram()!;
269+
const sourceScript = language.scripts.get(asScriptId(fileName));
270+
const root = sourceScript?.generated?.root;
271+
if (!(root instanceof VueVirtualCode)) {
272+
return result;
273+
}
274+
275+
if (!result?.definitions?.length) {
276+
const { template } = root.sfc;
277+
if (template) {
278+
const textSpan = {
279+
start: template.start + 1,
280+
length: 'template'.length,
281+
};
282+
if (position >= textSpan.start && position <= textSpan.start + textSpan.length) {
283+
return {
284+
textSpan,
285+
definitions: [{
286+
fileName,
287+
textSpan,
288+
kind: ts.ScriptElementKind.scriptElement,
289+
name: fileName,
290+
containerKind: ts.ScriptElementKind.unknown,
291+
containerName: '',
292+
}],
293+
};
294+
}
295+
}
296+
return;
297+
}
298+
299+
if (
300+
!root.sfc.template
301+
|| position < root.sfc.template.startTagEnd
302+
|| position > root.sfc.template.endTagStart
303+
) {
304+
return result;
305+
}
306+
307+
const definitions = new Set<ts.DefinitionInfo>(result.definitions);
308+
const skippedDefinitions: ts.DefinitionInfo[] = [];
309+
310+
// #5275
311+
if (result.definitions.length >= 2) {
312+
for (const definition of result.definitions) {
313+
if (
314+
root.sfc.content[definition.textSpan.start - 1] === '@'
315+
|| root.sfc.content.slice(definition.textSpan.start - 5, definition.textSpan.start) === 'v-on:'
316+
) {
317+
definitions.delete(definition);
318+
}
319+
}
320+
}
321+
322+
for (const definition of result.definitions) {
323+
if (vueOptions.extensions.some(ext => definition.fileName.endsWith(ext))) {
324+
continue;
325+
}
326+
327+
const sourceFile = program.getSourceFile(definition.fileName);
328+
if (!sourceFile) {
329+
continue;
330+
}
331+
332+
visit(sourceFile, definition, sourceFile);
333+
}
334+
335+
for (const definition of skippedDefinitions) {
336+
definitions.delete(definition);
337+
}
338+
339+
return {
340+
definitions: [...definitions],
341+
textSpan: result.textSpan,
342+
};
343+
344+
function visit(
345+
node: ts.Node,
346+
definition: ts.DefinitionInfo,
347+
sourceFile: ts.SourceFile,
348+
) {
349+
if (ts.isPropertySignature(node) && node.type) {
350+
proxy(node.name, node.type, definition, sourceFile);
351+
}
352+
else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.type && !node.initializer) {
353+
proxy(node.name, node.type, definition, sourceFile);
354+
}
355+
else {
356+
ts.forEachChild(node, child => visit(child, definition, sourceFile));
357+
}
358+
}
359+
360+
function proxy(
361+
name: ts.PropertyName,
362+
type: ts.TypeNode,
363+
definition: ts.DefinitionInfo,
364+
sourceFile: ts.SourceFile,
365+
) {
366+
const { textSpan, fileName } = definition;
367+
const start = name.getStart(sourceFile);
368+
const end = name.getEnd();
369+
370+
if (start !== textSpan.start || end - start !== textSpan.length) {
371+
return;
372+
}
373+
374+
if (!ts.isIndexedAccessTypeNode(type)) {
375+
return;
376+
}
377+
378+
const pos = type.indexType.getStart(sourceFile);
379+
const res = getDefinitionAndBoundSpan(fileName, pos);
380+
if (res?.definitions?.length) {
381+
for (const definition of res.definitions) {
382+
definitions.add(definition);
383+
}
384+
skippedDefinitions.push(definition);
385+
}
386+
}
387+
};
388+
}
255389
}
256390

257391
export function resolveCompletionResult<T>(
@@ -375,147 +509,3 @@ export function resolveCompletionEntryDetails(
375509
}
376510
}
377511
}
378-
379-
function getCodeFixesAtPosition(
380-
getCodeFixesAtPosition: ts.LanguageService['getCodeFixesAtPosition'],
381-
): ts.LanguageService['getCodeFixesAtPosition'] {
382-
return (...args) => {
383-
let result = getCodeFixesAtPosition(...args);
384-
// filter __VLS_
385-
result = result.filter(entry => !entry.description.includes('__VLS_'));
386-
return result;
387-
};
388-
}
389-
390-
function getDefinitionAndBoundSpan<T>(
391-
ts: typeof import('typescript'),
392-
language: Language<T>,
393-
asScriptId: (fileName: string) => T,
394-
languageService: ts.LanguageService,
395-
vueOptions: VueCompilerOptions,
396-
getDefinitionAndBoundSpan: ts.LanguageService['getDefinitionAndBoundSpan'],
397-
): ts.LanguageService['getDefinitionAndBoundSpan'] {
398-
return (fileName, position) => {
399-
const result = getDefinitionAndBoundSpan(fileName, position);
400-
401-
const program = languageService.getProgram()!;
402-
const sourceScript = language.scripts.get(asScriptId(fileName));
403-
const root = sourceScript?.generated?.root;
404-
if (!(root instanceof VueVirtualCode)) {
405-
return result;
406-
}
407-
408-
if (!result?.definitions?.length) {
409-
const { template } = root.sfc;
410-
if (template) {
411-
const textSpan = {
412-
start: template.start + 1,
413-
length: 'template'.length,
414-
};
415-
if (position >= textSpan.start && position <= textSpan.start + textSpan.length) {
416-
return {
417-
textSpan,
418-
definitions: [{
419-
fileName,
420-
textSpan,
421-
kind: ts.ScriptElementKind.scriptElement,
422-
name: fileName,
423-
containerKind: ts.ScriptElementKind.unknown,
424-
containerName: '',
425-
}],
426-
};
427-
}
428-
}
429-
return;
430-
}
431-
432-
if (
433-
!root.sfc.template
434-
|| position < root.sfc.template.startTagEnd
435-
|| position > root.sfc.template.endTagStart
436-
) {
437-
return result;
438-
}
439-
440-
const definitions = new Set<ts.DefinitionInfo>(result.definitions);
441-
const skippedDefinitions: ts.DefinitionInfo[] = [];
442-
443-
// #5275
444-
if (result.definitions.length >= 2) {
445-
for (const definition of result.definitions) {
446-
if (
447-
root.sfc.content[definition.textSpan.start - 1] === '@'
448-
|| root.sfc.content.slice(definition.textSpan.start - 5, definition.textSpan.start) === 'v-on:'
449-
) {
450-
definitions.delete(definition);
451-
}
452-
}
453-
}
454-
455-
for (const definition of result.definitions) {
456-
if (vueOptions.extensions.some(ext => definition.fileName.endsWith(ext))) {
457-
continue;
458-
}
459-
460-
const sourceFile = program.getSourceFile(definition.fileName);
461-
if (!sourceFile) {
462-
continue;
463-
}
464-
465-
visit(sourceFile, definition, sourceFile);
466-
}
467-
468-
for (const definition of skippedDefinitions) {
469-
definitions.delete(definition);
470-
}
471-
472-
return {
473-
definitions: [...definitions],
474-
textSpan: result.textSpan,
475-
};
476-
477-
function visit(
478-
node: ts.Node,
479-
definition: ts.DefinitionInfo,
480-
sourceFile: ts.SourceFile,
481-
) {
482-
if (ts.isPropertySignature(node) && node.type) {
483-
proxy(node.name, node.type, definition, sourceFile);
484-
}
485-
else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.type && !node.initializer) {
486-
proxy(node.name, node.type, definition, sourceFile);
487-
}
488-
else {
489-
ts.forEachChild(node, child => visit(child, definition, sourceFile));
490-
}
491-
}
492-
493-
function proxy(
494-
name: ts.PropertyName,
495-
type: ts.TypeNode,
496-
definition: ts.DefinitionInfo,
497-
sourceFile: ts.SourceFile,
498-
) {
499-
const { textSpan, fileName } = definition;
500-
const start = name.getStart(sourceFile);
501-
const end = name.getEnd();
502-
503-
if (start !== textSpan.start || end - start !== textSpan.length) {
504-
return;
505-
}
506-
507-
if (!ts.isIndexedAccessTypeNode(type)) {
508-
return;
509-
}
510-
511-
const pos = type.indexType.getStart(sourceFile);
512-
const res = getDefinitionAndBoundSpan(fileName, pos);
513-
if (res?.definitions?.length) {
514-
for (const definition of res.definitions) {
515-
definitions.add(definition);
516-
}
517-
skippedDefinitions.push(definition);
518-
}
519-
}
520-
};
521-
}

0 commit comments

Comments
 (0)