• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    export function checkResolvedModule(actual: ResolvedModuleFull | undefined, expected: ResolvedModuleFull | undefined): boolean {
3        if (!expected) {
4            if (actual) {
5                assert.fail(actual, expected, "expected resolved module to be undefined");
6            }
7            return true;
8        }
9        else if (!actual) {
10            assert.fail(actual, expected, "expected resolved module to be defined");
11        }
12
13        assert.isTrue(actual.resolvedFileName === expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`);
14        assert.isTrue(actual.extension === expected.extension, `'ext': expected '${actual.extension}' to be equal to '${expected.extension}'`);
15        assert.isTrue(actual.isExternalLibraryImport === expected.isExternalLibraryImport, `'isExternalLibraryImport': expected '${actual.isExternalLibraryImport}' to be equal to '${expected.isExternalLibraryImport}'`);
16        return true;
17    }
18
19    export function checkResolvedModuleWithFailedLookupLocations(actual: ResolvedModuleWithFailedLookupLocations, expectedResolvedModule: ResolvedModuleFull, expectedFailedLookupLocations: string[]): void {
20        assert.isTrue(actual.resolvedModule !== undefined, "module should be resolved");
21        checkResolvedModule(actual.resolvedModule, expectedResolvedModule);
22        assert.deepEqual(actual.failedLookupLocations, expectedFailedLookupLocations, `Failed lookup locations should match - expected has ${expectedFailedLookupLocations.length}, actual has ${actual.failedLookupLocations.length}`);
23    }
24
25    export function createResolvedModule(resolvedFileName: string, isExternalLibraryImport = false): ResolvedModuleFull {
26        return { resolvedFileName, extension: extensionFromPath(resolvedFileName), isExternalLibraryImport };
27    }
28
29    interface File {
30        name: string;
31        content?: string;
32        symlinks?: string[];
33    }
34
35    function createModuleResolutionHost(hasDirectoryExists: boolean, ...files: File[]): ModuleResolutionHost {
36        const map = new Map<string, File>();
37        for (const file of files) {
38            map.set(file.name, file);
39            if (file.symlinks) {
40                for (const symlink of file.symlinks) {
41                    map.set(symlink, file);
42                }
43            }
44        }
45
46        if (hasDirectoryExists) {
47            const directories = new Map<string, string>();
48            for (const f of files) {
49                let name = getDirectoryPath(f.name);
50                while (true) {
51                    directories.set(name, name);
52                    const baseName = getDirectoryPath(name);
53                    if (baseName === name) {
54                        break;
55                    }
56                    name = baseName;
57                }
58            }
59            return {
60                readFile,
61                realpath,
62                directoryExists: path => directories.has(path),
63                fileExists: path => {
64                    assert.isTrue(directories.has(getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`);
65                    return map.has(path);
66                },
67                useCaseSensitiveFileNames: true
68            };
69        }
70        else {
71            return { readFile, realpath, fileExists: path => map.has(path), useCaseSensitiveFileNames: true };
72        }
73        function readFile(path: string): string | undefined {
74            const file = map.get(path);
75            return file && file.content;
76        }
77        function realpath(path: string): string {
78            return map.get(path)!.name;
79        }
80    }
81
82    describe("unittests:: moduleResolution:: Node module resolution - relative paths", () => {
83        // node module resolution does _not_ implicitly append these extensions to an extensionless path (though will still attempt to load them if explicitly)
84        const nonImplicitExtensions = [Extension.Mts, Extension.Dmts, Extension.Mjs, Extension.Cts, Extension.Dcts, Extension.Cjs, Extension.Dets];
85        const autoExtensions = filter(supportedTSExtensionsFlat, e => nonImplicitExtensions.indexOf(e) === -1);
86        function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void {
87            for (const ext of autoExtensions) {
88                test(ext, /*hasDirectoryExists*/ false);
89                test(ext, /*hasDirectoryExists*/ true);
90            }
91
92            function test(ext: string, hasDirectoryExists: boolean) {
93                const containingFile = { name: containingFileName };
94                const moduleFile = { name: moduleFileNameNoExt + ext };
95                const resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile));
96                checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name));
97
98                const failedLookupLocations: string[] = [];
99                const dir = getDirectoryPath(containingFileName);
100                for (const e of autoExtensions) {
101                    if (e === ext) {
102                        break;
103                    }
104                    else {
105                        failedLookupLocations.push(normalizePath(getRootLength(moduleName) === 0 ? combinePaths(dir, moduleName) : moduleName) + e);
106                    }
107                }
108
109                assert.deepEqual(resolution.failedLookupLocations, failedLookupLocations);
110
111            }
112        }
113
114        it("module name that starts with './' resolved as relative file name", () => {
115            testLoadAsFile("/foo/bar/baz.ts", "/foo/bar/foo", "./foo");
116        });
117
118        it("module name that starts with '../' resolved as relative file name", () => {
119            testLoadAsFile("/foo/bar/baz.ts", "/foo/foo", "../foo");
120        });
121
122        it("module name that starts with '/' script extension resolved as relative file name", () => {
123            testLoadAsFile("/foo/bar/baz.ts", "/foo", "/foo");
124        });
125
126        it("module name that starts with 'c:/' script extension resolved as relative file name", () => {
127            testLoadAsFile("c:/foo/bar/baz.ts", "c:/foo", "c:/foo");
128        });
129
130        function testLoadingFromPackageJson(containingFileName: string, packageJsonFileName: string, fieldRef: string, moduleFileName: string, moduleName: string): void {
131            test(/*hasDirectoryExists*/ false);
132            test(/*hasDirectoryExists*/ true);
133
134            function test(hasDirectoryExists: boolean) {
135                const containingFile = { name: containingFileName };
136                const packageJson = { name: packageJsonFileName, content: JSON.stringify({ typings: fieldRef }) };
137                const moduleFile = { name: moduleFileName };
138                const resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile));
139                checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name));
140                // expect three failed lookup location - attempt to load module as file with all supported extensions
141                assert.equal(resolution.failedLookupLocations.length, supportedTSExtensions[0].length);
142                assert.deepEqual(resolution.affectingLocations, [packageJsonFileName]);
143            }
144        }
145
146        it("module name as directory - load from 'typings'", () => {
147            testLoadingFromPackageJson("/a/b/c/d.ts", "/a/b/c/bar/package.json", "c/d/e.d.ts", "/a/b/c/bar/c/d/e.d.ts", "./bar");
148            testLoadingFromPackageJson("/a/b/c/d.ts", "/a/bar/package.json", "e.d.ts", "/a/bar/e.d.ts", "../../bar");
149            testLoadingFromPackageJson("/a/b/c/d.ts", "/bar/package.json", "e.d.ts", "/bar/e.d.ts", "/bar");
150            testLoadingFromPackageJson("c:/a/b/c/d.ts", "c:/bar/package.json", "e.d.ts", "c:/bar/e.d.ts", "c:/bar");
151        });
152
153        function testTypingsIgnored(typings: any): void {
154            test(/*hasDirectoryExists*/ false);
155            test(/*hasDirectoryExists*/ true);
156
157            function test(hasDirectoryExists: boolean) {
158                const containingFile = { name: "/a/b.ts" };
159                const packageJson = { name: "/node_modules/b/package.json", content: JSON.stringify({ typings }) };
160                const moduleFile = { name: "/a/b.d.ts" };
161
162                const indexPath = "/node_modules/b/index.d.ts";
163                const indexFile = { name: indexPath };
164
165                const resolution = nodeModuleNameResolver("b", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile, indexFile));
166
167                checkResolvedModule(resolution.resolvedModule, createResolvedModule(indexPath, /*isExternalLibraryImport*/ true));
168            }
169        }
170
171        it("module name as directory - handle invalid 'typings'", () => {
172            testTypingsIgnored(["a", "b"]);
173            testTypingsIgnored({ a: "b" });
174            testTypingsIgnored(/*typings*/ true);
175            testTypingsIgnored(/*typings*/ null); // eslint-disable-line no-null/no-null
176            testTypingsIgnored(/*typings*/ undefined);
177        });
178        it("module name as directory - load index.d.ts", () => {
179            test(/*hasDirectoryExists*/ false);
180            test(/*hasDirectoryExists*/ true);
181
182            function test(hasDirectoryExists: boolean) {
183                const containingFile = { name: "/a/b/c.ts" };
184                const packageJson = { name: "/a/b/foo/package.json", content: JSON.stringify({ main: "/c/d" }) };
185                const indexFile = { name: "/a/b/foo/index.d.ts" };
186                const resolution = nodeModuleNameResolver("./foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, indexFile));
187                checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(indexFile.name), [
188                    "/a/b/foo.ts",
189                    "/a/b/foo.tsx",
190                    "/a/b/foo.d.ts",
191                    "/a/b/foo.ets",
192                    "/a/b/foo.d.ets",
193                    "/c/d",
194                    "/c/d.ts",
195                    "/c/d.tsx",
196                    "/c/d.d.ts",
197                    "/c/d.ets",
198                    "/c/d.d.ets",
199                    "/c/d/index.ts",
200                    "/c/d/index.tsx",
201                    "/c/d/index.d.ts",
202                    "/c/d/index.ets",
203                    "/c/d/index.d.ets",
204                    "/a/b/foo/index.ts",
205                    "/a/b/foo/index.tsx",
206                ]);
207            }
208        });
209    });
210
211    describe("unittests:: moduleResolution:: Node module resolution - non-relative paths", () => {
212        it("computes correct commonPrefix for moduleName cache", () => {
213            const resolutionCache = createModuleResolutionCache("/", (f) => f);
214            let cache = resolutionCache.getOrCreateCacheForModuleName("a", /*mode*/ undefined);
215            cache.set("/sub", {
216                resolvedModule: {
217                    originalPath: undefined,
218                    resolvedFileName: "/sub/node_modules/a/index.ts",
219                    isExternalLibraryImport: true,
220                    extension: Extension.Ts,
221                },
222                failedLookupLocations: [],
223                affectingLocations: [],
224                resolutionDiagnostics: [],
225            });
226            assert.isDefined(cache.get("/sub"));
227            assert.isUndefined(cache.get("/"));
228
229            cache = resolutionCache.getOrCreateCacheForModuleName("b", /*mode*/ undefined);
230            cache.set("/sub/dir/foo", {
231                resolvedModule: {
232                    originalPath: undefined,
233                    resolvedFileName: "/sub/directory/node_modules/b/index.ts",
234                    isExternalLibraryImport: true,
235                    extension: Extension.Ts,
236                },
237                failedLookupLocations: [],
238                affectingLocations: [],
239                resolutionDiagnostics: [],
240            });
241            assert.isDefined(cache.get("/sub/dir/foo"));
242            assert.isDefined(cache.get("/sub/dir"));
243            assert.isDefined(cache.get("/sub"));
244            assert.isUndefined(cache.get("/"));
245
246            cache = resolutionCache.getOrCreateCacheForModuleName("c", /*mode*/ undefined);
247            cache.set("/foo/bar", {
248                resolvedModule: {
249                    originalPath: undefined,
250                    resolvedFileName: "/bar/node_modules/c/index.ts",
251                    isExternalLibraryImport: true,
252                    extension: Extension.Ts,
253                },
254                failedLookupLocations: [],
255                affectingLocations: [],
256                resolutionDiagnostics: [],
257            });
258            assert.isDefined(cache.get("/foo/bar"));
259            assert.isDefined(cache.get("/foo"));
260            assert.isDefined(cache.get("/"));
261
262            cache = resolutionCache.getOrCreateCacheForModuleName("d", /*mode*/ undefined);
263            cache.set("/foo", {
264                resolvedModule: {
265                    originalPath: undefined,
266                    resolvedFileName: "/foo/index.ts",
267                    isExternalLibraryImport: true,
268                    extension: Extension.Ts,
269                },
270                failedLookupLocations: [],
271                affectingLocations: [],
272                resolutionDiagnostics: [],
273            });
274            assert.isDefined(cache.get("/foo"));
275            assert.isUndefined(cache.get("/"));
276
277            cache = resolutionCache.getOrCreateCacheForModuleName("e", /*mode*/ undefined);
278            cache.set("c:/foo", {
279                resolvedModule: {
280                    originalPath: undefined,
281                    resolvedFileName: "d:/bar/node_modules/e/index.ts",
282                    isExternalLibraryImport: true,
283                    extension: Extension.Ts,
284                },
285                failedLookupLocations: [],
286                affectingLocations: [],
287                resolutionDiagnostics: [],
288            });
289            assert.isDefined(cache.get("c:/foo"));
290            assert.isDefined(cache.get("c:/"));
291            assert.isUndefined(cache.get("d:/"));
292
293            cache = resolutionCache.getOrCreateCacheForModuleName("f", /*mode*/ undefined);
294            cache.set("/foo/bar/baz", {
295                resolvedModule: undefined,
296                failedLookupLocations: [],
297                affectingLocations: [],
298                resolutionDiagnostics: [],
299            });
300            assert.isDefined(cache.get("/foo/bar/baz"));
301            assert.isDefined(cache.get("/foo/bar"));
302            assert.isDefined(cache.get("/foo"));
303            assert.isDefined(cache.get("/"));
304        });
305
306        it("load module as file - ts files not loaded", () => {
307            test(/*hasDirectoryExists*/ false);
308            test(/*hasDirectoryExists*/ true);
309
310            function test(hasDirectoryExists: boolean) {
311                const containingFile = { name: "/a/b/c/d/e.ts" };
312                const moduleFile = { name: "/a/b/node_modules/foo.ts" };
313                const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile));
314                checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [
315                    "/a/b/c/d/node_modules/foo/package.json",
316                    "/a/b/c/d/node_modules/foo.ts",
317                    "/a/b/c/d/node_modules/foo.tsx",
318                    "/a/b/c/d/node_modules/foo.d.ts",
319                    "/a/b/c/d/node_modules/foo.ets",
320                    "/a/b/c/d/node_modules/foo.d.ets",
321
322                    "/a/b/c/d/node_modules/foo/index.ts",
323                    "/a/b/c/d/node_modules/foo/index.tsx",
324                    "/a/b/c/d/node_modules/foo/index.d.ts",
325                    "/a/b/c/d/node_modules/foo/index.ets",
326                    "/a/b/c/d/node_modules/foo/index.d.ets",
327
328                    "/a/b/c/d/node_modules/@types/foo/package.json",
329                    "/a/b/c/d/node_modules/@types/foo.d.ts",
330
331                    "/a/b/c/d/node_modules/@types/foo/index.d.ts",
332
333                    "/a/b/c/node_modules/foo/package.json",
334                    "/a/b/c/node_modules/foo.ts",
335                    "/a/b/c/node_modules/foo.tsx",
336                    "/a/b/c/node_modules/foo.d.ts",
337                    "/a/b/c/node_modules/foo.ets",
338                    "/a/b/c/node_modules/foo.d.ets",
339
340                    "/a/b/c/node_modules/foo/index.ts",
341                    "/a/b/c/node_modules/foo/index.tsx",
342                    "/a/b/c/node_modules/foo/index.d.ts",
343                    "/a/b/c/node_modules/foo/index.ets",
344                    "/a/b/c/node_modules/foo/index.d.ets",
345
346                    "/a/b/c/node_modules/@types/foo/package.json",
347                    "/a/b/c/node_modules/@types/foo.d.ts",
348
349                    "/a/b/c/node_modules/@types/foo/index.d.ts",
350                    "/a/b/node_modules/foo/package.json",
351                ]);
352            }
353        });
354
355        it("load module as file", () => {
356            test(/*hasDirectoryExists*/ false);
357            test(/*hasDirectoryExists*/ true);
358
359            function test(hasDirectoryExists: boolean) {
360                const containingFile = { name: "/a/b/c/d/e.ts" };
361                const moduleFile = { name: "/a/b/node_modules/foo.d.ts" };
362                const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile));
363                checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true));
364            }
365        });
366
367        it("load module as directory", () => {
368            test(/*hasDirectoryExists*/ false);
369            test(/*hasDirectoryExists*/ true);
370
371            function test(hasDirectoryExists: boolean) {
372                const containingFile: File = { name: "/a/node_modules/b/c/node_modules/d/e.ts" };
373                const moduleFile: File = { name: "/a/node_modules/foo/index.d.ts" };
374                const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile));
375                checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [
376                    "/a/node_modules/b/c/node_modules/d/node_modules/foo/package.json",
377                    "/a/node_modules/b/c/node_modules/d/node_modules/foo.ts",
378                    "/a/node_modules/b/c/node_modules/d/node_modules/foo.tsx",
379                    "/a/node_modules/b/c/node_modules/d/node_modules/foo.d.ts",
380                    "/a/node_modules/b/c/node_modules/d/node_modules/foo.ets",
381                    "/a/node_modules/b/c/node_modules/d/node_modules/foo.d.ets",
382
383                    "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.ts",
384                    "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.tsx",
385                    "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.d.ts",
386                    "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.ets",
387                    "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.d.ets",
388
389                    "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/package.json",
390                    "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo.d.ts",
391
392                    "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/index.d.ts",
393
394                    "/a/node_modules/b/c/node_modules/foo/package.json",
395                    "/a/node_modules/b/c/node_modules/foo.ts",
396                    "/a/node_modules/b/c/node_modules/foo.tsx",
397                    "/a/node_modules/b/c/node_modules/foo.d.ts",
398                    "/a/node_modules/b/c/node_modules/foo.ets",
399                    "/a/node_modules/b/c/node_modules/foo.d.ets",
400
401                    "/a/node_modules/b/c/node_modules/foo/index.ts",
402                    "/a/node_modules/b/c/node_modules/foo/index.tsx",
403                    "/a/node_modules/b/c/node_modules/foo/index.d.ts",
404                    "/a/node_modules/b/c/node_modules/foo/index.ets",
405                    "/a/node_modules/b/c/node_modules/foo/index.d.ets",
406
407                    "/a/node_modules/b/c/node_modules/@types/foo/package.json",
408                    "/a/node_modules/b/c/node_modules/@types/foo.d.ts",
409
410                    "/a/node_modules/b/c/node_modules/@types/foo/index.d.ts",
411
412                    "/a/node_modules/b/node_modules/foo/package.json",
413                    "/a/node_modules/b/node_modules/foo.ts",
414                    "/a/node_modules/b/node_modules/foo.tsx",
415                    "/a/node_modules/b/node_modules/foo.d.ts",
416                    "/a/node_modules/b/node_modules/foo.ets",
417                    "/a/node_modules/b/node_modules/foo.d.ets",
418
419                    "/a/node_modules/b/node_modules/foo/index.ts",
420                    "/a/node_modules/b/node_modules/foo/index.tsx",
421                    "/a/node_modules/b/node_modules/foo/index.d.ts",
422                    "/a/node_modules/b/node_modules/foo/index.ets",
423                    "/a/node_modules/b/node_modules/foo/index.d.ets",
424
425                    "/a/node_modules/b/node_modules/@types/foo/package.json",
426                    "/a/node_modules/b/node_modules/@types/foo.d.ts",
427
428                    "/a/node_modules/b/node_modules/@types/foo/index.d.ts",
429
430                    "/a/node_modules/foo/package.json",
431                    "/a/node_modules/foo.ts",
432                    "/a/node_modules/foo.tsx",
433                    "/a/node_modules/foo.d.ts",
434                    "/a/node_modules/foo.ets",
435                    "/a/node_modules/foo.d.ets",
436
437                    "/a/node_modules/foo/index.ts",
438                    "/a/node_modules/foo/index.tsx"
439                ]);
440            }
441        });
442
443        testPreserveSymlinks(/*preserveSymlinks*/ false);
444        testPreserveSymlinks(/*preserveSymlinks*/ true);
445        function testPreserveSymlinks(preserveSymlinks: boolean) {
446            it(`preserveSymlinks: ${preserveSymlinks}`, () => {
447                const realFileName = "/linked/index.d.ts";
448                const symlinkFileName = "/app/node_modules/linked/index.d.ts";
449                const host = createModuleResolutionHost(
450                    /*hasDirectoryExists*/ true,
451                    { name: realFileName, symlinks: [symlinkFileName] },
452                    { name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' },
453                );
454                const resolution = nodeModuleNameResolver("linked", "/app/app.ts", { preserveSymlinks }, host);
455                const resolvedFileName = preserveSymlinks ? symlinkFileName : realFileName;
456                checkResolvedModule(resolution.resolvedModule, createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/ true));
457            });
458        }
459
460        it("uses originalPath for caching", () => {
461            const host = createModuleResolutionHost(
462                /*hasDirectoryExists*/ true,
463                {
464                    name: "/modules/a.ts",
465                    symlinks: ["/sub/node_modules/a/index.ts"],
466                },
467                {
468                    name: "/sub/node_modules/a/package.json",
469                    content: '{"version": "0.0.0", "main": "./index"}'
470                }
471            );
472            const compilerOptions: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs };
473            const cache = createModuleResolutionCache("/", (f) => f);
474            let resolution = resolveModuleName("a", "/sub/dir/foo.ts", compilerOptions, host, cache);
475            checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true));
476
477            resolution = resolveModuleName("a", "/sub/foo.ts", compilerOptions, host, cache);
478            checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true));
479
480            resolution = resolveModuleName("a", "/foo.ts", compilerOptions, host, cache);
481            assert.isUndefined(resolution.resolvedModule, "lookup in parent directory doesn't hit the cache");
482        });
483
484        it("preserves originalPath on cache hit", () => {
485            const host = createModuleResolutionHost(
486                /*hasDirectoryExists*/ true,
487                { name: "/linked/index.d.ts", symlinks: ["/app/node_modules/linked/index.d.ts"] },
488                { name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' },
489            );
490            const cache = createModuleResolutionCache("/", (f) => f);
491            const compilerOptions: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs };
492            checkResolution(resolveModuleName("linked", "/app/src/app.ts", compilerOptions, host, cache));
493            checkResolution(resolveModuleName("linked", "/app/lib/main.ts", compilerOptions, host, cache));
494
495            function checkResolution(resolution: ResolvedModuleWithFailedLookupLocations) {
496                checkResolvedModule(resolution.resolvedModule, createResolvedModule("/linked/index.d.ts", /*isExternalLibraryImport*/ true));
497                assert.strictEqual(resolution.resolvedModule!.originalPath, "/app/node_modules/linked/index.d.ts");
498            }
499        });
500    });
501
502    describe("unittests:: moduleResolution:: Relative imports", () => {
503        function test(files: ESMap<string, string>, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) {
504            const options: CompilerOptions = { module: ModuleKind.CommonJS };
505            const host: CompilerHost = {
506                getSourceFile: (fileName: string, languageVersion: ScriptTarget) => {
507                    const path = normalizePath(combinePaths(currentDirectory, fileName));
508                    const file = files.get(path);
509                    return file ? createSourceFile(fileName, file, languageVersion) : undefined;
510                },
511                getDefaultLibFileName: () => "lib.d.ts",
512                writeFile: notImplemented,
513                getCurrentDirectory: () => currentDirectory,
514                getDirectories: () => [],
515                getCanonicalFileName: fileName => fileName.toLowerCase(),
516                getNewLine: () => "\r\n",
517                useCaseSensitiveFileNames: () => false,
518                fileExists: fileName => {
519                    const path = normalizePath(combinePaths(currentDirectory, fileName));
520                    return files.has(path);
521                },
522                readFile: notImplemented,
523            };
524
525            const program = createProgram(rootFiles, options, host);
526
527            assert.equal(program.getSourceFiles().length, expectedFilesCount);
528            const syntacticDiagnostics = program.getSyntacticDiagnostics();
529            assert.equal(syntacticDiagnostics.length, 0, `expect no syntactic diagnostics, got: ${JSON.stringify(Harness.Compiler.minimalDiagnosticsToString(syntacticDiagnostics))}`);
530            const semanticDiagnostics = program.getSemanticDiagnostics();
531            assert.equal(semanticDiagnostics.length, 0, `expect no semantic diagnostics, got: ${JSON.stringify(Harness.Compiler.minimalDiagnosticsToString(semanticDiagnostics))}`);
532
533            // try to get file using a relative name
534            for (const relativeFileName of relativeNamesToCheck) {
535                assert.isTrue(program.getSourceFile(relativeFileName) !== undefined, `expected to get file by relative name, got undefined`);
536            }
537        }
538
539        it("should find all modules", () => {
540            const files = new Map(getEntries({
541                "/a/b/c/first/shared.ts": `
542class A {}
543export = A`,
544                "/a/b/c/first/second/class_a.ts": `
545import Shared = require('../shared');
546import C = require('../../third/class_c');
547class B {}
548export = B;`,
549                "/a/b/c/third/class_c.ts": `
550import Shared = require('../first/shared');
551class C {}
552export = C;
553                `
554            }));
555            test(files, "/a/b/c/first/second", ["class_a.ts"], 3, ["../../../c/third/class_c.ts"]);
556        });
557
558        it("should find modules in node_modules", () => {
559            const files = new Map(getEntries({
560                "/parent/node_modules/mod/index.d.ts": "export var x",
561                "/parent/app/myapp.ts": `import {x} from "mod"`
562            }));
563            test(files, "/parent/app", ["myapp.ts"], 2, []);
564        });
565
566        it("should find file referenced via absolute and relative names", () => {
567            const files = new Map(getEntries({
568                "/a/b/c.ts": `/// <reference path="b.ts"/>`,
569                "/a/b/b.ts": "var x"
570            }));
571            test(files, "/a/b", ["c.ts", "/a/b/b.ts"], 2, []);
572        });
573    });
574
575    describe("unittests:: moduleResolution:: Files with different casing with forceConsistentCasingInFileNames", () => {
576        let library: SourceFile;
577        function test(
578            files: ESMap<string, string>,
579            options: CompilerOptions,
580            currentDirectory: string,
581            useCaseSensitiveFileNames: boolean,
582            rootFiles: string[],
583            expectedDiagnostics: (program: Program) => readonly Diagnostic[]
584        ): void {
585            const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
586            if (!useCaseSensitiveFileNames) {
587                const oldFiles = files;
588                files = new Map<string, string>();
589                oldFiles.forEach((file, fileName) => {
590                    files.set(getCanonicalFileName(fileName), file);
591                });
592            }
593
594            const host: CompilerHost = {
595                getSourceFile: (fileName: string, languageVersion: ScriptTarget) => {
596                    if (fileName === "lib.d.ts") {
597                        if (!library) {
598                            library = createSourceFile("lib.d.ts", "", ScriptTarget.ES5);
599                        }
600                        return library;
601                    }
602                    const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName)));
603                    const file = files.get(path);
604                    return file ? createSourceFile(fileName, file, languageVersion) : undefined;
605                },
606                getDefaultLibFileName: () => "lib.d.ts",
607                writeFile: notImplemented,
608                getCurrentDirectory: () => currentDirectory,
609                getDirectories: () => [],
610                getCanonicalFileName,
611                getNewLine: () => "\r\n",
612                useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
613                fileExists: fileName => {
614                    const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName)));
615                    return files.has(path);
616                },
617                readFile: notImplemented,
618            };
619            const program = createProgram(rootFiles, options, host);
620            const diagnostics = sortAndDeduplicateDiagnostics([...program.getSemanticDiagnostics(), ...program.getOptionsDiagnostics()]);
621            assert.deepEqual(diagnostics, sortAndDeduplicateDiagnostics(expectedDiagnostics(program)));
622        }
623
624        it("should succeed when the same file is referenced using absolute and relative names", () => {
625            const files = new Map(getEntries({
626                "/a/b/c.ts": `/// <reference path="d.ts"/>`,
627                "/a/b/d.ts": "var x"
628            }));
629            test(
630                files,
631                { module: ModuleKind.AMD },
632                "/a/b",
633                /*useCaseSensitiveFileNames*/ false,
634                ["c.ts", "/a/b/d.ts"],
635                () => emptyArray
636            );
637        });
638
639        it("should fail when two files used in program differ only in casing (tripleslash references)", () => {
640            const files = new Map(getEntries({
641                "/a/b/c.ts": `/// <reference path="D.ts"/>`,
642                "/a/b/d.ts": "var x"
643            }));
644            test(
645                files,
646                { module: ModuleKind.AMD, forceConsistentCasingInFileNames: true },
647                "/a/b",
648                /*useCaseSensitiveFileNames*/ false,
649                ["c.ts", "d.ts"],
650                program => [{
651                    ...tscWatch.getDiagnosticOfFileFromProgram(
652                        program,
653                        "c.ts",
654                        `/// <reference path="D.ts"/>`.indexOf(`D.ts`),
655                        "D.ts".length,
656                        tscWatch.getDiagnosticMessageChain(
657                            Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
658                            ["D.ts", "d.ts"],
659                            [
660                                tscWatch.getDiagnosticMessageChain(
661                                    Diagnostics.The_file_is_in_the_program_because_Colon,
662                                    emptyArray,
663                                    [
664                                        tscWatch.getDiagnosticMessageChain(Diagnostics.Referenced_via_0_from_file_1, ["D.ts", "c.ts"]),
665                                        tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation)
666                                    ]
667                                )
668                            ],
669                        )
670                    ),
671                    relatedInformation: undefined,
672                }]
673            );
674        });
675
676        it("should fail when two files used in program differ only in casing (imports)", () => {
677            const files = new Map(getEntries({
678                "/a/b/c.ts": `import {x} from "D"`,
679                "/a/b/d.ts": "export var x"
680            }));
681            test(
682                files,
683                { module: ModuleKind.AMD, forceConsistentCasingInFileNames: true },
684                "/a/b",
685                /*useCaseSensitiveFileNames*/ false,
686                ["c.ts", "d.ts"],
687                program => [{
688                    ...tscWatch.getDiagnosticOfFileFromProgram(
689                        program,
690                        "c.ts",
691                        `import {x} from "D"`.indexOf(`"D"`),
692                        `"D"`.length,
693                        tscWatch.getDiagnosticMessageChain(
694                            Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
695                            ["/a/b/D.ts", "d.ts"],
696                            [
697                                tscWatch.getDiagnosticMessageChain(
698                                    Diagnostics.The_file_is_in_the_program_because_Colon,
699                                    emptyArray,
700                                    [
701                                        tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]),
702                                        tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation)
703                                    ]
704                                )
705                            ],
706                        )
707                    ),
708                    relatedInformation: undefined,
709                }]
710            );
711        });
712
713        it("should fail when two files used in program differ only in casing (imports, relative module names)", () => {
714            const files = new Map(getEntries({
715                "moduleA.ts": `import {x} from "./ModuleB"`,
716                "moduleB.ts": "export var x"
717            }));
718            test(
719                files,
720                { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true },
721                "",
722                /*useCaseSensitiveFileNames*/ false,
723                ["moduleA.ts", "moduleB.ts"],
724                program => [{
725                    ...tscWatch.getDiagnosticOfFileFromProgram(
726                        program,
727                        "moduleA.ts",
728                        `import {x} from "./ModuleB"`.indexOf(`"./ModuleB"`),
729                        `"./ModuleB"`.length,
730                        tscWatch.getDiagnosticMessageChain(
731                            Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
732                            ["ModuleB.ts", "moduleB.ts"],
733                            [
734                                tscWatch.getDiagnosticMessageChain(
735                                    Diagnostics.The_file_is_in_the_program_because_Colon,
736                                    emptyArray,
737                                    [
738                                        tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleB"`, "moduleA.ts"]),
739                                        tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation)
740                                    ]
741                                )
742                            ],
743                        )
744                    ),
745                    relatedInformation: undefined
746                }]
747            );
748        });
749
750        it("should fail when two files exist on disk that differs only in casing", () => {
751            const files = new Map(getEntries({
752                "/a/b/c.ts": `import {x} from "D"`,
753                "/a/b/D.ts": "export var x",
754                "/a/b/d.ts": "export var y"
755            }));
756            test(
757                files,
758                { module: ModuleKind.AMD },
759                "/a/b",
760                /*useCaseSensitiveFileNames*/ true,
761                ["c.ts", "d.ts"],
762                program => [{
763                    ...tscWatch.getDiagnosticOfFileFromProgram(
764                        program,
765                        "c.ts",
766                        `import {x} from "D"`.indexOf(`"D"`),
767                        `"D"`.length,
768                        tscWatch.getDiagnosticMessageChain(
769                            Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
770                            ["/a/b/D.ts", "d.ts"],
771                            [
772                                tscWatch.getDiagnosticMessageChain(
773                                    Diagnostics.The_file_is_in_the_program_because_Colon,
774                                    emptyArray,
775                                    [
776                                        tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]),
777                                        tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation)
778                                    ]
779                                )
780                            ],
781                        )
782                    ),
783                    relatedInformation: undefined
784                }]
785            );
786        });
787
788        it("should fail when module name in 'require' calls has inconsistent casing", () => {
789            const files = new Map(getEntries({
790                "moduleA.ts": `import a = require("./ModuleC")`,
791                "moduleB.ts": `import a = require("./moduleC")`,
792                "moduleC.ts": "export var x"
793            }));
794            test(
795                files,
796                { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true },
797                "",
798                /*useCaseSensitiveFileNames*/ false,
799                ["moduleA.ts", "moduleB.ts", "moduleC.ts"],
800                program => {
801                    const importInA = {
802                        ...tscWatch.getDiagnosticOfFileFromProgram(
803                            program,
804                            "moduleA.ts",
805                            `import a = require("./ModuleC")`.indexOf(`"./ModuleC"`),
806                            `"./ModuleC"`.length,
807                            Diagnostics.File_is_included_via_import_here,
808                        ),
809                        reportsUnnecessary: undefined,
810                        reportsDeprecated: undefined
811                    };
812                    const importInB = {
813                        ...tscWatch.getDiagnosticOfFileFromProgram(
814                            program,
815                            "moduleB.ts",
816                            `import a = require("./moduleC")`.indexOf(`"./moduleC"`),
817                            `"./moduleC"`.length,
818                            Diagnostics.File_is_included_via_import_here,
819                        ),
820                        reportsUnnecessary: undefined,
821                        reportsDeprecated: undefined
822                    };
823                    const importHereInA = tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "moduleA.ts"]);
824                    const importHereInB = tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "moduleB.ts"]);
825                    const details = [tscWatch.getDiagnosticMessageChain(
826                        Diagnostics.The_file_is_in_the_program_because_Colon,
827                        emptyArray,
828                        [importHereInA, importHereInB, tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation)]
829                    )];
830                    return [
831                        {
832                            ...tscWatch.getDiagnosticOfFileFrom(
833                                importInA.file,
834                                importInA.start,
835                                importInA.length,
836                                tscWatch.getDiagnosticMessageChain(
837                                    Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
838                                    ["ModuleC.ts", "moduleC.ts" ],
839                                    details,
840                                )
841                            ),
842                            relatedInformation: [importInB]
843                        },
844                        {
845                            ...tscWatch.getDiagnosticOfFileFrom(
846                                importInB.file,
847                                importInB.start,
848                                importInB.length,
849                                tscWatch.getDiagnosticMessageChain(
850                                    Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing,
851                                    ["moduleC.ts", "ModuleC.ts"],
852                                    details,
853                                )
854                            ),
855                            relatedInformation: [importInA]
856                        }
857                    ];
858                }
859            );
860        });
861
862        it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => {
863            const files = new Map(getEntries({
864                "/a/B/c/moduleA.ts": `import a = require("./ModuleC")`,
865                "/a/B/c/moduleB.ts": `import a = require("./moduleC")`,
866                "/a/B/c/moduleC.ts": "export var x",
867                "/a/B/c/moduleD.ts": `
868import a = require("./moduleA");
869import b = require("./moduleB");
870                `
871            }));
872            test(
873                files,
874                { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true },
875                "/a/B/c",
876                /*useCaseSensitiveFileNames*/ false,
877                ["moduleD.ts"],
878                program => [{
879                    ...tscWatch.getDiagnosticOfFileFromProgram(
880                        program,
881                        "moduleB.ts",
882                        `import a = require("./moduleC")`.indexOf(`"./moduleC"`),
883                        `"./moduleC"`.length,
884                        tscWatch.getDiagnosticMessageChain(
885                            Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing,
886                            ["/a/B/c/moduleC.ts", "/a/B/c/ModuleC.ts"],
887                            [
888                                tscWatch.getDiagnosticMessageChain(
889                                    Diagnostics.The_file_is_in_the_program_because_Colon,
890                                    emptyArray,
891                                    [
892                                        tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "/a/B/c/moduleA.ts"]),
893                                        tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "/a/B/c/moduleB.ts"])
894                                    ]
895                                )
896                            ],
897                        )
898                    ),
899                    relatedInformation: [
900                        {
901                            ...tscWatch.getDiagnosticOfFileFromProgram(
902                                program,
903                                "moduleA.ts",
904                                `import a = require("./ModuleC")`.indexOf(`"./ModuleC"`),
905                                `"./ModuleC"`.length,
906                                Diagnostics.File_is_included_via_import_here,
907                            ),
908                            reportsUnnecessary: undefined,
909                            reportsDeprecated: undefined
910                        }
911                    ]
912                }]
913            );
914        });
915        it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => {
916            const files = new Map(getEntries({
917                "/a/B/c/moduleA.ts": `import a = require("./moduleC")`,
918                "/a/B/c/moduleB.ts": `import a = require("./moduleC")`,
919                "/a/B/c/moduleC.ts": "export var x",
920                "/a/B/c/moduleD.ts": `
921import a = require("./moduleA");
922import b = require("./moduleB");
923                `
924            }));
925            test(
926                files,
927                { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true },
928                "/a/B/c",
929                /*useCaseSensitiveFileNames*/ false,
930                ["moduleD.ts"],
931                () => emptyArray
932            );
933        });
934
935        it("should succeed when the two files in program differ only in drive letter in their names", () => {
936            const files = new Map(getEntries({
937                "d:/someFolder/moduleA.ts": `import a = require("D:/someFolder/moduleC")`,
938                "d:/someFolder/moduleB.ts": `import a = require("./moduleC")`,
939                "D:/someFolder/moduleC.ts": "export const x = 10",
940            }));
941            test(
942                files,
943                { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true },
944                "d:/someFolder",
945                /*useCaseSensitiveFileNames*/ false,
946                ["d:/someFolder/moduleA.ts", "d:/someFolder/moduleB.ts"],
947                () => emptyArray
948            );
949        });
950    });
951
952    describe("unittests:: moduleResolution:: baseUrl augmented module resolution", () => {
953
954        it("module resolution without path mappings/rootDirs", () => {
955            test(/*hasDirectoryExists*/ false);
956            test(/*hasDirectoryExists*/ true);
957
958            function test(hasDirectoryExists: boolean) {
959                const file1: File = { name: "/root/folder1/file1.ts" };
960                const file2: File = { name: "/root/folder2/file2.ts" };
961                const file3: File = { name: "/root/folder2/file3.ts" };
962                const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3);
963                for (const moduleResolution of [ ModuleResolutionKind.NodeJs, ModuleResolutionKind.Classic ]) {
964                    const options: CompilerOptions = { moduleResolution, baseUrl: "/root" };
965                    {
966                        const result = resolveModuleName("folder2/file2", file1.name, options, host);
967                        checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file2.name), []);
968                    }
969                    {
970                        const result = resolveModuleName("./file3", file2.name, options, host);
971                        checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file3.name), []);
972                    }
973                    {
974                        const result = resolveModuleName("/root/folder1/file1", file2.name, options, host);
975                        checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file1.name), []);
976                    }
977                }
978            }
979            // add failure tests
980        });
981
982        it("node + baseUrl", () => {
983            test(/*hasDirectoryExists*/ false);
984            test(/*hasDirectoryExists*/ true);
985
986            function test(hasDirectoryExists: boolean) {
987                const main: File = { name: "/root/a/b/main.ts" };
988                const m1: File = { name: "/root/m1.ts" }; // load file as module
989                const m2: File = { name: "/root/m2/index.d.ts" }; // load folder as module
990                const m3: File = { name: "/root/m3/package.json", content: JSON.stringify({ typings: "dist/typings.d.ts" }) };
991                const m3Typings: File = { name: "/root/m3/dist/typings.d.ts" };
992                const m4: File = { name: "/root/node_modules/m4.ts" }; // fallback to node
993
994                const options: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs, baseUrl: "/root" };
995                const host = createModuleResolutionHost(hasDirectoryExists, main, m1, m2, m3, m3Typings, m4);
996
997                check("m1", main, m1);
998                check("m2", main, m2);
999                check("m3", main, m3Typings);
1000                check("m4", main, m4, /*isExternalLibraryImport*/ true);
1001
1002                function check(name: string, caller: File, expected: File, isExternalLibraryImport = false) {
1003                    const result = resolveModuleName(name, caller.name, options, host);
1004                    checkResolvedModule(result.resolvedModule, createResolvedModule(expected.name, isExternalLibraryImport));
1005                }
1006            }
1007        });
1008
1009        it("classic + baseUrl", () => {
1010            test(/*hasDirectoryExists*/ false);
1011            test(/*hasDirectoryExists*/ true);
1012
1013            function test(hasDirectoryExists: boolean) {
1014                const main: File = { name: "/root/a/b/main.ts" };
1015                const m1: File = { name: "/root/x/m1.ts" }; // load from base url
1016                const m2: File = { name: "/m2.ts" }; // fallback to classic
1017
1018                const options: CompilerOptions = { moduleResolution: ModuleResolutionKind.Classic, baseUrl: "/root/x", jsx: JsxEmit.React };
1019                const host = createModuleResolutionHost(hasDirectoryExists, main, m1, m2);
1020
1021                check("m1", main, m1);
1022                check("m2", main, m2);
1023
1024                function check(name: string, caller: File, expected: File) {
1025                    const result = resolveModuleName(name, caller.name, options, host);
1026                    checkResolvedModule(result.resolvedModule, createResolvedModule(expected.name));
1027                }
1028            }
1029        });
1030
1031        it("node + baseUrl + path mappings", () => {
1032            test(/*hasDirectoryExists*/ false);
1033            test(/*hasDirectoryExists*/ true);
1034
1035            function test(hasDirectoryExists: boolean) {
1036                const main: File = { name: "/root/folder1/main.ts" };
1037
1038                const file1: File = { name: "/root/folder1/file1.ts" };
1039                const file2: File = { name: "/root/generated/folder1/file2.ts" }; // load remapped file as module
1040                const file3: File = { name: "/root/generated/folder2/file3/index.d.ts" }; // load folder a module
1041                const file4Typings: File = { name: "/root/generated/folder2/file4/package.json", content: JSON.stringify({ typings: "dist/types.d.ts" }) };
1042                const file4: File = { name: "/root/generated/folder2/file4/dist/types.d.ts" }; // load file pointed by typings
1043                const file5: File = { name: "/root/someanotherfolder/file5/index.d.ts" }; // load remapped module from folder
1044                const file6: File = { name: "/root/node_modules/file6.ts" }; // fallback to node
1045                const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4, file4Typings, file5, file6);
1046
1047                const options: CompilerOptions = {
1048                    moduleResolution: ModuleResolutionKind.NodeJs,
1049                    baseUrl: "/root",
1050                    jsx: JsxEmit.React,
1051                    paths: {
1052                        "*": [
1053                            "*",
1054                            "generated/*"
1055                        ],
1056                        "somefolder/*": [
1057                            "someanotherfolder/*"
1058                        ],
1059                        "/rooted/*": [
1060                            "generated/*"
1061                        ]
1062                    }
1063                };
1064                check("folder1/file1", file1, []);
1065                check("folder1/file2", file2, [
1066                    // first try the '*'
1067                    "/root/folder1/file2.ts",
1068                    "/root/folder1/file2.tsx",
1069                    "/root/folder1/file2.d.ts",
1070                    "/root/folder1/file2.ets",
1071                    "/root/folder1/file2.d.ets",
1072                    "/root/folder1/file2/package.json",
1073
1074                    "/root/folder1/file2/index.ts",
1075                    "/root/folder1/file2/index.tsx",
1076                    "/root/folder1/file2/index.d.ts",
1077                    "/root/folder1/file2/index.ets",
1078                    "/root/folder1/file2/index.d.ets",
1079                    // then first attempt on 'generated/*' was successful
1080                ]);
1081                check("/rooted/folder1/file2", file2, []);
1082                check("folder2/file3", file3, [
1083                    // first try '*'
1084                    "/root/folder2/file3.ts",
1085                    "/root/folder2/file3.tsx",
1086                    "/root/folder2/file3.d.ts",
1087                    "/root/folder2/file3.ets",
1088                    "/root/folder2/file3.d.ets",
1089                    "/root/folder2/file3/package.json",
1090
1091                    "/root/folder2/file3/index.ts",
1092                    "/root/folder2/file3/index.tsx",
1093                    "/root/folder2/file3/index.d.ts",
1094                    "/root/folder2/file3/index.ets",
1095                    "/root/folder2/file3/index.d.ets",
1096
1097                    // then use remapped location
1098                    "/root/generated/folder2/file3.ts",
1099                    "/root/generated/folder2/file3.tsx",
1100                    "/root/generated/folder2/file3.d.ts",
1101                    "/root/generated/folder2/file3.ets",
1102                    "/root/generated/folder2/file3.d.ets",
1103                    "/root/generated/folder2/file3/package.json",
1104
1105                    "/root/generated/folder2/file3/index.ts",
1106                    "/root/generated/folder2/file3/index.tsx",
1107                    // success on index.d.ts
1108                ]);
1109                check("folder2/file4", file4, [
1110                    // first try '*'
1111                    "/root/folder2/file4.ts",
1112                    "/root/folder2/file4.tsx",
1113                    "/root/folder2/file4.d.ts",
1114                    "/root/folder2/file4.ets",
1115                    "/root/folder2/file4.d.ets",
1116                    "/root/folder2/file4/package.json",
1117
1118                    "/root/folder2/file4/index.ts",
1119                    "/root/folder2/file4/index.tsx",
1120                    "/root/folder2/file4/index.d.ts",
1121                    "/root/folder2/file4/index.ets",
1122                    "/root/folder2/file4/index.d.ets",
1123
1124                    // try to load from file from remapped location
1125                    "/root/generated/folder2/file4.ts",
1126                    "/root/generated/folder2/file4.tsx",
1127                    "/root/generated/folder2/file4.d.ts",
1128                    "/root/generated/folder2/file4.ets",
1129                    "/root/generated/folder2/file4.d.ets"
1130                    // success on loading as from folder
1131                ]);
1132                check("somefolder/file5", file5, [
1133                    // load from remapped location
1134                    // first load from fle
1135                    "/root/someanotherfolder/file5.ts",
1136                    "/root/someanotherfolder/file5.tsx",
1137                    "/root/someanotherfolder/file5.d.ts",
1138                    "/root/someanotherfolder/file5.ets",
1139                    "/root/someanotherfolder/file5.d.ets",
1140
1141                    // load from folder
1142                    "/root/someanotherfolder/file5/package.json",
1143                    "/root/someanotherfolder/file5/index.ts",
1144                    "/root/someanotherfolder/file5/index.tsx",
1145                    // success on index.d.ts
1146                ]);
1147                check("file6", file6, [
1148                    // first try *
1149                    // load from file
1150                    "/root/file6.ts",
1151                    "/root/file6.tsx",
1152                    "/root/file6.d.ts",
1153                    "/root/file6.ets",
1154                    "/root/file6.d.ets",
1155
1156                    // load from folder
1157                    "/root/file6/package.json",
1158                    "/root/file6/index.ts",
1159                    "/root/file6/index.tsx",
1160                    "/root/file6/index.d.ts",
1161                    "/root/file6/index.ets",
1162                    "/root/file6/index.d.ets",
1163
1164                    // then try 'generated/*'
1165                    // load from file
1166                    "/root/generated/file6.ts",
1167                    "/root/generated/file6.tsx",
1168                    "/root/generated/file6.d.ts",
1169                    "/root/generated/file6.ets",
1170                    "/root/generated/file6.d.ets",
1171
1172                    // load from folder
1173                    "/root/generated/file6/package.json",
1174                    "/root/generated/file6/index.ts",
1175                    "/root/generated/file6/index.tsx",
1176                    "/root/generated/file6/index.d.ts",
1177                    "/root/generated/file6/index.ets",
1178                    "/root/generated/file6/index.d.ets",
1179
1180                    // fallback to standard node behavior
1181                    "/root/folder1/node_modules/file6/package.json",
1182
1183                    // load from file
1184                    "/root/folder1/node_modules/file6.ts",
1185                    "/root/folder1/node_modules/file6.tsx",
1186                    "/root/folder1/node_modules/file6.d.ts",
1187                    "/root/folder1/node_modules/file6.ets",
1188                    "/root/folder1/node_modules/file6.d.ets",
1189
1190                    // load from folder
1191                    "/root/folder1/node_modules/file6/index.ts",
1192                    "/root/folder1/node_modules/file6/index.tsx",
1193                    "/root/folder1/node_modules/file6/index.d.ts",
1194                    "/root/folder1/node_modules/file6/index.ets",
1195                    "/root/folder1/node_modules/file6/index.d.ets",
1196
1197                    "/root/folder1/node_modules/@types/file6/package.json",
1198                    "/root/folder1/node_modules/@types/file6.d.ts",
1199                    "/root/folder1/node_modules/@types/file6/index.d.ts",
1200
1201                    "/root/node_modules/file6/package.json",
1202                    // success on /root/node_modules/file6.ts
1203                ], /*isExternalLibraryImport*/ true);
1204
1205                function check(name: string, expected: File, expectedFailedLookups: string[], isExternalLibraryImport = false) {
1206                    const result = resolveModuleName(name, main.name, options, host);
1207                    checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name, isExternalLibraryImport), expectedFailedLookups);
1208                }
1209            }
1210        });
1211
1212        it ("classic + baseUrl + path mappings", () => {
1213            // classic mode does not use directoryExists
1214            test(/*hasDirectoryExists*/ false);
1215
1216            function test(hasDirectoryExists: boolean) {
1217                const main: File = { name: "/root/folder1/main.ts" };
1218
1219                const file1: File = { name: "/root/folder1/file1.ts" };
1220                const file2: File = { name: "/root/generated/folder1/file2.ts" };
1221                const file3: File = { name: "/folder1/file3.ts" }; // fallback to classic
1222                const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3);
1223
1224                const options: CompilerOptions = {
1225                    moduleResolution: ModuleResolutionKind.Classic,
1226                    baseUrl: "/root",
1227                    jsx: JsxEmit.React,
1228                    paths: {
1229                        "*": [
1230                            "*",
1231                            "generated/*"
1232                        ],
1233                        "somefolder/*": [
1234                            "someanotherfolder/*"
1235                        ],
1236                        "/rooted/*": [
1237                            "generated/*"
1238                        ]
1239                    }
1240                };
1241                check("folder1/file1", file1, []);
1242                check("folder1/file2", file2, [
1243                    // first try '*'
1244                    "/root/folder1/file2.ts",
1245                    "/root/folder1/file2.tsx",
1246                    "/root/folder1/file2.d.ts",
1247                    "/root/folder1/file2.ets",
1248                    "/root/folder1/file2.d.ets",
1249                    // success when using 'generated/*'
1250                ]);
1251                check("/rooted/folder1/file2", file2, []);
1252                check("folder1/file3", file3, [
1253                    // first try '*'
1254                    "/root/folder1/file3.ts",
1255                    "/root/folder1/file3.tsx",
1256                    "/root/folder1/file3.d.ts",
1257                    "/root/folder1/file3.ets",
1258                    "/root/folder1/file3.d.ets",
1259                    // then try 'generated/*'
1260                    "/root/generated/folder1/file3.ts",
1261                    "/root/generated/folder1/file3.tsx",
1262                    "/root/generated/folder1/file3.d.ts",
1263                    "/root/generated/folder1/file3.ets",
1264                    "/root/generated/folder1/file3.d.ets",
1265                    // fallback to classic
1266                    "/root/folder1/folder1/file3.ts",
1267                    "/root/folder1/folder1/file3.tsx",
1268                    "/root/folder1/folder1/file3.d.ts",
1269                    "/root/folder1/folder1/file3.ets",
1270                    "/root/folder1/folder1/file3.d.ets",
1271                    "/root/folder1/file3.ts",
1272                    "/root/folder1/file3.tsx",
1273                    "/root/folder1/file3.d.ts",
1274                    "/root/folder1/file3.ets",
1275                    "/root/folder1/file3.d.ets"
1276                ]);
1277
1278                function check(name: string, expected: File, expectedFailedLookups: string[]) {
1279                    const result = resolveModuleName(name, main.name, options, host);
1280                    checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups);
1281                }
1282            }
1283        });
1284
1285        it ("node + rootDirs", () => {
1286            test(/*hasDirectoryExists*/ false);
1287            test(/*hasDirectoryExists*/ true);
1288
1289            function test(hasDirectoryExists: boolean) {
1290                const file1: File = { name: "/root/folder1/file1.ts" };
1291                const file1_1: File = { name: "/root/folder1/file1_1/index.d.ts" }; // eslint-disable-line @typescript-eslint/naming-convention
1292                const file2: File = { name: "/root/generated/folder1/file2.ts" };
1293                const file3: File = { name: "/root/generated/folder2/file3.ts" };
1294                const host = createModuleResolutionHost(hasDirectoryExists, file1, file1_1, file2, file3);
1295                const options: CompilerOptions = {
1296                    moduleResolution: ModuleResolutionKind.NodeJs,
1297                    rootDirs: [
1298                        "/root",
1299                        "/root/generated/"
1300                    ]
1301                };
1302                check("./file2", file1, file2, [
1303                    // first try current location
1304                    // load from file
1305                    "/root/folder1/file2.ts",
1306                    "/root/folder1/file2.tsx",
1307                    "/root/folder1/file2.d.ts",
1308                    "/root/folder1/file2.ets",
1309                    "/root/folder1/file2.d.ets",
1310                    // load from folder
1311                    "/root/folder1/file2/package.json",
1312                    "/root/folder1/file2/index.ts",
1313                    "/root/folder1/file2/index.tsx",
1314                    "/root/folder1/file2/index.d.ts",
1315                    "/root/folder1/file2/index.ets",
1316                    "/root/folder1/file2/index.d.ets",
1317                    // success after using alternative rootDir entry
1318                ]);
1319                check("../folder1/file1", file3, file1, [
1320                    // first try current location
1321                    // load from file
1322                    "/root/generated/folder1/file1.ts",
1323                    "/root/generated/folder1/file1.tsx",
1324                    "/root/generated/folder1/file1.d.ts",
1325                    "/root/generated/folder1/file1.ets",
1326                    "/root/generated/folder1/file1.d.ets",
1327                    // load from module
1328                    "/root/generated/folder1/file1/package.json",
1329                    "/root/generated/folder1/file1/index.ts",
1330                    "/root/generated/folder1/file1/index.tsx",
1331                    "/root/generated/folder1/file1/index.d.ts",
1332                    "/root/generated/folder1/file1/index.ets",
1333                    "/root/generated/folder1/file1/index.d.ets"
1334                    // success after using alternative rootDir entry
1335                ]);
1336                check("../folder1/file1_1", file3, file1_1, [
1337                    // first try current location
1338                    // load from file
1339                    "/root/generated/folder1/file1_1.ts",
1340                    "/root/generated/folder1/file1_1.tsx",
1341                    "/root/generated/folder1/file1_1.d.ts",
1342                    "/root/generated/folder1/file1_1.ets",
1343                    "/root/generated/folder1/file1_1.d.ets",
1344                    // load from folder
1345                    "/root/generated/folder1/file1_1/package.json",
1346                    "/root/generated/folder1/file1_1/index.ts",
1347                    "/root/generated/folder1/file1_1/index.tsx",
1348                    "/root/generated/folder1/file1_1/index.d.ts",
1349                    "/root/generated/folder1/file1_1/index.ets",
1350                    "/root/generated/folder1/file1_1/index.d.ets",
1351                    // try alternative rootDir entry
1352                    // load from file
1353                    "/root/folder1/file1_1.ts",
1354                    "/root/folder1/file1_1.tsx",
1355                    "/root/folder1/file1_1.d.ts",
1356                    "/root/folder1/file1_1.ets",
1357                    "/root/folder1/file1_1.d.ets",
1358                    // load from directory
1359                    "/root/folder1/file1_1/package.json",
1360                    "/root/folder1/file1_1/index.ts",
1361                    "/root/folder1/file1_1/index.tsx",
1362                    // success on loading '/root/folder1/file1_1/index.d.ts'
1363                ]);
1364
1365                function check(name: string, container: File, expected: File, expectedFailedLookups: string[]) {
1366                    const result = resolveModuleName(name, container.name, options, host);
1367                    checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups);
1368                }
1369            }
1370        });
1371
1372        it ("classic + rootDirs", () => {
1373            test(/*hasDirectoryExists*/ false);
1374
1375            function test(hasDirectoryExists: boolean) {
1376                const file1: File = { name: "/root/folder1/file1.ts" };
1377                const file2: File = { name: "/root/generated/folder1/file2.ts" };
1378                const file3: File = { name: "/root/generated/folder2/file3.ts" };
1379                const file4: File = { name: "/folder1/file1_1.ts" };
1380                const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4);
1381                const options: CompilerOptions = {
1382                    moduleResolution: ModuleResolutionKind.Classic,
1383                    jsx: JsxEmit.React,
1384                    rootDirs: [
1385                        "/root",
1386                        "/root/generated/"
1387                    ]
1388                };
1389                check("./file2", file1, file2, [
1390                    // first load from current location
1391                    "/root/folder1/file2.ts",
1392                    "/root/folder1/file2.tsx",
1393                    "/root/folder1/file2.d.ts",
1394                    "/root/folder1/file2.ets",
1395                    "/root/folder1/file2.d.ets"
1396                    // then try alternative rootDir entry
1397                ]);
1398                check("../folder1/file1", file3, file1, [
1399                    // first load from current location
1400                    "/root/generated/folder1/file1.ts",
1401                    "/root/generated/folder1/file1.tsx",
1402                    "/root/generated/folder1/file1.d.ts",
1403                    "/root/generated/folder1/file1.ets",
1404                    "/root/generated/folder1/file1.d.ets"
1405                    // then try alternative rootDir entry
1406                ]);
1407                check("folder1/file1_1", file3, file4, [
1408                    // current location
1409                    "/root/generated/folder2/folder1/file1_1.ts",
1410                    "/root/generated/folder2/folder1/file1_1.tsx",
1411                    "/root/generated/folder2/folder1/file1_1.d.ts",
1412                    "/root/generated/folder2/folder1/file1_1.ets",
1413                    "/root/generated/folder2/folder1/file1_1.d.ets",
1414                    // other entry in rootDirs
1415                    "/root/generated/folder1/file1_1.ts",
1416                    "/root/generated/folder1/file1_1.tsx",
1417                    "/root/generated/folder1/file1_1.d.ts",
1418                    "/root/generated/folder1/file1_1.ets",
1419                    "/root/generated/folder1/file1_1.d.ets",
1420                    // fallback
1421                    "/root/folder1/file1_1.ts",
1422                    "/root/folder1/file1_1.tsx",
1423                    "/root/folder1/file1_1.d.ts",
1424                    "/root/folder1/file1_1.ets",
1425                    "/root/folder1/file1_1.d.ets"
1426                    // found one
1427                ]);
1428
1429                function check(name: string, container: File, expected: File, expectedFailedLookups: string[]) {
1430                    const result = resolveModuleName(name, container.name, options, host);
1431                    checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups);
1432                }
1433            }
1434        });
1435
1436        it ("nested node module", () => {
1437            test(/*hasDirectoryExists*/ false);
1438            test(/*hasDirectoryExists*/ true);
1439
1440            function test(hasDirectoryExists: boolean) {
1441                const app: File = { name: "/root/src/app.ts" };
1442                const libsPackage: File = { name: "/root/src/libs/guid/package.json", content: JSON.stringify({ typings: "dist/guid.d.ts" }) };
1443                const libsTypings: File = { name: "/root/src/libs/guid/dist/guid.d.ts" };
1444                const host = createModuleResolutionHost(hasDirectoryExists, app, libsPackage, libsTypings);
1445                const options: CompilerOptions = {
1446                    moduleResolution: ModuleResolutionKind.NodeJs,
1447                    baseUrl: "/root",
1448                    paths: {
1449                        "libs/guid": [ "src/libs/guid" ]
1450                    }
1451                };
1452                const result = resolveModuleName("libs/guid", app.name, options, host);
1453                checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(libsTypings.name), [
1454                    // first try to load module as file
1455                    "/root/src/libs/guid.ts",
1456                    "/root/src/libs/guid.tsx",
1457                    "/root/src/libs/guid.d.ts",
1458                    "/root/src/libs/guid.ets",
1459                    "/root/src/libs/guid.d.ets",
1460                ]);
1461            }
1462        });
1463    });
1464
1465    describe("unittests:: moduleResolution:: ModuleResolutionHost.directoryExists", () => {
1466        it("No 'fileExists' calls if containing directory is missing", () => {
1467            const host: ModuleResolutionHost = {
1468                readFile: notImplemented,
1469                fileExists: notImplemented,
1470                directoryExists: _ => false
1471            };
1472
1473            const result = resolveModuleName("someName", "/a/b/c/d", { moduleResolution: ModuleResolutionKind.NodeJs }, host);
1474            assert(!result.resolvedModule);
1475        });
1476    });
1477
1478    describe("unittests:: moduleResolution:: Type reference directive resolution: ", () => {
1479        function testWorker(hasDirectoryExists: boolean, typesRoot: string | undefined, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) {
1480            const host = createModuleResolutionHost(hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles));
1481            const result = resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host);
1482            assert(result.resolvedTypeReferenceDirective!.resolvedFileName !== undefined, "expected type directive to be resolved");
1483            assert.equal(result.resolvedTypeReferenceDirective!.resolvedFileName, targetFile.name, "unexpected result of type reference resolution");
1484            assert.equal(result.resolvedTypeReferenceDirective!.primary, primary, "unexpected 'primary' value");
1485        }
1486
1487        function test(typesRoot: string, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) {
1488            testWorker(/*hasDirectoryExists*/ false, typesRoot, typeDirective, primary, initialFile, targetFile, ...otherFiles);
1489        }
1490
1491        it("Can be resolved from primary location", () => {
1492            {
1493                const f1 = { name: "/root/src/app.ts" };
1494                const f2 = { name: "/root/src/types/lib/index.d.ts" };
1495                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2);
1496            }
1497            {
1498                const f1 = { name: "/root/src/app.ts" };
1499                const f2 = { name: "/root/src/types/lib/typings/lib.d.ts" };
1500                const packageFile = { name: "/root/src/types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) };
1501                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2, packageFile);
1502            }
1503            {
1504                const f1 = { name: "/root/src/app.ts" };
1505                const f2 = { name: "/root/src/node_modules/lib/index.d.ts" };
1506                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2);
1507            }
1508            {
1509                const f1 = { name: "/root/src/app.ts" };
1510                const f2 = { name: "/root/src/node_modules/lib/typings/lib.d.ts" };
1511                const packageFile = { name: "/root/src/node_modules/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) };
1512                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile);
1513            }
1514            {
1515                const f1 = { name: "/root/src/app.ts" };
1516                const f2 = { name: "/root/src/node_modules/@types/lib/index.d.ts" };
1517                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2);
1518            }
1519            {
1520                const f1 = { name: "/root/src/app.ts" };
1521                const f2 = { name: "/root/src/node_modules/@types/lib/typings/lib.d.ts" };
1522                const packageFile = { name: "/root/src/node_modules/@types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) };
1523                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile);
1524            }
1525        });
1526        it("Can be resolved from secondary location", () => {
1527            {
1528                const f1 = { name: "/root/src/app.ts" };
1529                const f2 = { name: "/root/node_modules/lib.d.ts" };
1530                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2);
1531            }
1532            {
1533                const f1 = { name: "/root/src/app.ts" };
1534                const f2 = { name: "/root/node_modules/lib/index.d.ts" };
1535                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2);
1536            }
1537            {
1538                const f1 = { name: "/root/src/app.ts" };
1539                const f2 = { name: "/root/node_modules/lib/typings/lib.d.ts" };
1540                const packageFile = { name: "/root/node_modules/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) };
1541                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile);
1542            }
1543            {
1544                const f1 = { name: "/root/src/app.ts" };
1545                const f2 = { name: "/root/node_modules/@types/lib/index.d.ts" };
1546                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2);
1547            }
1548            {
1549                const f1 = { name: "/root/src/app.ts" };
1550                const f2 = { name: "/root/node_modules/@types/lib/typings/lib.d.ts" };
1551                const packageFile = { name: "/root/node_modules/@types/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) };
1552                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile);
1553            }
1554        });
1555        it("Primary resolution overrides secondary resolutions", () => {
1556            {
1557                const f1 = { name: "/root/src/a/b/c/app.ts" };
1558                const f2 = { name: "/root/src/types/lib/index.d.ts" };
1559                const f3 = { name: "/root/src/a/b/node_modules/lib.d.ts" };
1560                test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2, f3);
1561            }
1562        });
1563        it("Reused program keeps errors", () => {
1564            const f1 = { name: "/root/src/a/b/c/d/e/app.ts", content: `/// <reference types="lib"/>` };
1565            const f2 = { name: "/root/src/a/b/c/d/node_modules/lib/index.d.ts", content: `declare var x: number;` };
1566            const f3 = { name: "/root/src/a/b/c/d/f/g/app.ts", content: `/// <reference types="lib"/>` };
1567            const f4 = { name: "/root/src/a/b/c/d/f/node_modules/lib/index.d.ts", content: `declare var x: number;` };
1568            const files = [f1, f2, f3, f4];
1569
1570            const names = map(files, f => f.name);
1571            const sourceFiles = arrayToMap(map(files, f => createSourceFile(f.name, f.content, ScriptTarget.ES2015)), f => f.fileName);
1572            const compilerHost: CompilerHost = {
1573                fileExists: fileName => sourceFiles.has(fileName),
1574                getSourceFile: fileName => sourceFiles.get(fileName),
1575                getDefaultLibFileName: () => "lib.d.ts",
1576                writeFile: notImplemented,
1577                getCurrentDirectory: () => "/",
1578                getDirectories: () => [],
1579                getCanonicalFileName: f => f.toLowerCase(),
1580                getNewLine: () => "\r\n",
1581                useCaseSensitiveFileNames: () => false,
1582                readFile: fileName => {
1583                    const file = sourceFiles.get(fileName);
1584                    return file && file.text;
1585                },
1586            };
1587            const program1 = createProgram(names, {}, compilerHost);
1588            const diagnostics1 = program1.getOptionsDiagnostics();
1589            assert.equal(diagnostics1.length, 1, "expected one diagnostic");
1590
1591            const program2 = createProgram(names, {}, compilerHost, program1);
1592            assert.isTrue(program2.structureIsReused === StructureIsReused.Completely);
1593            const diagnostics2 = program2.getOptionsDiagnostics();
1594            assert.equal(diagnostics2.length, 1, "expected one diagnostic");
1595            assert.deepEqual(diagnostics1[0].messageText, diagnostics2[0].messageText, "expected one diagnostic");
1596        });
1597
1598        it("Modules in the same .d.ts file are preferred to external files", () => {
1599            const f = {
1600                name: "/a/b/c/c/app.d.ts",
1601                content: `
1602                declare module "fs" {
1603                    export interface Stat { id: number }
1604                }
1605                declare module "fs-client" {
1606                    import { Stat } from "fs";
1607                    export function foo(): Stat;
1608                }`
1609            };
1610            const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015);
1611            const compilerHost: CompilerHost = {
1612                fileExists: fileName => fileName === file.fileName,
1613                getSourceFile: fileName => fileName === file.fileName ? file : undefined,
1614                getDefaultLibFileName: () => "lib.d.ts",
1615                writeFile: notImplemented,
1616                getCurrentDirectory: () => "/",
1617                getDirectories: () => [],
1618                getCanonicalFileName: f => f.toLowerCase(),
1619                getNewLine: () => "\r\n",
1620                useCaseSensitiveFileNames: () => false,
1621                readFile: fileName => fileName === file.fileName ? file.text : undefined,
1622                resolveModuleNames: notImplemented,
1623            };
1624            createProgram([f.name], {}, compilerHost);
1625        });
1626
1627        it("Modules in .ts file are not checked in the same file", () => {
1628            const f = {
1629                name: "/a/b/c/c/app.ts",
1630                content: `
1631                declare module "fs" {
1632                    export interface Stat { id: number }
1633                }
1634                declare module "fs-client" {
1635                    import { Stat } from "fs";
1636                    export function foo(): Stat;
1637                }`
1638            };
1639            const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015);
1640            const compilerHost: CompilerHost = {
1641                fileExists: fileName => fileName === file.fileName,
1642                getSourceFile: fileName => fileName === file.fileName ? file : undefined,
1643                getDefaultLibFileName: () => "lib.d.ts",
1644                writeFile: notImplemented,
1645                getCurrentDirectory: () => "/",
1646                getDirectories: () => [],
1647                getCanonicalFileName: f => f.toLowerCase(),
1648                getNewLine: () => "\r\n",
1649                useCaseSensitiveFileNames: () => false,
1650                readFile: fileName => fileName === file.fileName ? file.text : undefined,
1651                resolveModuleNames(moduleNames: string[], _containingFile: string) {
1652                    assert.deepEqual(moduleNames, ["fs"]);
1653                    return [undefined!]; // TODO: GH#18217
1654                }
1655            };
1656            createProgram([f.name], {}, compilerHost);
1657        });
1658        describe("can be resolved when typeReferenceDirective is relative and in a sibling folder", () => {
1659            const initialFile = { name: "/root/src/background/app.ts" };
1660            const targetFile = { name: "/root/src/typedefs/filesystem.d.ts" };
1661            it("when host doesnt have directoryExists", () => {
1662                testWorker(/*hasDirectoryExists*/ false, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile);
1663            });
1664            it("when host has directoryExists", () => {
1665                testWorker(/*hasDirectoryExists*/ true, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile);
1666            });
1667        });
1668    });
1669}
1670