1
+ /// <reference path="..\..\..\src\harness\harness.ts" />
2
+
3
+ namespace ts {
4
+ function notImplemented ( ) : any {
5
+ throw new Error ( "Not yet implemented" ) ;
6
+ }
7
+
8
+ const nullLogger : server . Logger = {
9
+ close : ( ) => void 0 ,
10
+ isVerbose : ( ) => void 0 ,
11
+ loggingEnabled : ( ) => false ,
12
+ perftrc : ( ) => void 0 ,
13
+ info : ( ) => void 0 ,
14
+ startGroup : ( ) => void 0 ,
15
+ endGroup : ( ) => void 0 ,
16
+ msg : ( ) => void 0
17
+ } ;
18
+
19
+ const { content : libFileContent } = Harness . getDefaultLibraryFile ( Harness . IO ) ;
20
+
21
+ function getExecutingFilePathFromLibFile ( libFile : FileOrFolder ) : string {
22
+ return combinePaths ( getDirectoryPath ( libFile . path ) , "tsc.js" ) ;
23
+ }
24
+
25
+ interface FileOrFolder {
26
+ path : string ;
27
+ content ?: string ;
28
+ }
29
+
30
+ interface FSEntry {
31
+ path : Path ;
32
+ fullPath : string ;
33
+ }
34
+
35
+ interface File extends FSEntry {
36
+ content : string ;
37
+ }
38
+
39
+ interface Folder extends FSEntry {
40
+ entries : FSEntry [ ] ;
41
+ }
42
+
43
+ function isFolder ( s : FSEntry ) : s is Folder {
44
+ return isArray ( ( < Folder > s ) . entries ) ;
45
+ }
46
+
47
+ function isFile ( s : FSEntry ) : s is File {
48
+ return typeof ( < File > s ) . content === "string" ;
49
+ }
50
+
51
+ function addFolder ( fullPath : string , toPath : ( s : string ) => Path , fs : FileMap < FSEntry > ) : Folder {
52
+ const path = toPath ( fullPath ) ;
53
+ if ( fs . contains ( path ) ) {
54
+ Debug . assert ( isFolder ( fs . get ( path ) ) ) ;
55
+ return ( < Folder > fs . get ( path ) ) ;
56
+ }
57
+
58
+ const entry : Folder = { path, entries : [ ] , fullPath } ;
59
+ fs . set ( path , entry ) ;
60
+
61
+ const baseFullPath = getDirectoryPath ( fullPath ) ;
62
+ if ( fullPath !== baseFullPath ) {
63
+ addFolder ( baseFullPath , toPath , fs ) . entries . push ( entry ) ;
64
+ }
65
+
66
+ return entry ;
67
+ }
68
+
69
+ function sizeOfMap ( map : Map < any > ) : number {
70
+ let n = 0 ;
71
+ for ( const name in map ) {
72
+ if ( hasProperty ( map , name ) ) {
73
+ n ++ ;
74
+ }
75
+ }
76
+ return n ;
77
+ }
78
+
79
+ function checkMapKeys ( caption : string , map : Map < any > , expectedKeys : string [ ] ) {
80
+ assert . equal ( sizeOfMap ( map ) , expectedKeys . length , `${ caption } : incorrect size of map` ) ;
81
+ for ( const name of expectedKeys ) {
82
+ assert . isTrue ( hasProperty ( map , name ) , `${ caption } is expected to contain ${ name } , actual keys: ${ getKeys ( map ) } ` ) ;
83
+ }
84
+ }
85
+
86
+ function checkFileNames ( caption : string , actualFileNames : string [ ] , expectedFileNames : string [ ] ) {
87
+ assert . equal ( actualFileNames . length , expectedFileNames . length , `${ caption } : incorrect actual number of files, expected ${ JSON . stringify ( expectedFileNames ) } , got ${ actualFileNames } ` ) ;
88
+ for ( const f of expectedFileNames ) {
89
+ assert . isTrue ( contains ( actualFileNames , f ) , `${ caption } : expected to find ${ f } in ${ JSON . stringify ( actualFileNames ) } ` ) ;
90
+ }
91
+ }
92
+
93
+ function readDirectory ( folder : FSEntry , ext : string , excludes : Path [ ] , result : string [ ] ) : void {
94
+ if ( ! folder || ! isFolder ( folder ) || contains ( excludes , folder . path ) ) {
95
+ return ;
96
+ }
97
+ for ( const entry of folder . entries ) {
98
+ if ( contains ( excludes , entry . path ) ) {
99
+ continue ;
100
+ }
101
+ if ( isFolder ( entry ) ) {
102
+ readDirectory ( entry , ext , excludes , result ) ;
103
+ }
104
+ else if ( fileExtensionIs ( entry . path , ext ) ) {
105
+ result . push ( entry . fullPath ) ;
106
+ }
107
+ }
108
+ }
109
+
110
+ class TestServerHost implements server . ServerHost {
111
+ args : string [ ] = [ ] ;
112
+ newLine : "\n" ;
113
+
114
+ private fs : ts . FileMap < FSEntry > ;
115
+ private getCanonicalFileName : ( s : string ) => string ;
116
+ private toPath : ( f : string ) => Path ;
117
+ readonly watchedDirectories : Map < { cb : DirectoryWatcherCallback , recursive : boolean } [ ] > = { } ;
118
+ readonly watchedFiles : Map < FileWatcherCallback [ ] > = { } ;
119
+
120
+ constructor ( public useCaseSensitiveFileNames : boolean , private executingFilePath : string , private currentDirectory : string , fileOrFolderList : FileOrFolder [ ] ) {
121
+ this . getCanonicalFileName = createGetCanonicalFileName ( useCaseSensitiveFileNames ) ;
122
+ this . toPath = s => toPath ( s , currentDirectory , this . getCanonicalFileName ) ;
123
+
124
+ this . reloadFS ( fileOrFolderList ) ;
125
+ }
126
+
127
+ reloadFS ( filesOrFolders : FileOrFolder [ ] ) {
128
+ this . fs = createFileMap < FSEntry > ( ) ;
129
+ for ( const fileOrFolder of filesOrFolders ) {
130
+ const path = this . toPath ( fileOrFolder . path ) ;
131
+ const fullPath = getNormalizedAbsolutePath ( fileOrFolder . path , this . currentDirectory ) ;
132
+ if ( typeof fileOrFolder . content === "string" ) {
133
+ const entry = { path, content : fileOrFolder . content , fullPath } ;
134
+ this . fs . set ( path , entry ) ;
135
+ addFolder ( getDirectoryPath ( fullPath ) , this . toPath , this . fs ) . entries . push ( entry ) ;
136
+ }
137
+ else {
138
+ addFolder ( fullPath , this . toPath , this . fs ) ;
139
+ }
140
+ }
141
+ }
142
+
143
+ fileExists ( s : string ) {
144
+ const path = this . toPath ( s ) ;
145
+ return this . fs . contains ( path ) && isFile ( this . fs . get ( path ) ) ;
146
+ } ;
147
+
148
+ directoryExists ( s : string ) {
149
+ const path = this . toPath ( s ) ;
150
+ return this . fs . contains ( path ) && isFolder ( this . fs . get ( path ) ) ;
151
+ }
152
+
153
+ getDirectories ( s : string ) {
154
+ const path = this . toPath ( s ) ;
155
+ if ( ! this . fs . contains ( path ) ) {
156
+ return [ ] ;
157
+ }
158
+ else {
159
+ const entry = this . fs . get ( path ) ;
160
+ return isFolder ( entry ) ? map ( entry . entries , x => getBaseFileName ( x . fullPath ) ) : [ ] ;
161
+ }
162
+ }
163
+
164
+ readDirectory ( path : string , ext : string , excludes : string [ ] ) : string [ ] {
165
+ const result : string [ ] = [ ] ;
166
+ readDirectory ( this . fs . get ( this . toPath ( path ) ) , ext , map ( excludes , e => toPath ( e , path , this . getCanonicalFileName ) ) , result ) ;
167
+ return result ;
168
+ }
169
+
170
+ watchDirectory ( directoryName : string , callback : DirectoryWatcherCallback , recursive : boolean ) : DirectoryWatcher {
171
+ const path = this . toPath ( directoryName ) ;
172
+ const callbacks = lookUp ( this . watchedDirectories , path ) || ( this . watchedDirectories [ path ] = [ ] ) ;
173
+ callbacks . push ( { cb : callback , recursive } ) ;
174
+ return {
175
+ referenceCount : 0 ,
176
+ directoryName,
177
+ close : ( ) => {
178
+ for ( let i = 0 ; i < callbacks . length ; i ++ ) {
179
+ if ( callbacks [ i ] . cb === callback ) {
180
+ callbacks . splice ( i , 1 ) ;
181
+ break ;
182
+ }
183
+ }
184
+ if ( ! callbacks . length ) {
185
+ delete this . watchedDirectories [ path ] ;
186
+ }
187
+ }
188
+ } ;
189
+ }
190
+
191
+ watchFile ( fileName : string , callback : FileWatcherCallback ) {
192
+ const path = this . toPath ( fileName ) ;
193
+ const callbacks = lookUp ( this . watchedFiles , path ) || ( this . watchedFiles [ path ] = [ ] ) ;
194
+ callbacks . push ( callback ) ;
195
+ return {
196
+ close : ( ) => {
197
+ const i = callbacks . indexOf ( callback ) ;
198
+ callbacks . splice ( i , 1 ) ;
199
+ if ( ! callbacks . length ) {
200
+ delete this . watchedFiles [ path ] ;
201
+ }
202
+ }
203
+ } ;
204
+ }
205
+
206
+ // TOOD: record and invoke callbacks to simulate timer events
207
+ readonly setTimeout = ( callback : ( ...args : any [ ] ) => void , ms : number , ...args : any [ ] ) : any => void 0 ;
208
+ readonly clearTimeout = ( timeoutId : any ) : void => void 0 ;
209
+ readonly readFile = ( s : string ) => ( < File > this . fs . get ( this . toPath ( s ) ) ) . content ;
210
+ readonly resolvePath = ( s : string ) => s ;
211
+ readonly getExecutingFilePath = ( ) => this . executingFilePath ;
212
+ readonly getCurrentDirectory = ( ) => this . currentDirectory ;
213
+ readonly writeFile = ( path : string , content : string ) => notImplemented ( ) ;
214
+ readonly write = ( s : string ) => notImplemented ( ) ;
215
+ readonly createDirectory = ( s : string ) => notImplemented ( ) ;
216
+ readonly exit = ( ) => notImplemented ( ) ;
217
+ }
218
+
219
+ describe ( "tsserver project system:" , ( ) => {
220
+ it ( "create inferred project" , ( ) => {
221
+ const appFile : FileOrFolder = {
222
+ path : "/a/b/c/app.ts" ,
223
+ content : `
224
+ import {f} from "./module"
225
+ console.log(f)
226
+ `
227
+ } ;
228
+ const libFile : FileOrFolder = {
229
+ path : "/a/lib/lib.d.ts" ,
230
+ content : libFileContent
231
+ } ;
232
+ const moduleFile : FileOrFolder = {
233
+ path : "/a/b/c/module.d.ts" ,
234
+ content : `export let x: number`
235
+ } ;
236
+ const host = new TestServerHost ( /*useCaseSensitiveFileNames*/ false , getExecutingFilePathFromLibFile ( libFile ) , "/" , [ appFile , moduleFile , libFile ] ) ;
237
+ const projectService = new server . ProjectService ( host , nullLogger ) ;
238
+ const { configFileName } = projectService . openClientFile ( appFile . path ) ;
239
+
240
+ 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" ) ;
243
+
244
+ const project = projectService . inferredProjects [ 0 ] ;
245
+
246
+ checkFileNames ( "inferred project" , project . getFileNames ( ) , [ appFile . path , libFile . path , moduleFile . path ] ) ;
247
+ checkMapKeys ( "watchedDirectories" , host . watchedDirectories , [ "/a/b/c" , "/a/b" , "/a" ] ) ;
248
+ } ) ;
249
+
250
+ it ( "create configured project without file list" , ( ) => {
251
+ const configFile : FileOrFolder = {
252
+ path : "/a/b/tsconfig.json" ,
253
+ content : `
254
+ {
255
+ "compilerOptions": {},
256
+ "exclude": [
257
+ "e"
258
+ ]
259
+ }`
260
+ } ;
261
+ const libFile : FileOrFolder = {
262
+ path : "/a/lib/lib.d.ts" ,
263
+ content : libFileContent
264
+ } ;
265
+ const file1 : FileOrFolder = {
266
+ path : "/a/b/c/f1.ts" ,
267
+ content : "let x = 1"
268
+ } ;
269
+ const file2 : FileOrFolder = {
270
+ path : "/a/b/d/f2.ts" ,
271
+ content : "let y = 1"
272
+ } ;
273
+ const file3 : FileOrFolder = {
274
+ path : "/a/b/e/f3.ts" ,
275
+ content : "let z = 1"
276
+ } ;
277
+ const host = new TestServerHost ( /*useCaseSensitiveFileNames*/ false , getExecutingFilePathFromLibFile ( libFile ) , "/" , [ configFile , libFile , file1 , file2 , file3 ] ) ;
278
+ const projectService = new server . ProjectService ( host , nullLogger ) ;
279
+ const { configFileName, configFileErrors } = projectService . openClientFile ( file1 . path ) ;
280
+
281
+ assert ( configFileName , "should find config file" ) ;
282
+ 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" ) ;
285
+
286
+ 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 ] ) ;
289
+
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 ) ] ) ;
292
+ } ) ;
293
+ } ) ;
294
+ }
0 commit comments