-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Go to Implementation #10482
Go to Implementation #10482
Changes from all commits
111e509
457b67f
051c7b0
115141c
b0eff90
3ccc58c
66f30c9
9c562f8
b7071c1
5913a35
1cdd1d3
2069e1c
f91a123
4a37fd7
b6f7dd7
ea3752a
fd93699
4eef417
29d85cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -88,6 +88,10 @@ namespace FourSlash { | |
marker?: Marker; | ||
} | ||
|
||
interface ImplementationLocationInformation extends ts.ImplementationLocation { | ||
matched?: boolean; | ||
} | ||
|
||
export interface TextSpan { | ||
start: number; | ||
end: number; | ||
|
@@ -1699,6 +1703,17 @@ namespace FourSlash { | |
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Type definitions Count")); | ||
} | ||
|
||
public verifyImplementationListIsEmpty(negative: boolean) { | ||
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition); | ||
|
||
if (negative) { | ||
assert.isTrue(implementations && implementations.length > 0, "Expected at least one implementation but got 0"); | ||
} | ||
else { | ||
assert.isUndefined(implementations, "Expected implementation list to be empty but implementations returned"); | ||
} | ||
} | ||
|
||
public verifyGoToDefinitionName(expectedName: string, expectedContainerName: string) { | ||
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition); | ||
const actualDefinitionName = definitions && definitions.length ? definitions[0].name : ""; | ||
|
@@ -1707,6 +1722,82 @@ namespace FourSlash { | |
assert.equal(actualDefinitionContainerName, expectedContainerName, this.messageAtLastKnownMarker("Definition Info Container Name")); | ||
} | ||
|
||
public goToImplementation() { | ||
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition); | ||
if (!implementations || !implementations.length) { | ||
this.raiseError("goToImplementation failed - expected to find at least one implementation location but got 0"); | ||
} | ||
if (implementations.length > 1) { | ||
this.raiseError(`goToImplementation failed - more than 1 implementation returned (${implementations.length})`); | ||
} | ||
|
||
const implementation = implementations[0]; | ||
this.openFile(implementation.fileName); | ||
this.currentCaretPosition = implementation.textSpan.start; | ||
} | ||
|
||
public verifyRangesInImplementationList(markerName: string) { | ||
this.goToMarker(markerName); | ||
const implementations: ImplementationLocationInformation[] = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition); | ||
if (!implementations || !implementations.length) { | ||
this.raiseError("verifyRangesInImplementationList failed - expected to find at least one implementation location but got 0"); | ||
} | ||
|
||
for (let i = 0; i < implementations.length; i++) { | ||
for (let j = 0; j < implementations.length; j++) { | ||
if (i !== j && implementationsAreEqual(implementations[i], implementations[j])) { | ||
const { textSpan, fileName } = implementations[i]; | ||
const end = textSpan.start + textSpan.length; | ||
this.raiseError(`Duplicate implementations returned for range (${textSpan.start}, ${end}) in ${fileName}`); | ||
} | ||
} | ||
} | ||
|
||
const ranges = this.getRanges(); | ||
|
||
if (!ranges || !ranges.length) { | ||
this.raiseError("verifyRangesInImplementationList failed - expected to find at least one range in test source"); | ||
} | ||
|
||
const unsatisfiedRanges: Range[] = []; | ||
|
||
for (const range of ranges) { | ||
const length = range.end - range.start; | ||
const matchingImpl = ts.find(implementations, impl => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be a function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
range.fileName === impl.fileName && range.start === impl.textSpan.start && length === impl.textSpan.length); | ||
if (matchingImpl) { | ||
matchingImpl.matched = true; | ||
} | ||
else { | ||
unsatisfiedRanges.push(range); | ||
} | ||
} | ||
|
||
const unmatchedImplementations = implementations.filter(impl => !impl.matched); | ||
if (unmatchedImplementations.length || unsatisfiedRanges.length) { | ||
let error = "Not all ranges or implementations are satisfied"; | ||
if (unsatisfiedRanges.length) { | ||
error += "\nUnsatisfied ranges:"; | ||
for (const range of unsatisfiedRanges) { | ||
error += `\n (${range.start}, ${range.end}) in ${range.fileName}: ${this.rangeText(range)}`; | ||
} | ||
} | ||
|
||
if (unmatchedImplementations.length) { | ||
error += "\nUnmatched implementations:"; | ||
for (const impl of unmatchedImplementations) { | ||
const end = impl.textSpan.start + impl.textSpan.length; | ||
error += `\n (${impl.textSpan.start}, ${end}) in ${impl.fileName}: ${this.getFileContent(impl.fileName).slice(impl.textSpan.start, end)}`; | ||
} | ||
} | ||
this.raiseError(error); | ||
} | ||
|
||
function implementationsAreEqual(a: ImplementationLocationInformation, b: ImplementationLocationInformation) { | ||
return a.fileName === b.fileName && TestState.textSpansEqual(a.textSpan, b.textSpan); | ||
} | ||
} | ||
|
||
public getMarkers(): Marker[] { | ||
// Return a copy of the list | ||
return this.testData.markers.slice(0); | ||
|
@@ -2885,6 +2976,10 @@ namespace FourSlashInterface { | |
this.state.goToTypeDefinition(definitionIndex); | ||
} | ||
|
||
public implementation() { | ||
this.state.goToImplementation(); | ||
} | ||
|
||
public position(position: number, fileIndex?: number): void; | ||
public position(position: number, fileName?: string): void; | ||
public position(position: number, fileNameOrIndex?: any): void { | ||
|
@@ -2985,6 +3080,10 @@ namespace FourSlashInterface { | |
this.state.verifyTypeDefinitionsCount(this.negative, expectedCount); | ||
} | ||
|
||
public implementationListIsEmpty() { | ||
this.state.verifyImplementationListIsEmpty(this.negative); | ||
} | ||
|
||
public isValidBraceCompletionAtPosition(openingBrace: string) { | ||
this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace); | ||
} | ||
|
@@ -3253,6 +3352,10 @@ namespace FourSlashInterface { | |
public ProjectInfo(expected: string[]) { | ||
this.state.verifyProjectInfo(expected); | ||
} | ||
|
||
public allRangesAppearInImplementationList(markerName: string) { | ||
this.state.verifyRangesInImplementationList(markerName); | ||
} | ||
} | ||
|
||
export class Edit { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: why you make this empty interface? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was following what |
||
} | ||
|
||
/** | ||
* Location in source code expressed as (one-based) line and character offset. | ||
*/ | ||
|
@@ -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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this duplicate-implementation check be unnecessary so long as we check off each range as being matched by the first implementation we find? Then the duplicate will go into
unmatchedImplementations
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to be explicit about the error. Otherwise it would look like that particular implementation was not returned when actually it was returned twice.