@@ -107,11 +107,11 @@ namespace ts {
107107 }
108108 }
109109
110- function checkConfiguredProjectNumber ( projectService : server . ProjectService , expected : number ) {
110+ function checkNumberOfConfiguredProjects ( projectService : server . ProjectService , expected : number ) {
111111 assert . equal ( projectService . configuredProjects . length , expected , `expected ${ expected } configured project(s)` ) ;
112112 }
113113
114- function checkInferredProjectNumber ( projectService : server . ProjectService , expected : number ) {
114+ function checkNumberOfInferredProjects ( projectService : server . ProjectService , expected : number ) {
115115 assert . equal ( projectService . inferredProjects . length , expected , `expected ${ expected } inferred project(s)` ) ;
116116 }
117117
@@ -131,13 +131,16 @@ namespace ts {
131131 checkFileNames ( "configuredProjects project, rootFileNames" , project . getRootFiles ( ) , expectedFiles ) ;
132132 }
133133
134+ type TimeOutCallback = ( ) => any ;
135+
134136 class TestServerHost implements server . ServerHost {
135137 args : string [ ] = [ ] ;
136138 newLine : "\n" ;
137139
138140 private fs : ts . FileMap < FSEntry > ;
139141 private getCanonicalFileName : ( s : string ) => string ;
140142 private toPath : ( f : string ) => Path ;
143+ private callbackQueue : TimeOutCallback [ ] = [ ] ;
141144 readonly watchedDirectories : Map < { cb : DirectoryWatcherCallback , recursive : boolean } [ ] > = { } ;
142145 readonly watchedFiles : Map < FileWatcherCallback [ ] > = { } ;
143146
@@ -222,12 +225,12 @@ namespace ts {
222225 }
223226 }
224227
225- triggerFileWatcherCallback ( fileName : string ) : void {
228+ triggerFileWatcherCallback ( fileName : string , removed ?: boolean ) : void {
226229 const path = this . toPath ( fileName ) ;
227230 const callbacks = lookUp ( this . watchedFiles , path ) ;
228231 if ( callbacks ) {
229232 for ( const callback of callbacks ) {
230- callback ( path , /* removed*/ true ) ;
233+ callback ( path , removed ) ;
231234 }
232235 }
233236 }
@@ -248,8 +251,27 @@ namespace ts {
248251 }
249252
250253 // TOOD: record and invoke callbacks to simulate timer events
251- readonly setTimeout = setTimeout ;
252- readonly clearTimeout = ( timeoutId : any ) : void => void 0 ;
254+ readonly setTimeout = ( callback : TimeOutCallback , time : number ) => {
255+ this . callbackQueue . push ( callback ) ;
256+ return this . callbackQueue . length - 1 ;
257+ } ;
258+ readonly clearTimeout = ( timeoutId : any ) : void => {
259+ if ( typeof timeoutId === "number" ) {
260+ this . callbackQueue . splice ( timeoutId , 1 ) ;
261+ }
262+ } ;
263+
264+ checkTimeoutQueueLength ( expected : number ) {
265+ assert . equal ( this . callbackQueue . length , expected , `expected ${ expected } timeout callbacks queued but found ${ this . callbackQueue . length } .` ) ;
266+ }
267+
268+ runQueuedTimeoutCallbacks ( ) {
269+ for ( const callback of this . callbackQueue ) {
270+ callback ( ) ;
271+ }
272+ this . callbackQueue = [ ] ;
273+ }
274+
253275 readonly readFile = ( s : string ) => ( < File > this . fs . get ( this . toPath ( s ) ) ) . content ;
254276 readonly resolvePath = ( s : string ) => s ;
255277 readonly getExecutingFilePath = ( ) => this . executingFilePath ;
@@ -292,8 +314,8 @@ namespace ts {
292314 const { configFileName } = projectService . openClientFile ( appFile . path ) ;
293315
294316 assert ( ! configFileName , `should not find config, got: '${ configFileName } ` ) ;
295- checkConfiguredProjectNumber ( projectService , 0 ) ;
296- checkInferredProjectNumber ( projectService , 1 ) ;
317+ checkNumberOfConfiguredProjects ( projectService , 0 ) ;
318+ checkNumberOfInferredProjects ( projectService , 1 ) ;
297319
298320 const project = projectService . inferredProjects [ 0 ] ;
299321
@@ -331,8 +353,8 @@ namespace ts {
331353
332354 assert ( configFileName , "should find config file" ) ;
333355 assert . isTrue ( ! configFileErrors , `expect no errors in config file, got ${ JSON . stringify ( configFileErrors ) } ` ) ;
334- checkInferredProjectNumber ( projectService , 0 ) ;
335- checkConfiguredProjectNumber ( projectService , 1 ) ;
356+ checkNumberOfInferredProjects ( projectService , 0 ) ;
357+ checkNumberOfConfiguredProjects ( projectService , 1 ) ;
336358
337359 const project = projectService . configuredProjects [ 0 ] ;
338360 checkConfiguredProjectActualFiles ( project , [ file1 . path , libFile . path , file2 . path ] ) ;
@@ -356,27 +378,28 @@ namespace ts {
356378 projectService . openClientFile ( commonFile1 . path ) ;
357379 projectService . openClientFile ( commonFile2 . path ) ;
358380
359- checkInferredProjectNumber ( projectService , 2 ) ;
381+ checkNumberOfInferredProjects ( projectService , 2 ) ;
360382 checkWatchedDirectories ( host , [ "/a/b" , "/a" ] ) ;
361383
362384 // Add a tsconfig file
363385 host . reloadFS ( filesWithConfig ) ;
364386 host . triggerDirectoryWatcherCallback ( "/a/b" , configFile . path ) ;
365387
366- checkInferredProjectNumber ( projectService , 1 ) ;
367- checkConfiguredProjectNumber ( projectService , 1 ) ;
388+ checkNumberOfInferredProjects ( projectService , 1 ) ;
389+ checkNumberOfConfiguredProjects ( projectService , 1 ) ;
368390 // watching all files except one that was open
369391 checkWatchedFiles ( host , [ libFile . path , configFile . path ] ) ;
370392
371393 // remove the tsconfig file
372394 host . reloadFS ( filesWithoutConfig ) ;
373395 host . triggerFileWatcherCallback ( configFile . path ) ;
374- checkInferredProjectNumber ( projectService , 2 ) ;
375- checkConfiguredProjectNumber ( projectService , 0 ) ;
396+
397+ checkNumberOfInferredProjects ( projectService , 2 ) ;
398+ checkNumberOfConfiguredProjects ( projectService , 0 ) ;
376399 checkWatchedDirectories ( host , [ "/a/b" , "/a" ] ) ;
377400 } ) ;
378401
379- it ( "add new files to a configured project without file list" , ( done : ( ) => void ) => {
402+ it ( "add new files to a configured project without file list" , ( ) => {
380403 const configFile : FileOrFolder = {
381404 path : "/a/b/tsconfig.json" ,
382405 content : `{}`
@@ -385,19 +408,17 @@ namespace ts {
385408 const projectService = new server . ProjectService ( host , nullLogger ) ;
386409 projectService . openClientFile ( commonFile1 . path ) ;
387410 checkWatchedDirectories ( host , [ "/a/b" ] ) ;
388- checkConfiguredProjectNumber ( projectService , 1 ) ;
411+ checkNumberOfConfiguredProjects ( projectService , 1 ) ;
389412
390413 const project = projectService . configuredProjects [ 0 ] ;
391414 checkConfiguredProjectRootFiles ( project , [ commonFile1 . path ] ) ;
392415
393416 // add a new ts file
394417 host . reloadFS ( [ commonFile1 , commonFile2 , libFile , configFile ] ) ;
395418 host . triggerDirectoryWatcherCallback ( "/a/b" , commonFile2 . path ) ;
419+ host . runQueuedTimeoutCallbacks ( ) ;
396420 // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer.
397- setTimeout ( ( ) => {
398- checkConfiguredProjectRootFiles ( project , [ commonFile1 . path , commonFile2 . path ] ) ;
399- done ( ) ;
400- } , 1000 ) ;
421+ checkConfiguredProjectRootFiles ( project , [ commonFile1 . path , commonFile2 . path ] ) ;
401422 } ) ;
402423
403424 it ( "should ignore non-existing files specified in the config file" , ( ) => {
@@ -416,13 +437,13 @@ namespace ts {
416437 projectService . openClientFile ( commonFile1 . path ) ;
417438 projectService . openClientFile ( commonFile2 . path ) ;
418439
419- checkConfiguredProjectNumber ( projectService , 1 ) ;
440+ checkNumberOfConfiguredProjects ( projectService , 1 ) ;
420441 const project = projectService . configuredProjects [ 0 ] ;
421442 checkConfiguredProjectRootFiles ( project , [ commonFile1 . path ] ) ;
422- checkInferredProjectNumber ( projectService , 1 ) ;
443+ checkNumberOfInferredProjects ( projectService , 1 ) ;
423444 } ) ;
424445
425- it ( "handle recreated files correctly" , ( done : ( ) => void ) => {
446+ it ( "handle recreated files correctly" , ( ) => {
426447 const configFile : FileOrFolder = {
427448 path : "/a/b/tsconfig.json" ,
428449 content : `{}`
@@ -431,23 +452,120 @@ namespace ts {
431452 const projectService = new server . ProjectService ( host , nullLogger ) ;
432453 projectService . openClientFile ( commonFile1 . path ) ;
433454
434- checkConfiguredProjectNumber ( projectService , 1 ) ;
455+ checkNumberOfConfiguredProjects ( projectService , 1 ) ;
456+ const project = projectService . configuredProjects [ 0 ] ;
457+ checkConfiguredProjectRootFiles ( project , [ commonFile1 . path , commonFile2 . path ] ) ;
458+
459+ // delete commonFile2
460+ host . reloadFS ( [ commonFile1 , configFile ] ) ;
461+ host . triggerDirectoryWatcherCallback ( "/a/b" , commonFile2 . path ) ;
462+ host . runQueuedTimeoutCallbacks ( ) ;
463+ checkConfiguredProjectRootFiles ( project , [ commonFile1 . path ] ) ;
464+
465+ // re-add commonFile2
466+ host . reloadFS ( [ commonFile1 , commonFile2 , configFile ] ) ;
467+ host . triggerDirectoryWatcherCallback ( "/a/b" , commonFile2 . path ) ;
468+ host . runQueuedTimeoutCallbacks ( ) ;
469+ checkConfiguredProjectRootFiles ( project , [ commonFile1 . path , commonFile2 . path ] ) ;
470+ } ) ;
471+
472+ it ( "should create new inferred projects for files excluded from a configured project" , ( ) => {
473+ const configFile : FileOrFolder = {
474+ path : "/a/b/tsconfig.json" ,
475+ content : `{
476+ "compilerOptions": {},
477+ "files": ["${ commonFile1 . path } ", "${ commonFile2 . path } "]
478+ }`
479+ } ;
480+ const files = [ commonFile1 , commonFile2 , configFile ] ;
481+ const host = new TestServerHost ( /*useCaseSensitiveFileNames*/ false , getExecutingFilePathFromLibFile ( libFile ) , "/" , files ) ;
482+ const projectService = new server . ProjectService ( host , nullLogger ) ;
483+ projectService . openClientFile ( commonFile1 . path ) ;
484+
435485 const project = projectService . configuredProjects [ 0 ] ;
436486 checkConfiguredProjectRootFiles ( project , [ commonFile1 . path , commonFile2 . path ] ) ;
487+ configFile . content = `{
488+ "compilerOptions": {},
489+ "files": ["${ commonFile1 . path } "]
490+ }` ;
491+ host . reloadFS ( files ) ;
492+ host . triggerFileWatcherCallback ( configFile . path ) ;
437493
438- // delete commonFile1
439- projectService . closeClientFile ( commonFile1 . path ) ;
440- host . reloadFS ( [ configFile ] ) ;
441- host . triggerDirectoryWatcherCallback ( "/a/b" , commonFile1 . path ) ;
442- host . setTimeout ( ( ) => {
443- // re-add commonFile1
444- host . reloadFS ( [ commonFile1 , configFile ] ) ;
445- projectService . openClientFile ( commonFile1 . path ) ;
446- host . setTimeout ( ( ) => {
447- checkConfiguredProjectRootFiles ( project , [ commonFile1 . path , commonFile2 . path ] ) ;
448- done ( ) ;
449- } , 500 ) ;
450- } , 500 ) ;
494+ checkNumberOfConfiguredProjects ( projectService , 1 ) ;
495+ checkConfiguredProjectRootFiles ( project , [ commonFile1 . path ] ) ;
496+
497+ projectService . openClientFile ( commonFile2 . path ) ;
498+ checkNumberOfInferredProjects ( projectService , 1 ) ;
499+ } ) ;
500+
501+ it ( "files explicitly excluded in config file" , ( ) => {
502+ const configFile : FileOrFolder = {
503+ path : "/a/b/tsconfig.json" ,
504+ content : `{
505+ "compilerOptions": {},
506+ "exclude": ["/a/c"]
507+ }`
508+ } ;
509+ const excludedFile1 : FileOrFolder = {
510+ path : "/a/c/excluedFile1.ts" ,
511+ content : `let t = 1;`
512+ } ;
513+
514+ const host = new TestServerHost ( /*useCaseSensitiveFileNames*/ false , getExecutingFilePathFromLibFile ( libFile ) , "/" , [ commonFile1 , commonFile2 , excludedFile1 , configFile ] ) ;
515+ const projectService = new server . ProjectService ( host , nullLogger ) ;
516+
517+ projectService . openClientFile ( commonFile1 . path ) ;
518+ checkNumberOfConfiguredProjects ( projectService , 1 ) ;
519+ const project = projectService . configuredProjects [ 0 ] ;
520+ checkConfiguredProjectRootFiles ( project , [ commonFile1 . path , commonFile2 . path ] ) ;
521+ projectService . openClientFile ( excludedFile1 . path ) ;
522+ checkNumberOfInferredProjects ( projectService , 1 ) ;
451523 } ) ;
524+
525+ it ( "should properly handle module resolution changes in config file" , ( ) => {
526+ const file1 : FileOrFolder = {
527+ path : "/a/b/file1.ts" ,
528+ content : `import { T } from "module1";`
529+ }
530+ const nodeModuleFile : FileOrFolder = {
531+ path : "/a/b/node_modules/module1.ts" ,
532+ content : `export interface T {}`
533+ }
534+ const classicModuleFile : FileOrFolder = {
535+ path : "/a/module1.ts" ,
536+ content : `export interface T {}`
537+ }
538+ const configFile : FileOrFolder = {
539+ path : "/a/b/tsconfig.json" ,
540+ content : `{
541+ "compilerOptions": {
542+ "moduleResolution": "node"
543+ },
544+ "files": ["${ file1 . path } "]
545+ }`
546+ } ;
547+ const files = [ file1 , nodeModuleFile , classicModuleFile , configFile ] ;
548+ const host = new TestServerHost ( /*useCaseSensitiveFileNames*/ false , getExecutingFilePathFromLibFile ( libFile ) , "/" , files ) ;
549+ const projectService = new server . ProjectService ( host , nullLogger ) ;
550+ projectService . openClientFile ( file1 . path ) ;
551+ projectService . openClientFile ( nodeModuleFile . path ) ;
552+ projectService . openClientFile ( classicModuleFile . path ) ;
553+
554+ checkNumberOfConfiguredProjects ( projectService , 1 ) ;
555+ const project = projectService . configuredProjects [ 0 ] ;
556+ checkConfiguredProjectActualFiles ( project , [ file1 . path , nodeModuleFile . path ] ) ;
557+ checkNumberOfInferredProjects ( projectService , 1 ) ;
558+
559+ configFile . content = `{
560+ "compilerOptions": {
561+ "moduleResolution": "classic"
562+ },
563+ "files": ["${ file1 . path } "]
564+ }` ;
565+ host . reloadFS ( files ) ;
566+ host . triggerFileWatcherCallback ( configFile . path ) ;
567+ checkConfiguredProjectActualFiles ( project , [ file1 . path , classicModuleFile . path ] ) ;
568+ checkNumberOfInferredProjects ( projectService , 1 ) ;
569+ } )
452570 } ) ;
453571}
0 commit comments