Skip to content

Commit c57a1ab

Browse files
authored
(feat) implement go to type definition (#1323)
Fixing a bug in ImplementationProvider at the same time
1 parent cf0c592 commit c57a1ab

File tree

12 files changed

+199
-6
lines changed

12 files changed

+199
-6
lines changed

packages/language-server/src/ls-config.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const defaultLSConfig: LSConfig = {
2020
selectionRange: { enable: true },
2121
signatureHelp: { enable: true },
2222
semanticTokens: { enable: true },
23-
implementation: { enable: true }
23+
implementation: { enable: true },
24+
typeDefinition: { enable: true }
2425
},
2526
css: {
2627
enable: true,
@@ -116,6 +117,9 @@ export interface LSTypescriptConfig {
116117
implementation: {
117118
enable: boolean;
118119
};
120+
typeDefinition: {
121+
enable: boolean;
122+
};
119123
}
120124

121125
export interface LSCSSConfig {

packages/language-server/src/plugins/PluginHost.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,20 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
446446
);
447447
}
448448

449+
getTypeDefinition(
450+
textDocument: TextDocumentIdentifier,
451+
position: Position
452+
): Promise<Location[] | null> {
453+
const document = this.getDocument(textDocument.uri);
454+
455+
return this.execute<Location[] | null>(
456+
'getTypeDefinition',
457+
[document, position],
458+
ExecuteMode.FirstNonNull,
459+
'high'
460+
);
461+
}
462+
449463
onWatchFileChanges(onWatchFileChangesParas: OnWatchFileChangesPara[]): void {
450464
for (const support of this.plugins) {
451465
support.onWatchFileChanges?.(onWatchFileChangesParas);

packages/language-server/src/plugins/interfaces.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ export interface ImplementationProvider {
171171
getImplementation(document: Document, position: Position): Resolvable<Location[] | null>;
172172
}
173173

174+
export interface TypeDefinitionProvider {
175+
getTypeDefinition(document: Document, position: Position): Resolvable<Location[] | null>;
176+
}
177+
174178
export interface OnWatchFileChangesPara {
175179
fileName: string;
176180
changeType: FileChangeType;
@@ -199,7 +203,8 @@ type ProviderBase = DiagnosticsProvider &
199203
SignatureHelpProvider &
200204
SemanticTokensProvider &
201205
LinkedEditingRangesProvider &
202-
ImplementationProvider;
206+
ImplementationProvider &
207+
TypeDefinitionProvider;
203208

204209
export type LSProvider = ProviderBase & BackwardsCompatibleDefinitionsProvider;
205210

packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@ import {
3636
FileRename,
3737
FindReferencesProvider,
3838
HoverProvider,
39+
ImplementationProvider,
3940
OnWatchFileChanges,
4041
OnWatchFileChangesPara,
4142
RenameProvider,
4243
SelectionRangeProvider,
4344
SemanticTokensProvider,
4445
SignatureHelpProvider,
46+
TypeDefinitionProvider,
4547
UpdateImportsProvider,
46-
ImplementationProvider,
4748
UpdateTsOrJsFile
4849
} from '../interfaces';
4950
import { CodeActionsProviderImpl } from './features/CodeActionsProvider';
@@ -60,6 +61,7 @@ import { RenameProviderImpl } from './features/RenameProvider';
6061
import { SelectionRangeProviderImpl } from './features/SelectionRangeProvider';
6162
import { SemanticTokensProviderImpl } from './features/SemanticTokensProvider';
6263
import { SignatureHelpProviderImpl } from './features/SignatureHelpProvider';
64+
import { TypeDefinitionProviderImpl } from './features/TypeDefinitionProvider';
6365
import { UpdateImportsProviderImpl } from './features/UpdateImportsProvider';
6466
import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './features/utils';
6567
import { LSAndTSDocResolver } from './LSAndTSDocResolver';
@@ -80,6 +82,7 @@ export class TypeScriptPlugin
8082
SignatureHelpProvider,
8183
SemanticTokensProvider,
8284
ImplementationProvider,
85+
TypeDefinitionProvider,
8386
OnWatchFileChanges,
8487
CompletionsProvider<CompletionEntryWithIdentifer>,
8588
UpdateTsOrJsFile
@@ -97,6 +100,7 @@ export class TypeScriptPlugin
97100
private readonly signatureHelpProvider: SignatureHelpProviderImpl;
98101
private readonly semanticTokensProvider: SemanticTokensProviderImpl;
99102
private readonly implementationProvider: ImplementationProviderImpl;
103+
private readonly typeDefinitionProvider: TypeDefinitionProviderImpl;
100104

101105
constructor(configManager: LSConfigManager, lsAndTsDocResolver: LSAndTSDocResolver) {
102106
this.configManager = configManager;
@@ -119,6 +123,7 @@ export class TypeScriptPlugin
119123
this.signatureHelpProvider = new SignatureHelpProviderImpl(this.lsAndTsDocResolver);
120124
this.semanticTokensProvider = new SemanticTokensProviderImpl(this.lsAndTsDocResolver);
121125
this.implementationProvider = new ImplementationProviderImpl(this.lsAndTsDocResolver);
126+
this.typeDefinitionProvider = new TypeDefinitionProviderImpl(this.lsAndTsDocResolver);
122127
}
123128

124129
async getDiagnostics(
@@ -467,6 +472,14 @@ export class TypeScriptPlugin
467472
return this.implementationProvider.getImplementation(document, position);
468473
}
469474

475+
async getTypeDefinition(document: Document, position: Position): Promise<Location[] | null> {
476+
if (!this.featureEnabled('typeDefinition')) {
477+
return null;
478+
}
479+
480+
return this.typeDefinitionProvider.getTypeDefinition(document, position);
481+
}
482+
470483
private async getLSAndTSDoc(document: Document) {
471484
return this.lsAndTsDocResolver.getLSAndTSDoc(document);
472485
}

packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class ImplementationProviderImpl implements ImplementationProvider {
3737
convertRange(fragment, implementation.textSpan)
3838
);
3939

40-
if (range.start.line > 0 && range.end.line > 0) {
40+
if (range.start.line >= 0 && range.end.line >= 0) {
4141
return Location.create(pathToUrl(implementation.fileName), range);
4242
}
4343
})
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Position, Location } from 'vscode-languageserver-protocol';
2+
import { Document, mapRangeToOriginal } from '../../../lib/documents';
3+
import { pathToUrl, isNotNullOrUndefined } from '../../../utils';
4+
import { TypeDefinitionProvider } from '../../interfaces';
5+
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
6+
import { convertRange } from '../utils';
7+
import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './utils';
8+
9+
export class TypeDefinitionProviderImpl implements TypeDefinitionProvider {
10+
constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}
11+
12+
async getTypeDefinition(document: Document, position: Position): Promise<Location[] | null> {
13+
const { tsDoc, lang } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
14+
15+
const mainFragment = await tsDoc.getFragment();
16+
const offset = mainFragment.offsetAt(mainFragment.getGeneratedPosition(position));
17+
18+
const typeDefs = lang.getTypeDefinitionAtPosition(tsDoc.filePath, offset);
19+
20+
const docs = new SnapshotFragmentMap(this.lsAndTsDocResolver);
21+
docs.set(tsDoc.filePath, { fragment: mainFragment, snapshot: tsDoc });
22+
23+
if (!typeDefs) {
24+
return null;
25+
}
26+
27+
const result = await Promise.all(
28+
typeDefs.map(async (typeDef) => {
29+
const { fragment, snapshot } = await docs.retrieve(typeDef.fileName);
30+
31+
if (!isNoTextSpanInGeneratedCode(snapshot.getFullText(), typeDef.textSpan)) {
32+
return;
33+
}
34+
35+
const range = mapRangeToOriginal(
36+
fragment,
37+
convertRange(fragment, typeDef.textSpan)
38+
);
39+
40+
if (range.start.line >= 0 && range.end.line >= 0) {
41+
return Location.create(pathToUrl(typeDef.fileName), range);
42+
}
43+
})
44+
);
45+
46+
return result.filter(isNotNullOrUndefined);
47+
}
48+
}

packages/language-server/src/server.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ export function startServer(options?: LSOptions) {
246246
full: true
247247
},
248248
linkedEditingRangeProvider: true,
249-
implementationProvider: true
249+
implementationProvider: true,
250+
typeDefinitionProvider: true
250251
}
251252
};
252253
});
@@ -354,6 +355,10 @@ export function startServer(options?: LSOptions) {
354355
pluginHost.getImplementation(evt.textDocument, evt.position)
355356
);
356357

358+
connection.onTypeDefinition((evt) =>
359+
pluginHost.getTypeDefinition(evt.textDocument, evt.position)
360+
);
361+
357362
const diagnosticsManager = new DiagnosticsManager(
358363
connection.sendDiagnostics,
359364
docManager,

packages/language-server/test/plugins/typescript/features/ImplemenationProvider.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Location } from 'vscode-languageserver-protocol';
1010

1111
const testDir = path.join(__dirname, '..');
1212

13-
describe('FindReferencesProvider', () => {
13+
describe('ImplementationProvider', () => {
1414
function getFullPath(filename: string) {
1515
return path.join(testDir, 'testfiles', 'implementation', filename);
1616
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import assert from 'assert';
2+
import path from 'path';
3+
import ts from 'typescript';
4+
import { Location } from 'vscode-languageserver-protocol';
5+
import { Document, DocumentManager } from '../../../../src/lib/documents';
6+
import { LSConfigManager } from '../../../../src/ls-config';
7+
import { LSAndTSDocResolver } from '../../../../src/plugins';
8+
import { TypeDefinitionProviderImpl } from '../../../../src/plugins/typescript/features/TypeDefinitionProvider';
9+
import { pathToUrl } from '../../../../src/utils';
10+
11+
const testDir = path.join(__dirname, '..');
12+
13+
describe('TypeDefinitionProvider', () => {
14+
function getFullPath(filename: string) {
15+
return path.join(testDir, 'testfiles', 'typedefinition', filename);
16+
}
17+
18+
function getUri(filename: string) {
19+
return pathToUrl(getFullPath(filename));
20+
}
21+
22+
function setup(filename: string) {
23+
const docManager = new DocumentManager(
24+
(textDocument) => new Document(textDocument.uri, textDocument.text)
25+
);
26+
const lsAndTsDocResolver = new LSAndTSDocResolver(
27+
docManager,
28+
[testDir],
29+
new LSConfigManager()
30+
);
31+
const provider = new TypeDefinitionProviderImpl(lsAndTsDocResolver);
32+
const filePath = getFullPath(filename);
33+
const document = docManager.openDocument(<any>{
34+
uri: pathToUrl(filePath),
35+
text: ts.sys.readFile(filePath) || ''
36+
});
37+
return { provider, document };
38+
}
39+
40+
it('find type definition in TS file', async () => {
41+
const { document, provider } = setup('typedefinition.svelte');
42+
43+
const typeDefs = await provider.getTypeDefinition(document, {
44+
line: 5,
45+
character: 15
46+
});
47+
48+
assert.deepStrictEqual(typeDefs, <Location[]>[
49+
{
50+
range: {
51+
start: {
52+
line: 0,
53+
character: 13
54+
},
55+
end: {
56+
line: 0,
57+
character: 30
58+
}
59+
},
60+
uri: getUri('some-class.ts')
61+
}
62+
]);
63+
});
64+
65+
it('find type definition in same Svelte file', async () => {
66+
const { document, provider } = setup('typedefinition.svelte');
67+
68+
const typeDefs = await provider.getTypeDefinition(document, {
69+
line: 6,
70+
character: 20
71+
});
72+
73+
assert.deepStrictEqual(typeDefs, <Location[]>[
74+
{
75+
range: {
76+
start: {
77+
line: 3,
78+
character: 10
79+
},
80+
end: {
81+
line: 3,
82+
character: 19
83+
}
84+
},
85+
uri: getUri('typedefinition.svelte')
86+
}
87+
]);
88+
});
89+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export class SomeImportedClass {}

0 commit comments

Comments
 (0)