@@ -88,6 +88,10 @@ namespace FourSlash {
8888 marker ?: Marker ;
8989 }
9090
91+ interface ImplementationLocationInformation extends ts . ImplementationLocation {
92+ matched ?: boolean ;
93+ }
94+
9195 export interface TextSpan {
9296 start : number ;
9397 end : number ;
@@ -1699,6 +1703,17 @@ namespace FourSlash {
16991703 assertFn ( actualCount , expectedCount , this . messageAtLastKnownMarker ( "Type definitions Count" ) ) ;
17001704 }
17011705
1706+ public verifyImplementationListIsEmpty ( negative : boolean ) {
1707+ const implementations = this . languageService . getImplementationAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
1708+
1709+ if ( negative ) {
1710+ assert . isTrue ( implementations && implementations . length > 0 , "Expected at least one implementation but got 0" ) ;
1711+ }
1712+ else {
1713+ assert . isUndefined ( implementations , "Expected implementation list to be empty but implementations returned" ) ;
1714+ }
1715+ }
1716+
17021717 public verifyGoToDefinitionName ( expectedName : string , expectedContainerName : string ) {
17031718 const definitions = this . languageService . getDefinitionAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
17041719 const actualDefinitionName = definitions && definitions . length ? definitions [ 0 ] . name : "" ;
@@ -1707,6 +1722,82 @@ namespace FourSlash {
17071722 assert . equal ( actualDefinitionContainerName , expectedContainerName , this . messageAtLastKnownMarker ( "Definition Info Container Name" ) ) ;
17081723 }
17091724
1725+ public goToImplementation ( ) {
1726+ const implementations = this . languageService . getImplementationAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
1727+ if ( ! implementations || ! implementations . length ) {
1728+ this . raiseError ( "goToImplementation failed - expected to find at least one implementation location but got 0" ) ;
1729+ }
1730+ if ( implementations . length > 1 ) {
1731+ this . raiseError ( `goToImplementation failed - more than 1 implementation returned (${ implementations . length } )` ) ;
1732+ }
1733+
1734+ const implementation = implementations [ 0 ] ;
1735+ this . openFile ( implementation . fileName ) ;
1736+ this . currentCaretPosition = implementation . textSpan . start ;
1737+ }
1738+
1739+ public verifyRangesInImplementationList ( markerName : string ) {
1740+ this . goToMarker ( markerName ) ;
1741+ const implementations : ImplementationLocationInformation [ ] = this . languageService . getImplementationAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
1742+ if ( ! implementations || ! implementations . length ) {
1743+ this . raiseError ( "verifyRangesInImplementationList failed - expected to find at least one implementation location but got 0" ) ;
1744+ }
1745+
1746+ for ( let i = 0 ; i < implementations . length ; i ++ ) {
1747+ for ( let j = 0 ; j < implementations . length ; j ++ ) {
1748+ if ( i !== j && implementationsAreEqual ( implementations [ i ] , implementations [ j ] ) ) {
1749+ const { textSpan, fileName } = implementations [ i ] ;
1750+ const end = textSpan . start + textSpan . length ;
1751+ this . raiseError ( `Duplicate implementations returned for range (${ textSpan . start } , ${ end } ) in ${ fileName } ` ) ;
1752+ }
1753+ }
1754+ }
1755+
1756+ const ranges = this . getRanges ( ) ;
1757+
1758+ if ( ! ranges || ! ranges . length ) {
1759+ this . raiseError ( "verifyRangesInImplementationList failed - expected to find at least one range in test source" ) ;
1760+ }
1761+
1762+ const unsatisfiedRanges : Range [ ] = [ ] ;
1763+
1764+ for ( const range of ranges ) {
1765+ const length = range . end - range . start ;
1766+ const matchingImpl = ts . find ( implementations , impl =>
1767+ range . fileName === impl . fileName && range . start === impl . textSpan . start && length === impl . textSpan . length ) ;
1768+ if ( matchingImpl ) {
1769+ matchingImpl . matched = true ;
1770+ }
1771+ else {
1772+ unsatisfiedRanges . push ( range ) ;
1773+ }
1774+ }
1775+
1776+ const unmatchedImplementations = implementations . filter ( impl => ! impl . matched ) ;
1777+ if ( unmatchedImplementations . length || unsatisfiedRanges . length ) {
1778+ let error = "Not all ranges or implementations are satisfied" ;
1779+ if ( unsatisfiedRanges . length ) {
1780+ error += "\nUnsatisfied ranges:" ;
1781+ for ( const range of unsatisfiedRanges ) {
1782+ error += `\n (${ range . start } , ${ range . end } ) in ${ range . fileName } : ${ this . rangeText ( range ) } ` ;
1783+ }
1784+ }
1785+
1786+ if ( unmatchedImplementations . length ) {
1787+ error += "\nUnmatched implementations:" ;
1788+ for ( const impl of unmatchedImplementations ) {
1789+ const end = impl . textSpan . start + impl . textSpan . length ;
1790+ error += `\n (${ impl . textSpan . start } , ${ end } ) in ${ impl . fileName } : ${ this . getFileContent ( impl . fileName ) . slice ( impl . textSpan . start , end ) } ` ;
1791+ }
1792+ }
1793+ this . raiseError ( error ) ;
1794+ }
1795+
1796+ function implementationsAreEqual ( a : ImplementationLocationInformation , b : ImplementationLocationInformation ) {
1797+ return a . fileName === b . fileName && TestState . textSpansEqual ( a . textSpan , b . textSpan ) ;
1798+ }
1799+ }
1800+
17101801 public getMarkers ( ) : Marker [ ] {
17111802 // Return a copy of the list
17121803 return this . testData . markers . slice ( 0 ) ;
@@ -2885,6 +2976,10 @@ namespace FourSlashInterface {
28852976 this . state . goToTypeDefinition ( definitionIndex ) ;
28862977 }
28872978
2979+ public implementation ( ) {
2980+ this . state . goToImplementation ( ) ;
2981+ }
2982+
28882983 public position ( position : number , fileIndex ?: number ) : void ;
28892984 public position ( position : number , fileName ?: string ) : void ;
28902985 public position ( position : number , fileNameOrIndex ?: any ) : void {
@@ -2985,6 +3080,10 @@ namespace FourSlashInterface {
29853080 this . state . verifyTypeDefinitionsCount ( this . negative , expectedCount ) ;
29863081 }
29873082
3083+ public implementationListIsEmpty ( ) {
3084+ this . state . verifyImplementationListIsEmpty ( this . negative ) ;
3085+ }
3086+
29883087 public isValidBraceCompletionAtPosition ( openingBrace : string ) {
29893088 this . state . verifyBraceCompletionAtPosition ( this . negative , openingBrace ) ;
29903089 }
@@ -3253,6 +3352,10 @@ namespace FourSlashInterface {
32533352 public ProjectInfo ( expected : string [ ] ) {
32543353 this . state . verifyProjectInfo ( expected ) ;
32553354 }
3355+
3356+ public allRangesAppearInImplementationList ( markerName : string ) {
3357+ this . state . verifyRangesInImplementationList ( markerName ) ;
3358+ }
32563359 }
32573360
32583361 export class Edit {
0 commit comments