@@ -263,22 +263,31 @@ namespace FourSlash {
263
263
constructor ( private basePath : string , private testType : FourSlashTestType , public testData : FourSlashData ) {
264
264
// Create a new Services Adapter
265
265
this . cancellationToken = new TestCancellationToken ( ) ;
266
- const compilationOptions = convertGlobalOptionsToCompilerOptions ( this . testData . globalOptions ) ;
267
- if ( compilationOptions . typeRoots ) {
268
- compilationOptions . typeRoots = compilationOptions . typeRoots . map ( p => ts . getNormalizedAbsolutePath ( p , this . basePath ) ) ;
269
- }
266
+ let compilationOptions = convertGlobalOptionsToCompilerOptions ( this . testData . globalOptions ) ;
270
267
compilationOptions . skipDefaultLibCheck = true ;
271
268
272
- const languageServiceAdapter = this . getLanguageServiceAdapter ( testType , this . cancellationToken , compilationOptions ) ;
273
- this . languageServiceAdapterHost = languageServiceAdapter . getHost ( ) ;
274
- this . languageService = languageServiceAdapter . getLanguageService ( ) ;
275
-
276
269
// Initialize the language service with all the scripts
277
270
let startResolveFileRef : FourSlashFile ;
278
271
279
272
ts . forEach ( testData . files , file => {
280
273
// Create map between fileName and its content for easily looking up when resolveReference flag is specified
281
274
this . inputFiles [ file . fileName ] = file . content ;
275
+
276
+ if ( ts . getBaseFileName ( file . fileName ) . toLowerCase ( ) === "tsconfig.json" ) {
277
+ const configJson = ts . parseConfigFileTextToJson ( file . fileName , file . content ) ;
278
+ assert . isTrue ( configJson . config !== undefined ) ;
279
+
280
+ // Extend our existing compiler options so that we can also support tsconfig only options
281
+ if ( configJson . config . compilerOptions ) {
282
+ const baseDirectory = ts . normalizePath ( ts . getDirectoryPath ( file . fileName ) ) ;
283
+ const tsConfig = ts . convertCompilerOptionsFromJson ( configJson . config . compilerOptions , baseDirectory , file . fileName ) ;
284
+
285
+ if ( ! tsConfig . errors || ! tsConfig . errors . length ) {
286
+ compilationOptions = ts . extend ( compilationOptions , tsConfig . options ) ;
287
+ }
288
+ }
289
+ }
290
+
282
291
if ( ! startResolveFileRef && file . fileOptions [ metadataOptionNames . resolveReference ] === "true" ) {
283
292
startResolveFileRef = file ;
284
293
}
@@ -288,6 +297,15 @@ namespace FourSlash {
288
297
}
289
298
} ) ;
290
299
300
+
301
+ if ( compilationOptions . typeRoots ) {
302
+ compilationOptions . typeRoots = compilationOptions . typeRoots . map ( p => ts . getNormalizedAbsolutePath ( p , this . basePath ) ) ;
303
+ }
304
+
305
+ const languageServiceAdapter = this . getLanguageServiceAdapter ( testType , this . cancellationToken , compilationOptions ) ;
306
+ this . languageServiceAdapterHost = languageServiceAdapter . getHost ( ) ;
307
+ this . languageService = languageServiceAdapter . getLanguageService ( ) ;
308
+
291
309
if ( startResolveFileRef ) {
292
310
// Add the entry-point file itself into the languageServiceShimHost
293
311
this . languageServiceAdapterHost . addScript ( startResolveFileRef . fileName , startResolveFileRef . content , /*isRootFile*/ true ) ;
@@ -730,10 +748,10 @@ namespace FourSlash {
730
748
}
731
749
}
732
750
733
- public verifyCompletionListContains ( symbol : string , text ?: string , documentation ?: string , kind ?: string ) {
751
+ public verifyCompletionListContains ( symbol : string , text ?: string , documentation ?: string , kind ?: string , spanIndex ?: number ) {
734
752
const completions = this . getCompletionListAtCaret ( ) ;
735
753
if ( completions ) {
736
- this . assertItemInCompletionList ( completions . entries , symbol , text , documentation , kind ) ;
754
+ this . assertItemInCompletionList ( completions . entries , symbol , text , documentation , kind , spanIndex ) ;
737
755
}
738
756
else {
739
757
this . raiseError ( `No completions at position '${ this . currentCaretPosition } ' when looking for '${ symbol } '.` ) ;
@@ -749,25 +767,32 @@ namespace FourSlash {
749
767
* @param expectedText the text associated with the symbol
750
768
* @param expectedDocumentation the documentation text associated with the symbol
751
769
* @param expectedKind the kind of symbol (see ScriptElementKind)
770
+ * @param spanIndex the index of the range that the completion item's replacement text span should match
752
771
*/
753
- public verifyCompletionListDoesNotContain ( symbol : string , expectedText ?: string , expectedDocumentation ?: string , expectedKind ?: string ) {
772
+ public verifyCompletionListDoesNotContain ( symbol : string , expectedText ?: string , expectedDocumentation ?: string , expectedKind ?: string , spanIndex ?: number ) {
754
773
const that = this ;
774
+ let replacementSpan : ts . TextSpan ;
775
+ if ( spanIndex !== undefined ) {
776
+ replacementSpan = this . getTextSpanForRangeAtIndex ( spanIndex ) ;
777
+ }
778
+
755
779
function filterByTextOrDocumentation ( entry : ts . CompletionEntry ) {
756
780
const details = that . getCompletionEntryDetails ( entry . name ) ;
757
781
const documentation = ts . displayPartsToString ( details . documentation ) ;
758
782
const text = ts . displayPartsToString ( details . displayParts ) ;
759
- if ( expectedText && expectedDocumentation ) {
760
- return ( documentation === expectedDocumentation && text === expectedText ) ? true : false ;
783
+
784
+ // If any of the expected values are undefined, assume that users don't
785
+ // care about them.
786
+ if ( replacementSpan && ! TestState . textSpansEqual ( replacementSpan , entry . replacementSpan ) ) {
787
+ return false ;
761
788
}
762
- else if ( expectedText && ! expectedDocumentation ) {
763
- return text === expectedText ? true : false ;
789
+ else if ( expectedText && text !== expectedText ) {
790
+ return false ;
764
791
}
765
- else if ( expectedDocumentation && ! expectedText ) {
766
- return documentation === expectedDocumentation ? true : false ;
792
+ else if ( expectedDocumentation && documentation !== expectedDocumentation ) {
793
+ return false ;
767
794
}
768
- // Because expectedText and expectedDocumentation are undefined, we assume that
769
- // users don"t care to compare them so we will treat that entry as if the entry has matching text and documentation
770
- // and keep it in the list of filtered entry.
795
+
771
796
return true ;
772
797
}
773
798
@@ -791,6 +816,10 @@ namespace FourSlash {
791
816
if ( expectedKind ) {
792
817
error += "Expected kind: " + expectedKind + " to equal: " + filterCompletions [ 0 ] . kind + "." ;
793
818
}
819
+ if ( replacementSpan ) {
820
+ const spanText = filterCompletions [ 0 ] . replacementSpan ? stringify ( filterCompletions [ 0 ] . replacementSpan ) : undefined ;
821
+ error += "Expected replacement span: " + stringify ( replacementSpan ) + " to equal: " + spanText + "." ;
822
+ }
794
823
this . raiseError ( error ) ;
795
824
}
796
825
}
@@ -2188,7 +2217,7 @@ namespace FourSlash {
2188
2217
return text . substring ( startPos , endPos ) ;
2189
2218
}
2190
2219
2191
- private assertItemInCompletionList ( items : ts . CompletionEntry [ ] , name : string , text ?: string , documentation ?: string , kind ?: string ) {
2220
+ private assertItemInCompletionList ( items : ts . CompletionEntry [ ] , name : string , text ?: string , documentation ?: string , kind ?: string , spanIndex ?: number ) {
2192
2221
for ( let i = 0 ; i < items . length ; i ++ ) {
2193
2222
const item = items [ i ] ;
2194
2223
if ( item . name === name ) {
@@ -2207,6 +2236,11 @@ namespace FourSlash {
2207
2236
assert . equal ( item . kind , kind , this . assertionMessageAtLastKnownMarker ( "completion item kind for " + name ) ) ;
2208
2237
}
2209
2238
2239
+ if ( spanIndex !== undefined ) {
2240
+ const span = this . getTextSpanForRangeAtIndex ( spanIndex ) ;
2241
+ assert . isTrue ( TestState . textSpansEqual ( span , item . replacementSpan ) , this . assertionMessageAtLastKnownMarker ( stringify ( span ) + " does not equal " + stringify ( item . replacementSpan ) + " replacement span for " + name ) ) ;
2242
+ }
2243
+
2210
2244
return ;
2211
2245
}
2212
2246
}
@@ -2263,6 +2297,17 @@ namespace FourSlash {
2263
2297
return `line ${ ( pos . line + 1 ) } , col ${ pos . character } ` ;
2264
2298
}
2265
2299
2300
+ private getTextSpanForRangeAtIndex ( index : number ) : ts . TextSpan {
2301
+ const ranges = this . getRanges ( ) ;
2302
+ if ( ranges && ranges . length > index ) {
2303
+ const range = ranges [ index ] ;
2304
+ return { start : range . start , length : range . end - range . start } ;
2305
+ }
2306
+ else {
2307
+ this . raiseError ( "Supplied span index: " + index + " does not exist in range list of size: " + ( ranges ? 0 : ranges . length ) ) ;
2308
+ }
2309
+ }
2310
+
2266
2311
public getMarkerByName ( markerName : string ) {
2267
2312
const markerPos = this . testData . markerPositions [ markerName ] ;
2268
2313
if ( markerPos === undefined ) {
@@ -2286,6 +2331,10 @@ namespace FourSlash {
2286
2331
public resetCancelled ( ) : void {
2287
2332
this . cancellationToken . resetCancelled ( ) ;
2288
2333
}
2334
+
2335
+ private static textSpansEqual ( a : ts . TextSpan , b : ts . TextSpan ) {
2336
+ return a && b && a . start === b . start && a . length === b . length ;
2337
+ }
2289
2338
}
2290
2339
2291
2340
export function runFourSlashTest ( basePath : string , testType : FourSlashTestType , fileName : string ) {
@@ -2294,12 +2343,16 @@ namespace FourSlash {
2294
2343
}
2295
2344
2296
2345
export function runFourSlashTestContent ( basePath : string , testType : FourSlashTestType , content : string , fileName : string ) : void {
2346
+ // Give file paths an absolute path for the virtual file system
2347
+ const absoluteBasePath = ts . combinePaths ( Harness . virtualFileSystemRoot , basePath ) ;
2348
+ const absoluteFileName = ts . combinePaths ( Harness . virtualFileSystemRoot , fileName ) ;
2349
+
2297
2350
// Parse out the files and their metadata
2298
- const testData = parseTestData ( basePath , content , fileName ) ;
2299
- const state = new TestState ( basePath , testType , testData ) ;
2351
+ const testData = parseTestData ( absoluteBasePath , content , absoluteFileName ) ;
2352
+ const state = new TestState ( absoluteBasePath , testType , testData ) ;
2300
2353
const output = ts . transpileModule ( content , { reportDiagnostics : true } ) ;
2301
2354
if ( output . diagnostics . length > 0 ) {
2302
- throw new Error ( `Syntax error in ${ basePath } : ${ output . diagnostics [ 0 ] . messageText } ` ) ;
2355
+ throw new Error ( `Syntax error in ${ absoluteBasePath } : ${ output . diagnostics [ 0 ] . messageText } ` ) ;
2303
2356
}
2304
2357
runCode ( output . outputText , state ) ;
2305
2358
}
@@ -2852,12 +2905,12 @@ namespace FourSlashInterface {
2852
2905
2853
2906
// Verifies the completion list contains the specified symbol. The
2854
2907
// completion list is brought up if necessary
2855
- public completionListContains ( symbol : string , text ?: string , documentation ?: string , kind ?: string ) {
2908
+ public completionListContains ( symbol : string , text ?: string , documentation ?: string , kind ?: string , spanIndex ?: number ) {
2856
2909
if ( this . negative ) {
2857
- this . state . verifyCompletionListDoesNotContain ( symbol , text , documentation , kind ) ;
2910
+ this . state . verifyCompletionListDoesNotContain ( symbol , text , documentation , kind , spanIndex ) ;
2858
2911
}
2859
2912
else {
2860
- this . state . verifyCompletionListContains ( symbol , text , documentation , kind ) ;
2913
+ this . state . verifyCompletionListContains ( symbol , text , documentation , kind , spanIndex ) ;
2861
2914
}
2862
2915
}
2863
2916
0 commit comments