@@ -107,6 +107,30 @@ namespace ts {
107107 }
108108 }
109109
110+ function checkConfiguredProjectNumber ( projectService : server . ProjectService , expected : number ) {
111+ assert . equal ( projectService . configuredProjects . length , expected , `expected ${ expected } configured project(s)` ) ;
112+ }
113+
114+ function checkInferredProjectNumber ( projectService : server . ProjectService , expected : number ) {
115+ assert . equal ( projectService . inferredProjects . length , expected , `expected ${ expected } inferred project(s)` ) ;
116+ }
117+
118+ function checkWatchedFiles ( host : TestServerHost , expectedFiles : string [ ] ) {
119+ checkMapKeys ( "watchedFiles" , host . watchedFiles , expectedFiles ) ;
120+ }
121+
122+ function checkWatchedDirectories ( host : TestServerHost , expectedDirectories : string [ ] ) {
123+ checkMapKeys ( "watchedDirectories" , host . watchedDirectories , expectedDirectories ) ;
124+ }
125+
126+ function checkConfiguredProjectActualFiles ( project : server . Project , expectedFiles : string [ ] ) {
127+ checkFileNames ( "configuredProjects project, actualFileNames" , project . getFileNames ( ) , expectedFiles ) ;
128+ }
129+
130+ function checkConfiguredProjectRootFiles ( project : server . Project , expectedFiles : string [ ] ) {
131+ checkFileNames ( "configuredProjects project, rootFileNames" , project . getRootFiles ( ) , expectedFiles ) ;
132+ }
133+
110134 class TestServerHost implements server . ServerHost {
111135 args : string [ ] = [ ] ;
112136 newLine : "\n" ;
@@ -188,6 +212,26 @@ namespace ts {
188212 } ;
189213 }
190214
215+ triggerDirectoryWatcherCallback ( directoryName : string , fileName : string ) : void {
216+ const path = this . toPath ( directoryName ) ;
217+ const callbacks = lookUp ( this . watchedDirectories , path ) ;
218+ if ( callbacks ) {
219+ for ( const callback of callbacks ) {
220+ callback . cb ( fileName ) ;
221+ }
222+ }
223+ }
224+
225+ triggerFileWatcherCallback ( fileName : string ) : void {
226+ const path = this . toPath ( fileName ) ;
227+ const callbacks = lookUp ( this . watchedFiles , path ) ;
228+ if ( callbacks ) {
229+ for ( const callback of callbacks ) {
230+ callback ( path , /*removed*/ true ) ;
231+ }
232+ }
233+ }
234+
191235 watchFile ( fileName : string , callback : FileWatcherCallback ) {
192236 const path = this . toPath ( fileName ) ;
193237 const callbacks = lookUp ( this . watchedFiles , path ) || ( this . watchedFiles [ path ] = [ ] ) ;
@@ -204,7 +248,7 @@ namespace ts {
204248 }
205249
206250 // TOOD: record and invoke callbacks to simulate timer events
207- readonly setTimeout = ( callback : ( ... args : any [ ] ) => void , ms : number , ... args : any [ ] ) : any => void 0 ;
251+ readonly setTimeout = setTimeout ;
208252 readonly clearTimeout = ( timeoutId : any ) : void => void 0 ;
209253 readonly readFile = ( s : string ) => ( < File > this . fs . get ( this . toPath ( s ) ) ) . content ;
210254 readonly resolvePath = ( s : string ) => s ;
@@ -216,7 +260,20 @@ namespace ts {
216260 readonly exit = ( ) => notImplemented ( ) ;
217261 }
218262
219- describe ( "tsserver project system:" , ( ) => {
263+ describe ( "tsserver-project-system" , ( ) => {
264+ const commonFile1 : FileOrFolder = {
265+ path : "/a/b/commonFile1.ts" ,
266+ content : "let x = 1"
267+ } ;
268+ const commonFile2 : FileOrFolder = {
269+ path : "/a/b/commonFile2.ts" ,
270+ content : "let y = 1"
271+ } ;
272+ const libFile : FileOrFolder = {
273+ path : "/a/lib/lib.d.ts" ,
274+ content : libFileContent
275+ } ;
276+
220277 it ( "create inferred project" , ( ) => {
221278 const appFile : FileOrFolder = {
222279 path : "/a/b/c/app.ts" ,
@@ -225,10 +282,7 @@ namespace ts {
225282 console.log(f)
226283 `
227284 } ;
228- const libFile : FileOrFolder = {
229- path : "/a/lib/lib.d.ts" ,
230- content : libFileContent
231- } ;
285+
232286 const moduleFile : FileOrFolder = {
233287 path : "/a/b/c/module.d.ts" ,
234288 content : `export let x: number`
@@ -238,13 +292,13 @@ namespace ts {
238292 const { configFileName } = projectService . openClientFile ( appFile . path ) ;
239293
240294 assert ( ! configFileName , `should not find config, got: '${ configFileName } ` ) ;
241- assert . equal ( projectService . inferredProjects . length , 1 , "expected one inferred project" ) ;
242- assert . equal ( projectService . configuredProjects . length , 0 , "expected no configured project" ) ;
295+ checkConfiguredProjectNumber ( projectService , 0 ) ;
296+ checkInferredProjectNumber ( projectService , 1 ) ;
243297
244298 const project = projectService . inferredProjects [ 0 ] ;
245299
246300 checkFileNames ( "inferred project" , project . getFileNames ( ) , [ appFile . path , libFile . path , moduleFile . path ] ) ;
247- checkMapKeys ( "watchedDirectories" , host . watchedDirectories , [ "/a/b/c" , "/a/b" , "/a" ] ) ;
301+ checkWatchedDirectories ( host , [ "/a/b/c" , "/a/b" , "/a" ] ) ;
248302 } ) ;
249303
250304 it ( "create configured project without file list" , ( ) => {
@@ -258,10 +312,6 @@ namespace ts {
258312 ]
259313 }`
260314 } ;
261- const libFile : FileOrFolder = {
262- path : "/a/lib/lib.d.ts" ,
263- content : libFileContent
264- } ;
265315 const file1 : FileOrFolder = {
266316 path : "/a/b/c/f1.ts" ,
267317 content : "let x = 1"
@@ -274,21 +324,130 @@ namespace ts {
274324 path : "/a/b/e/f3.ts" ,
275325 content : "let z = 1"
276326 } ;
327+
277328 const host = new TestServerHost ( /*useCaseSensitiveFileNames*/ false , getExecutingFilePathFromLibFile ( libFile ) , "/" , [ configFile , libFile , file1 , file2 , file3 ] ) ;
278329 const projectService = new server . ProjectService ( host , nullLogger ) ;
279330 const { configFileName, configFileErrors } = projectService . openClientFile ( file1 . path ) ;
280331
281332 assert ( configFileName , "should find config file" ) ;
282333 assert . isTrue ( ! configFileErrors , `expect no errors in config file, got ${ JSON . stringify ( configFileErrors ) } ` ) ;
283- assert . equal ( projectService . inferredProjects . length , 0 , "expected no inferred project" ) ;
284- assert . equal ( projectService . configuredProjects . length , 1 , "expected one configured project" ) ;
334+ checkInferredProjectNumber ( projectService , 0 ) ;
335+ checkConfiguredProjectNumber ( projectService , 1 ) ;
285336
286337 const project = projectService . configuredProjects [ 0 ] ;
287- checkFileNames ( "configuredProjects project, actualFileNames" , project . getFileNames ( ) , [ file1 . path , libFile . path , file2 . path ] ) ;
288- checkFileNames ( "configuredProjects project, rootFileNames" , project . getRootFiles ( ) , [ file1 . path , file2 . path ] ) ;
338+ checkConfiguredProjectActualFiles ( project , [ file1 . path , libFile . path , file2 . path ] ) ;
339+ checkConfiguredProjectRootFiles ( project , [ file1 . path , file2 . path ] ) ;
340+ // watching all files except one that was open
341+ checkWatchedFiles ( host , [ configFile . path , file2 . path , libFile . path ] ) ;
342+ checkWatchedDirectories ( host , [ getDirectoryPath ( configFile . path ) ] ) ;
343+ } ) ;
289344
290- checkMapKeys ( "watchedFiles" , host . watchedFiles , [ configFile . path , file2 . path , libFile . path ] ) ; // watching all files except one that was open
291- checkMapKeys ( "watchedDirectories" , host . watchedDirectories , [ getDirectoryPath ( configFile . path ) ] ) ;
345+ it ( "add and then remove a config file in a folder with loose files" , ( ) => {
346+ const configFile : FileOrFolder = {
347+ path : "/a/b/tsconfig.json" ,
348+ content : `{
349+ "files": ["commonFile1.ts"]
350+ }`
351+ } ;
352+ const filesWithoutConfig = [ libFile , commonFile1 , commonFile2 ] ;
353+ const filesWithConfig = [ libFile , commonFile1 , commonFile2 , configFile ] ;
354+ const host = new TestServerHost ( /*useCaseSensitiveFileNames*/ false , getExecutingFilePathFromLibFile ( libFile ) , "/" , filesWithoutConfig ) ;
355+ const projectService = new server . ProjectService ( host , nullLogger ) ;
356+ projectService . openClientFile ( commonFile1 . path ) ;
357+ projectService . openClientFile ( commonFile2 . path ) ;
358+
359+ checkInferredProjectNumber ( projectService , 2 ) ;
360+ checkWatchedDirectories ( host , [ "/a/b" , "/a" ] ) ;
361+
362+ // Add a tsconfig file
363+ host . reloadFS ( filesWithConfig ) ;
364+ host . triggerDirectoryWatcherCallback ( "/a/b" , configFile . path ) ;
365+
366+ checkInferredProjectNumber ( projectService , 1 ) ;
367+ checkConfiguredProjectNumber ( projectService , 1 ) ;
368+ // watching all files except one that was open
369+ checkWatchedFiles ( host , [ libFile . path , configFile . path ] ) ;
370+
371+ // remove the tsconfig file
372+ host . reloadFS ( filesWithoutConfig ) ;
373+ host . triggerFileWatcherCallback ( configFile . path ) ;
374+ checkInferredProjectNumber ( projectService , 2 ) ;
375+ checkConfiguredProjectNumber ( projectService , 0 ) ;
376+ checkWatchedDirectories ( host , [ "/a/b" , "/a" ] ) ;
377+ } ) ;
378+
379+ it ( "add new files to a configured project without file list" , ( done : ( ) => void ) => {
380+ const configFile : FileOrFolder = {
381+ path : "/a/b/tsconfig.json" ,
382+ content : `{}`
383+ } ;
384+ const host = new TestServerHost ( /*useCaseSensitiveFileNames*/ false , getExecutingFilePathFromLibFile ( libFile ) , "/" , [ commonFile1 , libFile , configFile ] ) ;
385+ const projectService = new server . ProjectService ( host , nullLogger ) ;
386+ projectService . openClientFile ( commonFile1 . path ) ;
387+ checkWatchedDirectories ( host , [ "/a/b" ] ) ;
388+ checkConfiguredProjectNumber ( projectService , 1 ) ;
389+
390+ const project = projectService . configuredProjects [ 0 ] ;
391+ checkConfiguredProjectRootFiles ( project , [ commonFile1 . path ] ) ;
392+
393+ // add a new ts file
394+ host . reloadFS ( [ commonFile1 , commonFile2 , libFile , configFile ] ) ;
395+ host . triggerDirectoryWatcherCallback ( "/a/b" , commonFile2 . path ) ;
396+ // 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 ) ;
401+ } ) ;
402+
403+ it ( "should ignore non-existing files specified in the config file" , ( ) => {
404+ const configFile : FileOrFolder = {
405+ path : "/a/b/tsconfig.json" ,
406+ content : `{
407+ "compilerOptions": {},
408+ "files": [
409+ "commonFile1.ts",
410+ "commonFile3.ts"
411+ ]
412+ }`
413+ } ;
414+ const host = new TestServerHost ( /*useCaseSensitiveFileNames*/ false , getExecutingFilePathFromLibFile ( libFile ) , "/" , [ commonFile1 , commonFile2 , configFile ] ) ;
415+ const projectService = new server . ProjectService ( host , nullLogger ) ;
416+ projectService . openClientFile ( commonFile1 . path ) ;
417+ projectService . openClientFile ( commonFile2 . path ) ;
418+
419+ checkConfiguredProjectNumber ( projectService , 1 ) ;
420+ const project = projectService . configuredProjects [ 0 ] ;
421+ checkConfiguredProjectRootFiles ( project , [ commonFile1 . path ] ) ;
422+ checkInferredProjectNumber ( projectService , 1 ) ;
423+ } ) ;
424+
425+ it ( "handle recreated files correctly" , ( done : ( ) => void ) => {
426+ const configFile : FileOrFolder = {
427+ path : "/a/b/tsconfig.json" ,
428+ content : `{}`
429+ } ;
430+ const host = new TestServerHost ( /*useCaseSensitiveFileNames*/ false , getExecutingFilePathFromLibFile ( libFile ) , "/" , [ commonFile1 , commonFile2 , configFile ] ) ;
431+ const projectService = new server . ProjectService ( host , nullLogger ) ;
432+ projectService . openClientFile ( commonFile1 . path ) ;
433+
434+ checkConfiguredProjectNumber ( projectService , 1 ) ;
435+ const project = projectService . configuredProjects [ 0 ] ;
436+ checkConfiguredProjectRootFiles ( project , [ commonFile1 . path , commonFile2 . path ] ) ;
437+
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 ) ;
292451 } ) ;
293452 } ) ;
294453}
0 commit comments