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