Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ namespace ts {

getJsxElementAttributesType,
getJsxIntrinsicTagNames,
isOptionalParameter
isOptionalParameter,
isTypeAssignableTo
};

const tupleTypes = createMap<TupleType>();
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,7 @@ namespace ts {
getJsxElementAttributesType(elementNode: JsxOpeningLikeElement): Type;
getJsxIntrinsicTagNames(): Symbol[];
isOptionalParameter(node: ParameterDeclaration): boolean;
isTypeAssignableTo(source: Type, target: Type): boolean;

// Should not be called directly. Should only be accessed through the Program instance.
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];
Expand Down
62 changes: 62 additions & 0 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,15 @@ namespace FourSlash {
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Type definitions Count"));
}

public verifyImplementationsCount(negative: boolean, expectedCount: number) {
const assertFn = negative ? assert.notEqual : assert.equal;

const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
const actualCount = implementations && implementations.length || 0;

assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Implementations Count"));
}

public verifyDefinitionsName(negative: boolean, expectedName: string, expectedContainerName: string) {
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
const actualDefinitionName = definitions && definitions.length ? definitions[0].name : "";
Expand All @@ -1618,6 +1627,47 @@ namespace FourSlash {
}
}

public goToImplementation(implIndex: number) {
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!implementations || !implementations.length) {
this.raiseError("goToImplementation failed - expected to at least one implementation location but got 0");
}

if (implIndex >= implementations.length) {
this.raiseError(`goToImplementation failed - implIndex value (${implIndex}) exceeds implementation list size (${implementations.length})`);
}

const implementation = implementations[implIndex];
this.openFile(implementation.fileName);
this.currentCaretPosition = implementation.textSpan.start;
}

public verifyRangesInImplementationList() {
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!implementations || !implementations.length) {
this.raiseError("verifyRangesInImplementationList failed - expected to at least one implementation location but got 0");
}

const ranges = this.getRanges();

if (!ranges || !ranges.length) {
this.raiseError("verifyRangesInImplementationList failed - expected to at least one range in test source");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expected to find

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed that typo about a dozen times when reviewing my own code and always forget to fix it. Fixed now!

}

for (const range of ranges) {
let rangeIsPresent = false;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const matchingImpl = ts.find(implementations, impl =>
    range.fileName === impl.fileName && range.start === impl.textSpan.start && length === impl.textSpan.length);
if (matchingImpl) {
    matchingImpl.matched = true;
}
else {
    unsatisfiedRanges.push(range);
}

const length = range.end - range.start;
for (const impl of implementations) {
if (range.fileName === impl.fileName && range.start === impl.textSpan.start && length === impl.textSpan.length) {
rangeIsPresent = true;
break;
}
}
assert.isTrue(rangeIsPresent, `No implementation found for range ${range.start}, ${range.end} in ${range.fileName}: ${this.rangeText(range)}`);
}
assert.equal(implementations.length, ranges.length, `Different number of implementations (${implementations.length}) and ranges (${ranges.length})`);
}

public getMarkers(): Marker[] {
// Return a copy of the list
return this.testData.markers.slice(0);
Expand Down Expand Up @@ -2768,6 +2818,10 @@ namespace FourSlashInterface {
this.state.goToTypeDefinition(definitionIndex);
}

public implementation(implementationIndex = 0) {
this.state.goToImplementation(implementationIndex);
}

public position(position: number, fileIndex?: number): void;
public position(position: number, fileName?: string): void;
public position(position: number, fileNameOrIndex?: any): void {
Expand Down Expand Up @@ -2876,6 +2930,10 @@ namespace FourSlashInterface {
this.state.verifyTypeDefinitionsCount(this.negative, expectedCount);
}

public implementationCountIs(expectedCount: number) {
this.state.verifyImplementationsCount(this.negative, expectedCount);
}

public definitionLocationExists() {
this.state.verifyDefinitionLocationExists(this.negative);
}
Expand Down Expand Up @@ -3113,6 +3171,10 @@ namespace FourSlashInterface {
public ProjectInfo(expected: string[]) {
this.state.verifyProjectInfo(expected);
}

public allRangesAppearInImplementationList() {
this.state.verifyRangesInImplementationList();
}
}

export class Edit {
Expand Down
3 changes: 3 additions & 0 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,9 @@ namespace Harness.LanguageService {
getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
return unwrapJSONCallResult(this.shim.getTypeDefinitionAtPosition(fileName, position));
}
getImplementationAtPosition(fileName: string, position: number): ts.ImplementationLocation[] {
return unwrapJSONCallResult(this.shim.getImplementationAtPosition(fileName, position));
}
getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position));
}
Expand Down
22 changes: 22 additions & 0 deletions src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,28 @@ namespace ts.server {
});
}

getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
};

const request = this.processRequest<protocol.ImplementationRequest>(CommandNames.Implementation, args);
const response = this.processResponse<protocol.ImplementationResponse>(request);

return response.body.map(entry => {
const fileName = entry.file;
const start = this.lineOffsetToPosition(fileName, entry.start);
const end = this.lineOffsetToPosition(fileName, entry.end);
return {
fileName,
textSpan: ts.createTextSpanFromBounds(start, end)
};
});
}

findReferences(fileName: string, position: number): ReferencedSymbol[] {
// Not yet implemented.
return [];
Expand Down
15 changes: 15 additions & 0 deletions src/server/protocol.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ declare namespace ts.server.protocol {
export interface TypeDefinitionRequest extends FileLocationRequest {
}

/**
* Go to implementation request; value of command field is
* "implementation". Return response giving the file locations that
* implement the symbol found in file at location line, col.
*/
export interface ImplementationRequest extends FileLocationRequest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: why you make this empty interface?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following what TypeDefinitionRequest and DefinitionRequest did (see above).

}

/**
* Location in source code expressed as (one-based) line and character offset.
*/
Expand Down Expand Up @@ -240,6 +248,13 @@ declare namespace ts.server.protocol {
body?: FileSpan[];
}

/**
* Implementation response message. Gives text range for implementations.
*/
export interface ImplementationResponse extends Response {
body?: FileSpan[];
}

/**
* Get occurrences request; value of command field is
* "occurrences". Return response giving spans that are relevant
Expand Down
27 changes: 27 additions & 0 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ namespace ts.server {
export const Formatonkey = "formatonkey";
export const Geterr = "geterr";
export const GeterrForProject = "geterrForProject";
export const Implementation = "implementation";
export const SemanticDiagnosticsSync = "semanticDiagnosticsSync";
export const SyntacticDiagnosticsSync = "syntacticDiagnosticsSync";
export const NavBar = "navbar";
Expand Down Expand Up @@ -357,6 +358,28 @@ namespace ts.server {
}));
}

private getImplementation(line: number, offset: number, fileName: string): protocol.FileSpan[] {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project || project.languageServiceDiabled) {
throw Errors.NoProject;
}

const compilerService = project.compilerService;
const implementations = compilerService.languageService.getImplementationAtPosition(file,
compilerService.host.lineOffsetToPosition(file, line, offset));

if (!implementations) {
return undefined;
}

return implementations.map(impl => ({
file: impl.fileName,
start: compilerService.host.positionToLineOffset(impl.fileName, impl.textSpan.start),
end: compilerService.host.positionToLineOffset(impl.fileName, ts.textSpanEnd(impl.textSpan))
}));
}

private getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[] {
fileName = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(fileName);
Expand Down Expand Up @@ -1074,6 +1097,10 @@ namespace ts.server {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return { response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
},
[CommandNames.Implementation]: (request: protocol.Request) => {
const implArgs = <protocol.FileLocationRequestArgs>request.arguments;
return { response: this.getImplementation(implArgs.line, implArgs.offset, implArgs.file), responseRequired: true };
},
[CommandNames.References]: (request: protocol.Request) => {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return { response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
Expand Down
Loading