@@ -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 ;
@@ -1693,45 +1697,87 @@ namespace FourSlash {
16931697 assert . equal ( actualDefinitionContainerName , expectedContainerName , this . messageAtLastKnownMarker ( "Definition Info Container Name" ) ) ;
16941698 }
16951699
1696- public goToImplementation ( implIndex : number ) {
1700+ public goToImplementation ( implIndex ? : number ) {
16971701 const implementations = this . languageService . getImplementationAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
16981702 if ( ! implementations || ! implementations . length ) {
16991703 this . raiseError ( "goToImplementation failed - expected to at least one implementation location but got 0" ) ;
17001704 }
17011705
1706+ if ( implIndex === undefined && implementations . length > 1 ) {
1707+ this . raiseError ( `goToImplementation failed - no index given but more than 1 implementation returned (${ implementations . length } )` ) ;
1708+ }
1709+
17021710 if ( implIndex >= implementations . length ) {
17031711 this . raiseError ( `goToImplementation failed - implIndex value (${ implIndex } ) exceeds implementation list size (${ implementations . length } )` ) ;
17041712 }
17051713
1706- const implementation = implementations [ implIndex ] ;
1714+ const implementation = implementations [ implIndex || 0 ] ;
17071715 this . openFile ( implementation . fileName ) ;
17081716 this . currentCaretPosition = implementation . textSpan . start ;
17091717 }
17101718
17111719 public verifyRangesInImplementationList ( ) {
1712- const implementations = this . languageService . getImplementationAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
1720+ const implementations : ImplementationLocationInformation [ ] = this . languageService . getImplementationAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
17131721 if ( ! implementations || ! implementations . length ) {
17141722 this . raiseError ( "verifyRangesInImplementationList failed - expected to at least one implementation location but got 0" ) ;
17151723 }
17161724
1725+ for ( let i = 0 ; i < implementations . length ; i ++ ) {
1726+ for ( let j = 0 ; j < implementations . length ; j ++ ) {
1727+ if ( i !== j && implementationsAreEqual ( implementations [ i ] , implementations [ j ] ) ) {
1728+ const { textSpan, fileName } = implementations [ i ] ;
1729+ const end = textSpan . start + textSpan . length ;
1730+ this . raiseError ( `Duplicate implementations returned for range (${ textSpan . start } , ${ end } ) in ${ fileName } ` ) ;
1731+ }
1732+ }
1733+ }
1734+
17171735 const ranges = this . getRanges ( ) ;
17181736
17191737 if ( ! ranges || ! ranges . length ) {
17201738 this . raiseError ( "verifyRangesInImplementationList failed - expected to at least one range in test source" ) ;
17211739 }
17221740
1741+ const unsatisfiedRanges : Range [ ] = [ ] ;
1742+
17231743 for ( const range of ranges ) {
17241744 let rangeIsPresent = false ;
17251745 const length = range . end - range . start ;
17261746 for ( const impl of implementations ) {
17271747 if ( range . fileName === impl . fileName && range . start === impl . textSpan . start && length === impl . textSpan . length ) {
1748+ impl . matched = true ;
17281749 rangeIsPresent = true ;
17291750 break ;
17301751 }
17311752 }
1732- assert . isTrue ( rangeIsPresent , `No implementation found for range ${ range . start } , ${ range . end } in ${ range . fileName } : ${ this . rangeText ( range ) } ` ) ;
1753+ if ( ! rangeIsPresent ) {
1754+ unsatisfiedRanges . push ( range ) ;
1755+ }
1756+ }
1757+
1758+ const unmatchedImplementations = implementations . filter ( impl => ! impl . matched ) ;
1759+ if ( unmatchedImplementations . length || unsatisfiedRanges . length ) {
1760+ let error = "Not all ranges or implementations are satisfied" ;
1761+ if ( unsatisfiedRanges . length ) {
1762+ error += "\nUnsatisfied ranges:" ;
1763+ for ( const range of unsatisfiedRanges ) {
1764+ error += `\n (${ range . start } , ${ range . end } ) in ${ range . fileName } : ${ this . rangeText ( range ) } ` ;
1765+ }
1766+ }
1767+
1768+ if ( unsatisfiedRanges . length ) {
1769+ error += "\nUnmatched implementations:" ;
1770+ for ( const impl of unmatchedImplementations ) {
1771+ const end = impl . textSpan . start + impl . textSpan . length ;
1772+ error += `\n (${ impl . textSpan . start } , ${ end } ) in ${ impl . fileName } : ${ this . getFileContent ( impl . fileName ) . slice ( impl . textSpan . start , end ) } ` ;
1773+ }
1774+ }
1775+ this . raiseError ( error ) ;
1776+ }
1777+
1778+ function implementationsAreEqual ( a : ImplementationLocationInformation , b : ImplementationLocationInformation ) {
1779+ return a . fileName === b . fileName && TestState . textSpansEqual ( a . textSpan , b . textSpan ) ;
17331780 }
1734- assert . equal ( implementations . length , ranges . length , `Different number of implementations (${ implementations . length } ) and ranges (${ ranges . length } )` ) ;
17351781 }
17361782
17371783 public getMarkers ( ) : Marker [ ] {
@@ -2911,7 +2957,7 @@ namespace FourSlashInterface {
29112957 this . state . goToTypeDefinition ( definitionIndex ) ;
29122958 }
29132959
2914- public implementation ( implementationIndex = 0 ) {
2960+ public implementation ( implementationIndex ?: number ) {
29152961 this . state . goToImplementation ( implementationIndex ) ;
29162962 }
29172963
0 commit comments