Skip to content

Commit 111e509

Browse files
committed
Go to Implementation
1 parent a013759 commit 111e509

61 files changed

Lines changed: 1670 additions & 29 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/compiler/checker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ namespace ts {
103103

104104
getJsxElementAttributesType,
105105
getJsxIntrinsicTagNames,
106-
isOptionalParameter
106+
isOptionalParameter,
107+
isTypeAssignableTo
107108
};
108109

109110
const tupleTypes = createMap<TupleType>();

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,6 +1889,7 @@ namespace ts {
18891889
getJsxElementAttributesType(elementNode: JsxOpeningLikeElement): Type;
18901890
getJsxIntrinsicTagNames(): Symbol[];
18911891
isOptionalParameter(node: ParameterDeclaration): boolean;
1892+
isTypeAssignableTo(source: Type, target: Type): boolean;
18921893

18931894
// Should not be called directly. Should only be accessed through the Program instance.
18941895
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];

src/compiler/utilities.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,11 @@ namespace ts {
16691669
return false;
16701670
}
16711671

1672+
export function isFunctionDeclarationIdentifierName(node: Identifier): boolean {
1673+
return node.parent.kind === SyntaxKind.FunctionDeclaration &&
1674+
(<FunctionDeclaration>node.parent).name === node;
1675+
}
1676+
16721677
// An alias symbol is created by one of the following declarations:
16731678
// import <symbol> = ...
16741679
// import <symbol> from ...

src/harness/fourslash.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,15 @@ namespace FourSlash {
16041604
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Type definitions Count"));
16051605
}
16061606

1607+
public verifyImplementationsCount(negative: boolean, expectedCount: number) {
1608+
const assertFn = negative ? assert.notEqual : assert.equal;
1609+
1610+
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
1611+
const actualCount = implementations && implementations.length || 0;
1612+
1613+
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Implementations Count"));
1614+
}
1615+
16071616
public verifyDefinitionsName(negative: boolean, expectedName: string, expectedContainerName: string) {
16081617
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
16091618
const actualDefinitionName = definitions && definitions.length ? definitions[0].name : "";
@@ -1618,6 +1627,47 @@ namespace FourSlash {
16181627
}
16191628
}
16201629

1630+
public goToImplementation(implIndex: number) {
1631+
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
1632+
if (!implementations || !implementations.length) {
1633+
this.raiseError("goToImplementation failed - expected to at least one implementation location but got 0");
1634+
}
1635+
1636+
if (implIndex >= implementations.length) {
1637+
this.raiseError(`goToImplementation failed - implIndex value (${implIndex}) exceeds implementation list size (${implementations.length})`);
1638+
}
1639+
1640+
const implementation = implementations[implIndex];
1641+
this.openFile(implementation.fileName);
1642+
this.currentCaretPosition = implementation.textSpan.start;
1643+
}
1644+
1645+
public verifyRangesInImplementationList() {
1646+
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
1647+
if (!implementations || !implementations.length) {
1648+
this.raiseError("verifyRangesInImplementationList failed - expected to at least one implementation location but got 0");
1649+
}
1650+
1651+
const ranges = this.getRanges();
1652+
1653+
if (!ranges || !ranges.length) {
1654+
this.raiseError("verifyRangesInImplementationList failed - expected to at least one range in test source");
1655+
}
1656+
1657+
for (const range of ranges) {
1658+
let rangeIsPresent = false;
1659+
const length = range.end - range.start;
1660+
for (const impl of implementations) {
1661+
if (range.fileName === impl.fileName && range.start === impl.textSpan.start && length === impl.textSpan.length) {
1662+
rangeIsPresent = true;
1663+
break;
1664+
}
1665+
}
1666+
assert.isTrue(rangeIsPresent, `No implementation found for range ${range.start}, ${range.end} in ${range.fileName}: ${this.rangeText(range)}`);
1667+
}
1668+
assert.equal(implementations.length, ranges.length, `Different number of implementations (${implementations.length}) and ranges (${ranges.length})`);
1669+
}
1670+
16211671
public getMarkers(): Marker[] {
16221672
// Return a copy of the list
16231673
return this.testData.markers.slice(0);
@@ -2768,6 +2818,10 @@ namespace FourSlashInterface {
27682818
this.state.goToTypeDefinition(definitionIndex);
27692819
}
27702820

2821+
public implementation(implementationIndex = 0) {
2822+
this.state.goToImplementation(implementationIndex);
2823+
}
2824+
27712825
public position(position: number, fileIndex?: number): void;
27722826
public position(position: number, fileName?: string): void;
27732827
public position(position: number, fileNameOrIndex?: any): void {
@@ -2876,6 +2930,10 @@ namespace FourSlashInterface {
28762930
this.state.verifyTypeDefinitionsCount(this.negative, expectedCount);
28772931
}
28782932

2933+
public implementationCountIs(expectedCount: number) {
2934+
this.state.verifyImplementationsCount(this.negative, expectedCount);
2935+
}
2936+
28792937
public definitionLocationExists() {
28802938
this.state.verifyDefinitionLocationExists(this.negative);
28812939
}
@@ -3113,6 +3171,10 @@ namespace FourSlashInterface {
31133171
public ProjectInfo(expected: string[]) {
31143172
this.state.verifyProjectInfo(expected);
31153173
}
3174+
3175+
public allRangesAppearInImplementationList() {
3176+
this.state.verifyRangesInImplementationList();
3177+
}
31163178
}
31173179

31183180
export class Edit {

src/harness/harnessLanguageService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,9 @@ namespace Harness.LanguageService {
408408
getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
409409
return unwrapJSONCallResult(this.shim.getTypeDefinitionAtPosition(fileName, position));
410410
}
411+
getImplementationAtPosition(fileName: string, position: number): ts.ImplementationLocation[] {
412+
return unwrapJSONCallResult(this.shim.getImplementationAtPosition(fileName, position));
413+
}
411414
getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
412415
return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position));
413416
}

src/server/client.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,28 @@ namespace ts.server {
353353
});
354354
}
355355

356+
getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] {
357+
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
358+
const args: protocol.FileLocationRequestArgs = {
359+
file: fileName,
360+
line: lineOffset.line,
361+
offset: lineOffset.offset,
362+
};
363+
364+
const request = this.processRequest<protocol.ImplementationRequest>(CommandNames.Implementation, args);
365+
const response = this.processResponse<protocol.ImplementationResponse>(request);
366+
367+
return response.body.map(entry => {
368+
const fileName = entry.file;
369+
const start = this.lineOffsetToPosition(fileName, entry.start);
370+
const end = this.lineOffsetToPosition(fileName, entry.end);
371+
return {
372+
fileName,
373+
textSpan: ts.createTextSpanFromBounds(start, end)
374+
};
375+
});
376+
}
377+
356378
findReferences(fileName: string, position: number): ReferencedSymbol[] {
357379
// Not yet implemented.
358380
return [];

src/server/protocol.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ declare namespace ts.server.protocol {
193193
export interface TypeDefinitionRequest extends FileLocationRequest {
194194
}
195195

196+
/**
197+
* Go to implementation request; value of command field is
198+
* "implementation". Return response giving the file locations that
199+
* implement the symbol found in file at location line, col.
200+
*/
201+
export interface ImplementationRequest extends FileLocationRequest {
202+
}
203+
196204
/**
197205
* Location in source code expressed as (one-based) line and character offset.
198206
*/
@@ -240,6 +248,13 @@ declare namespace ts.server.protocol {
240248
body?: FileSpan[];
241249
}
242250

251+
/**
252+
* Implementation response message. Gives text range for implementations.
253+
*/
254+
export interface ImplementationResponse extends Response {
255+
body?: FileSpan[];
256+
}
257+
243258
/**
244259
* Get occurrences request; value of command field is
245260
* "occurrences". Return response giving spans that are relevant

src/server/session.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ namespace ts.server {
111111
export const Formatonkey = "formatonkey";
112112
export const Geterr = "geterr";
113113
export const GeterrForProject = "geterrForProject";
114+
export const Implementation = "implementation";
114115
export const SemanticDiagnosticsSync = "semanticDiagnosticsSync";
115116
export const SyntacticDiagnosticsSync = "syntacticDiagnosticsSync";
116117
export const NavBar = "navbar";
@@ -357,6 +358,28 @@ namespace ts.server {
357358
}));
358359
}
359360

361+
private getImplementation(line: number, offset: number, fileName: string): protocol.FileSpan[] {
362+
const file = ts.normalizePath(fileName);
363+
const project = this.projectService.getProjectForFile(file);
364+
if (!project || project.languageServiceDiabled) {
365+
throw Errors.NoProject;
366+
}
367+
368+
const compilerService = project.compilerService;
369+
const position = compilerService.host.lineOffsetToPosition(file, line, offset);
370+
371+
const implementations = compilerService.languageService.getImplementationAtPosition(file, position);
372+
if (!implementations) {
373+
return undefined;
374+
}
375+
376+
return implementations.map(impl => ({
377+
file: impl.fileName,
378+
start: compilerService.host.positionToLineOffset(impl.fileName, impl.textSpan.start),
379+
end: compilerService.host.positionToLineOffset(impl.fileName, ts.textSpanEnd(impl.textSpan))
380+
}));
381+
}
382+
360383
private getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[] {
361384
fileName = ts.normalizePath(fileName);
362385
const project = this.projectService.getProjectForFile(fileName);
@@ -1074,6 +1097,10 @@ namespace ts.server {
10741097
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
10751098
return { response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
10761099
},
1100+
[CommandNames.Implementation]: (request: protocol.Request) => {
1101+
const implArgs = <protocol.FileLocationRequestArgs>request.arguments;
1102+
return { response: this.getImplementation(implArgs.line, implArgs.offset, implArgs.file), responseRequired: true };
1103+
},
10771104
[CommandNames.References]: (request: protocol.Request) => {
10781105
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
10791106
return { response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };

0 commit comments

Comments
 (0)