@@ -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 ) ;
@@ -342,6 +360,7 @@ namespace FourSlash {
342
360
InsertSpaceAfterFunctionKeywordForAnonymousFunctions : false ,
343
361
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis : false ,
344
362
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets : false ,
363
+ InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces : true ,
345
364
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces : false ,
346
365
InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces : false ,
347
366
PlaceOpenBraceOnNewLineForFunctions : false ,
@@ -730,10 +749,10 @@ namespace FourSlash {
730
749
}
731
750
}
732
751
733
- public verifyCompletionListContains ( symbol : string , text ?: string , documentation ?: string , kind ?: string ) {
752
+ public verifyCompletionListContains ( symbol : string , text ?: string , documentation ?: string , kind ?: string , spanIndex ?: number ) {
734
753
const completions = this . getCompletionListAtCaret ( ) ;
735
754
if ( completions ) {
736
- this . assertItemInCompletionList ( completions . entries , symbol , text , documentation , kind ) ;
755
+ this . assertItemInCompletionList ( completions . entries , symbol , text , documentation , kind , spanIndex ) ;
737
756
}
738
757
else {
739
758
this . raiseError ( `No completions at position '${ this . currentCaretPosition } ' when looking for '${ symbol } '.` ) ;
@@ -749,25 +768,32 @@ namespace FourSlash {
749
768
* @param expectedText the text associated with the symbol
750
769
* @param expectedDocumentation the documentation text associated with the symbol
751
770
* @param expectedKind the kind of symbol (see ScriptElementKind)
771
+ * @param spanIndex the index of the range that the completion item's replacement text span should match
752
772
*/
753
- public verifyCompletionListDoesNotContain ( symbol : string , expectedText ?: string , expectedDocumentation ?: string , expectedKind ?: string ) {
773
+ public verifyCompletionListDoesNotContain ( symbol : string , expectedText ?: string , expectedDocumentation ?: string , expectedKind ?: string , spanIndex ?: number ) {
754
774
const that = this ;
775
+ let replacementSpan : ts . TextSpan ;
776
+ if ( spanIndex !== undefined ) {
777
+ replacementSpan = this . getTextSpanForRangeAtIndex ( spanIndex ) ;
778
+ }
779
+
755
780
function filterByTextOrDocumentation ( entry : ts . CompletionEntry ) {
756
781
const details = that . getCompletionEntryDetails ( entry . name ) ;
757
782
const documentation = ts . displayPartsToString ( details . documentation ) ;
758
783
const text = ts . displayPartsToString ( details . displayParts ) ;
759
- if ( expectedText && expectedDocumentation ) {
760
- return ( documentation === expectedDocumentation && text === expectedText ) ? true : false ;
784
+
785
+ // If any of the expected values are undefined, assume that users don't
786
+ // care about them.
787
+ if ( replacementSpan && ! TestState . textSpansEqual ( replacementSpan , entry . replacementSpan ) ) {
788
+ return false ;
761
789
}
762
- else if ( expectedText && ! expectedDocumentation ) {
763
- return text === expectedText ? true : false ;
790
+ else if ( expectedText && text !== expectedText ) {
791
+ return false ;
764
792
}
765
- else if ( expectedDocumentation && ! expectedText ) {
766
- return documentation === expectedDocumentation ? true : false ;
793
+ else if ( expectedDocumentation && documentation !== expectedDocumentation ) {
794
+ return false ;
767
795
}
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.
796
+
771
797
return true ;
772
798
}
773
799
@@ -791,6 +817,10 @@ namespace FourSlash {
791
817
if ( expectedKind ) {
792
818
error += "Expected kind: " + expectedKind + " to equal: " + filterCompletions [ 0 ] . kind + "." ;
793
819
}
820
+ if ( replacementSpan ) {
821
+ const spanText = filterCompletions [ 0 ] . replacementSpan ? stringify ( filterCompletions [ 0 ] . replacementSpan ) : undefined ;
822
+ error += "Expected replacement span: " + stringify ( replacementSpan ) + " to equal: " + spanText + "." ;
823
+ }
794
824
this . raiseError ( error ) ;
795
825
}
796
826
}
@@ -2188,7 +2218,7 @@ namespace FourSlash {
2188
2218
return text . substring ( startPos , endPos ) ;
2189
2219
}
2190
2220
2191
- private assertItemInCompletionList ( items : ts . CompletionEntry [ ] , name : string , text ?: string , documentation ?: string , kind ?: string ) {
2221
+ private assertItemInCompletionList ( items : ts . CompletionEntry [ ] , name : string , text ?: string , documentation ?: string , kind ?: string , spanIndex ?: number ) {
2192
2222
for ( let i = 0 ; i < items . length ; i ++ ) {
2193
2223
const item = items [ i ] ;
2194
2224
if ( item . name === name ) {
@@ -2207,6 +2237,11 @@ namespace FourSlash {
2207
2237
assert . equal ( item . kind , kind , this . assertionMessageAtLastKnownMarker ( "completion item kind for " + name ) ) ;
2208
2238
}
2209
2239
2240
+ if ( spanIndex !== undefined ) {
2241
+ const span = this . getTextSpanForRangeAtIndex ( spanIndex ) ;
2242
+ assert . isTrue ( TestState . textSpansEqual ( span , item . replacementSpan ) , this . assertionMessageAtLastKnownMarker ( stringify ( span ) + " does not equal " + stringify ( item . replacementSpan ) + " replacement span for " + name ) ) ;
2243
+ }
2244
+
2210
2245
return ;
2211
2246
}
2212
2247
}
@@ -2263,6 +2298,17 @@ namespace FourSlash {
2263
2298
return `line ${ ( pos . line + 1 ) } , col ${ pos . character } ` ;
2264
2299
}
2265
2300
2301
+ private getTextSpanForRangeAtIndex ( index : number ) : ts . TextSpan {
2302
+ const ranges = this . getRanges ( ) ;
2303
+ if ( ranges && ranges . length > index ) {
2304
+ const range = ranges [ index ] ;
2305
+ return { start : range . start , length : range . end - range . start } ;
2306
+ }
2307
+ else {
2308
+ this . raiseError ( "Supplied span index: " + index + " does not exist in range list of size: " + ( ranges ? 0 : ranges . length ) ) ;
2309
+ }
2310
+ }
2311
+
2266
2312
public getMarkerByName ( markerName : string ) {
2267
2313
const markerPos = this . testData . markerPositions [ markerName ] ;
2268
2314
if ( markerPos === undefined ) {
@@ -2286,6 +2332,10 @@ namespace FourSlash {
2286
2332
public resetCancelled ( ) : void {
2287
2333
this . cancellationToken . resetCancelled ( ) ;
2288
2334
}
2335
+
2336
+ private static textSpansEqual ( a : ts . TextSpan , b : ts . TextSpan ) {
2337
+ return a && b && a . start === b . start && a . length === b . length ;
2338
+ }
2289
2339
}
2290
2340
2291
2341
export function runFourSlashTest ( basePath : string , testType : FourSlashTestType , fileName : string ) {
@@ -2294,12 +2344,16 @@ namespace FourSlash {
2294
2344
}
2295
2345
2296
2346
export function runFourSlashTestContent ( basePath : string , testType : FourSlashTestType , content : string , fileName : string ) : void {
2347
+ // Give file paths an absolute path for the virtual file system
2348
+ const absoluteBasePath = ts . combinePaths ( Harness . virtualFileSystemRoot , basePath ) ;
2349
+ const absoluteFileName = ts . combinePaths ( Harness . virtualFileSystemRoot , fileName ) ;
2350
+
2297
2351
// Parse out the files and their metadata
2298
- const testData = parseTestData ( basePath , content , fileName ) ;
2299
- const state = new TestState ( basePath , testType , testData ) ;
2352
+ const testData = parseTestData ( absoluteBasePath , content , absoluteFileName ) ;
2353
+ const state = new TestState ( absoluteBasePath , testType , testData ) ;
2300
2354
const output = ts . transpileModule ( content , { reportDiagnostics : true } ) ;
2301
2355
if ( output . diagnostics . length > 0 ) {
2302
- throw new Error ( `Syntax error in ${ basePath } : ${ output . diagnostics [ 0 ] . messageText } ` ) ;
2356
+ throw new Error ( `Syntax error in ${ absoluteBasePath } : ${ output . diagnostics [ 0 ] . messageText } ` ) ;
2303
2357
}
2304
2358
runCode ( output . outputText , state ) ;
2305
2359
}
@@ -2852,12 +2906,12 @@ namespace FourSlashInterface {
2852
2906
2853
2907
// Verifies the completion list contains the specified symbol. The
2854
2908
// completion list is brought up if necessary
2855
- public completionListContains ( symbol : string , text ?: string , documentation ?: string , kind ?: string ) {
2909
+ public completionListContains ( symbol : string , text ?: string , documentation ?: string , kind ?: string , spanIndex ?: number ) {
2856
2910
if ( this . negative ) {
2857
- this . state . verifyCompletionListDoesNotContain ( symbol , text , documentation , kind ) ;
2911
+ this . state . verifyCompletionListDoesNotContain ( symbol , text , documentation , kind , spanIndex ) ;
2858
2912
}
2859
2913
else {
2860
- this . state . verifyCompletionListContains ( symbol , text , documentation , kind ) ;
2914
+ this . state . verifyCompletionListContains ( symbol , text , documentation , kind , spanIndex ) ;
2861
2915
}
2862
2916
}
2863
2917
0 commit comments