Skip to content

Commit

Permalink
feat(typescript-plugin): skip declaration files in goto components de…
Browse files Browse the repository at this point in the history
…finition (#5221)
  • Loading branch information
KazariEX authored Mar 1, 2025
1 parent 6dd13c5 commit 378adb4
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/language-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export function getFullLanguageServicePlugins(
languageService.getCompletionsAtPosition = proxy.getCompletionsAtPosition;
languageService.getCompletionEntryDetails = proxy.getCompletionEntryDetails;
languageService.getCodeFixesAtPosition = proxy.getCodeFixesAtPosition;
languageService.getDefinitionAndBoundSpan = proxy.getDefinitionAndBoundSpan;
languageService.getQuickInfoAtPosition = proxy.getQuickInfoAtPosition;
}
return created;
Expand Down
121 changes: 118 additions & 3 deletions packages/typescript-plugin/lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function proxyLanguageServiceForVue<T>(
case 'getCompletionsAtPosition': return getCompletionsAtPosition(vueOptions, target[p]);
case 'getCompletionEntryDetails': return getCompletionEntryDetails(language, asScriptId, target[p]);
case 'getCodeFixesAtPosition': return getCodeFixesAtPosition(target[p]);
case 'getDefinitionAndBoundSpan': return getDefinitionAndBoundSpan(ts, language, languageService, vueOptions, asScriptId, target[p]);
case 'getQuickInfoAtPosition': return getQuickInfoAtPosition(ts, target, target[p]);
// TS plugin only
case 'getEncodedSemanticClassifications': return getEncodedSemanticClassifications(ts, language, target, asScriptId, target[p]);
Expand Down Expand Up @@ -91,7 +92,11 @@ function getCompletionsAtPosition(vueOptions: VueCompilerOptions, getCompletions
};
}

function getCompletionEntryDetails<T>(language: Language<T>, asScriptId: (fileName: string) => T, getCompletionEntryDetails: ts.LanguageService['getCompletionEntryDetails']): ts.LanguageService['getCompletionEntryDetails'] {
function getCompletionEntryDetails<T>(
language: Language<T>,
asScriptId: (fileName: string) => T,
getCompletionEntryDetails: ts.LanguageService['getCompletionEntryDetails']
): ts.LanguageService['getCompletionEntryDetails'] {
return (...args) => {
const details = getCompletionEntryDetails(...args);
// modify import statement
Expand Down Expand Up @@ -132,7 +137,9 @@ function getCompletionEntryDetails<T>(language: Language<T>, asScriptId: (fileNa
};
}

function getCodeFixesAtPosition(getCodeFixesAtPosition: ts.LanguageService['getCodeFixesAtPosition']): ts.LanguageService['getCodeFixesAtPosition'] {
function getCodeFixesAtPosition(
getCodeFixesAtPosition: ts.LanguageService['getCodeFixesAtPosition']
): ts.LanguageService['getCodeFixesAtPosition'] {
return (...args) => {
let result = getCodeFixesAtPosition(...args);
// filter __VLS_
Expand All @@ -141,7 +148,115 @@ function getCodeFixesAtPosition(getCodeFixesAtPosition: ts.LanguageService['getC
};
}

function getQuickInfoAtPosition(ts: typeof import('typescript'), languageService: ts.LanguageService, getQuickInfoAtPosition: ts.LanguageService['getQuickInfoAtPosition']): ts.LanguageService['getQuickInfoAtPosition'] {
function getDefinitionAndBoundSpan<T>(
ts: typeof import('typescript'),
language: Language<T>,
languageService: ts.LanguageService,
vueOptions: VueCompilerOptions,
asScriptId: (fileName: string) => T,
getDefinitionAndBoundSpan: ts.LanguageService['getDefinitionAndBoundSpan']
): ts.LanguageService['getDefinitionAndBoundSpan'] {
return (fileName, position) => {
const result = getDefinitionAndBoundSpan(fileName, position);
if (!result?.definitions?.length) {
return result;
}

const program = languageService.getProgram()!;
const sourceScript = language.scripts.get(asScriptId(fileName));
if (!sourceScript?.generated) {
return result;
}

const root = sourceScript.generated.root;
if (!(root instanceof VueVirtualCode)) {
return result;
}

if (
!root.sfc.template
|| position < root.sfc.template.startTagEnd
|| position > root.sfc.template.endTagStart
) {
return result;
}

const definitions = new Set<ts.DefinitionInfo>(result.definitions);
const skippedDefinitions: ts.DefinitionInfo[] = [];

for (const definition of result.definitions) {
if (vueOptions.extensions.some(ext => definition.fileName.endsWith(ext))) {
continue;
}

const sourceFile = program.getSourceFile(definition.fileName);
if (!sourceFile) {
continue;
}

visit(sourceFile, definition, sourceFile);
}

for (const definition of skippedDefinitions) {
definitions.delete(definition);
}

return {
definitions: [...definitions],
textSpan: result.textSpan,
};

function visit(
node: ts.Node,
definition: ts.DefinitionInfo,
sourceFile: ts.SourceFile
) {
if (ts.isPropertySignature(node) && node.type) {
proxy(node.name, node.type, definition, sourceFile);
}
else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.type && !node.initializer) {
proxy(node.name, node.type, definition, sourceFile);
}
else {
ts.forEachChild(node, child => visit(child, definition, sourceFile));
}
}

function proxy(
name: ts.PropertyName,
type: ts.TypeNode,
definition: ts.DefinitionInfo,
sourceFile: ts.SourceFile
) {
const { textSpan, fileName } = definition;
const start = name.getStart(sourceFile);
const end = name.getEnd();

if (start !== textSpan.start || end - start !== textSpan.length) {
return;
}

if (!ts.isIndexedAccessTypeNode(type)) {
return;
}

const pos = type.indexType.getStart(sourceFile);
const res = getDefinitionAndBoundSpan(fileName, pos);
if (res?.definitions?.length) {
for (const definition of res.definitions) {
definitions.add(definition);
}
skippedDefinitions.push(definition);
}
}
}
}

function getQuickInfoAtPosition(
ts: typeof import('typescript'),
languageService: ts.LanguageService,
getQuickInfoAtPosition: ts.LanguageService['getQuickInfoAtPosition']
): ts.LanguageService['getQuickInfoAtPosition'] {
return (...args) => {
const result = getQuickInfoAtPosition(...args);
if (result && result.documentation?.length === 1 && result.documentation[0].text.startsWith('__VLS_emit,')) {
Expand Down

0 comments on commit 378adb4

Please sign in to comment.