@@ -263,22 +263,31 @@ namespace FourSlash {
263263 constructor ( private basePath : string , private testType : FourSlashTestType , public testData : FourSlashData ) {
264264 // Create a new Services Adapter
265265 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 ) ;
270267 compilationOptions . skipDefaultLibCheck = true ;
271268
272- const languageServiceAdapter = this . getLanguageServiceAdapter ( testType , this . cancellationToken , compilationOptions ) ;
273- this . languageServiceAdapterHost = languageServiceAdapter . getHost ( ) ;
274- this . languageService = languageServiceAdapter . getLanguageService ( ) ;
275-
276269 // Initialize the language service with all the scripts
277270 let startResolveFileRef : FourSlashFile ;
278271
279272 ts . forEach ( testData . files , file => {
280273 // Create map between fileName and its content for easily looking up when resolveReference flag is specified
281274 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+
282291 if ( ! startResolveFileRef && file . fileOptions [ metadataOptionNames . resolveReference ] === "true" ) {
283292 startResolveFileRef = file ;
284293 }
@@ -288,6 +297,15 @@ namespace FourSlash {
288297 }
289298 } ) ;
290299
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+
291309 if ( startResolveFileRef ) {
292310 // Add the entry-point file itself into the languageServiceShimHost
293311 this . languageServiceAdapterHost . addScript ( startResolveFileRef . fileName , startResolveFileRef . content , /*isRootFile*/ true ) ;
@@ -342,6 +360,7 @@ namespace FourSlash {
342360 InsertSpaceAfterFunctionKeywordForAnonymousFunctions : false ,
343361 InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis : false ,
344362 InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets : false ,
363+ InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces : true ,
345364 InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces : false ,
346365 InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces : false ,
347366 PlaceOpenBraceOnNewLineForFunctions : false ,
@@ -730,10 +749,10 @@ namespace FourSlash {
730749 }
731750 }
732751
733- public verifyCompletionListContains ( symbol : string , text ?: string , documentation ?: string , kind ?: string ) {
752+ public verifyCompletionListContains ( symbol : string , text ?: string , documentation ?: string , kind ?: string , spanIndex ?: number ) {
734753 const completions = this . getCompletionListAtCaret ( ) ;
735754 if ( completions ) {
736- this . assertItemInCompletionList ( completions . entries , symbol , text , documentation , kind ) ;
755+ this . assertItemInCompletionList ( completions . entries , symbol , text , documentation , kind , spanIndex ) ;
737756 }
738757 else {
739758 this . raiseError ( `No completions at position '${ this . currentCaretPosition } ' when looking for '${ symbol } '.` ) ;
@@ -749,25 +768,32 @@ namespace FourSlash {
749768 * @param expectedText the text associated with the symbol
750769 * @param expectedDocumentation the documentation text associated with the symbol
751770 * @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
752772 */
753- public verifyCompletionListDoesNotContain ( symbol : string , expectedText ?: string , expectedDocumentation ?: string , expectedKind ?: string ) {
773+ public verifyCompletionListDoesNotContain ( symbol : string , expectedText ?: string , expectedDocumentation ?: string , expectedKind ?: string , spanIndex ?: number ) {
754774 const that = this ;
775+ let replacementSpan : ts . TextSpan ;
776+ if ( spanIndex !== undefined ) {
777+ replacementSpan = this . getTextSpanForRangeAtIndex ( spanIndex ) ;
778+ }
779+
755780 function filterByTextOrDocumentation ( entry : ts . CompletionEntry ) {
756781 const details = that . getCompletionEntryDetails ( entry . name ) ;
757782 const documentation = ts . displayPartsToString ( details . documentation ) ;
758783 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 ;
761789 }
762- else if ( expectedText && ! expectedDocumentation ) {
763- return text === expectedText ? true : false ;
790+ else if ( expectedText && text !== expectedText ) {
791+ return false ;
764792 }
765- else if ( expectedDocumentation && ! expectedText ) {
766- return documentation === expectedDocumentation ? true : false ;
793+ else if ( expectedDocumentation && documentation !== expectedDocumentation ) {
794+ return false ;
767795 }
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+
771797 return true ;
772798 }
773799
@@ -791,6 +817,10 @@ namespace FourSlash {
791817 if ( expectedKind ) {
792818 error += "Expected kind: " + expectedKind + " to equal: " + filterCompletions [ 0 ] . kind + "." ;
793819 }
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+ }
794824 this . raiseError ( error ) ;
795825 }
796826 }
@@ -2188,7 +2218,7 @@ namespace FourSlash {
21882218 return text . substring ( startPos , endPos ) ;
21892219 }
21902220
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 ) {
21922222 for ( let i = 0 ; i < items . length ; i ++ ) {
21932223 const item = items [ i ] ;
21942224 if ( item . name === name ) {
@@ -2207,6 +2237,11 @@ namespace FourSlash {
22072237 assert . equal ( item . kind , kind , this . assertionMessageAtLastKnownMarker ( "completion item kind for " + name ) ) ;
22082238 }
22092239
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+
22102245 return ;
22112246 }
22122247 }
@@ -2263,6 +2298,17 @@ namespace FourSlash {
22632298 return `line ${ ( pos . line + 1 ) } , col ${ pos . character } ` ;
22642299 }
22652300
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+
22662312 public getMarkerByName ( markerName : string ) {
22672313 const markerPos = this . testData . markerPositions [ markerName ] ;
22682314 if ( markerPos === undefined ) {
@@ -2286,6 +2332,10 @@ namespace FourSlash {
22862332 public resetCancelled ( ) : void {
22872333 this . cancellationToken . resetCancelled ( ) ;
22882334 }
2335+
2336+ private static textSpansEqual ( a : ts . TextSpan , b : ts . TextSpan ) {
2337+ return a && b && a . start === b . start && a . length === b . length ;
2338+ }
22892339 }
22902340
22912341 export function runFourSlashTest ( basePath : string , testType : FourSlashTestType , fileName : string ) {
@@ -2294,12 +2344,16 @@ namespace FourSlash {
22942344 }
22952345
22962346 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+
22972351 // 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 ) ;
23002354 const output = ts . transpileModule ( content , { reportDiagnostics : true } ) ;
23012355 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 } ` ) ;
23032357 }
23042358 runCode ( output . outputText , state ) ;
23052359 }
@@ -2852,12 +2906,12 @@ namespace FourSlashInterface {
28522906
28532907 // Verifies the completion list contains the specified symbol. The
28542908 // 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 ) {
28562910 if ( this . negative ) {
2857- this . state . verifyCompletionListDoesNotContain ( symbol , text , documentation , kind ) ;
2911+ this . state . verifyCompletionListDoesNotContain ( symbol , text , documentation , kind , spanIndex ) ;
28582912 }
28592913 else {
2860- this . state . verifyCompletionListContains ( symbol , text , documentation , kind ) ;
2914+ this . state . verifyCompletionListContains ( symbol , text , documentation , kind , spanIndex ) ;
28612915 }
28622916 }
28632917
0 commit comments