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