1namespace ts { 2 3 const enum ChangedPart { 4 references = 1 << 0, 5 importsAndExports = 1 << 1, 6 program = 1 << 2 7 } 8 9 const newLine = "\r\n"; 10 11 interface SourceFileWithText extends SourceFile { 12 sourceText?: SourceText; 13 } 14 15 export interface NamedSourceText { 16 name: string; 17 text: SourceText; 18 } 19 20 export interface ProgramWithSourceTexts extends Program { 21 sourceTexts?: readonly NamedSourceText[]; 22 host: TestCompilerHost; 23 } 24 25 interface TestCompilerHost extends CompilerHost { 26 getTrace(): string[]; 27 } 28 29 export class SourceText implements IScriptSnapshot { 30 private fullText: string | undefined; 31 32 constructor(private references: string, 33 private importsAndExports: string, 34 private program: string, 35 private changedPart: ChangedPart = 0, 36 private version = 0) { 37 } 38 39 static New(references: string, importsAndExports: string, program: string): SourceText { 40 Debug.assert(references !== undefined); 41 Debug.assert(importsAndExports !== undefined); 42 Debug.assert(program !== undefined); 43 return new SourceText(references + newLine, importsAndExports + newLine, program || ""); 44 } 45 46 public getVersion(): number { 47 return this.version; 48 } 49 50 public updateReferences(newReferences: string): SourceText { 51 Debug.assert(newReferences !== undefined); 52 return new SourceText(newReferences + newLine, this.importsAndExports, this.program, this.changedPart | ChangedPart.references, this.version + 1); 53 } 54 public updateImportsAndExports(newImportsAndExports: string): SourceText { 55 Debug.assert(newImportsAndExports !== undefined); 56 return new SourceText(this.references, newImportsAndExports + newLine, this.program, this.changedPart | ChangedPart.importsAndExports, this.version + 1); 57 } 58 public updateProgram(newProgram: string): SourceText { 59 Debug.assert(newProgram !== undefined); 60 return new SourceText(this.references, this.importsAndExports, newProgram, this.changedPart | ChangedPart.program, this.version + 1); 61 } 62 63 public getFullText() { 64 return this.fullText || (this.fullText = this.references + this.importsAndExports + this.program); 65 } 66 67 public getText(start: number, end: number): string { 68 return this.getFullText().substring(start, end); 69 } 70 71 getLength(): number { 72 return this.getFullText().length; 73 } 74 75 getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { 76 const oldText = oldSnapshot as SourceText; 77 let oldSpan: TextSpan; 78 let newLength: number; 79 switch (oldText.changedPart ^ this.changedPart) { 80 case ChangedPart.references: 81 oldSpan = createTextSpan(0, oldText.references.length); 82 newLength = this.references.length; 83 break; 84 case ChangedPart.importsAndExports: 85 oldSpan = createTextSpan(oldText.references.length, oldText.importsAndExports.length); 86 newLength = this.importsAndExports.length; 87 break; 88 case ChangedPart.program: 89 oldSpan = createTextSpan(oldText.references.length + oldText.importsAndExports.length, oldText.program.length); 90 newLength = this.program.length; 91 break; 92 default: 93 return Debug.fail("Unexpected change"); 94 } 95 96 return createTextChangeRange(oldSpan, newLength); 97 } 98 } 99 100 function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ScriptTarget) { 101 const file = createSourceFile(fileName, sourceText.getFullText(), target) as SourceFileWithText; 102 file.sourceText = sourceText; 103 file.version = "" + sourceText.getVersion(); 104 return file; 105 } 106 107 export function createTestCompilerHost(texts: readonly NamedSourceText[], target: ScriptTarget, oldProgram?: ProgramWithSourceTexts, useGetSourceFileByPath?: boolean) { 108 const files = arrayToMap(texts, t => t.name, t => { 109 if (oldProgram) { 110 let oldFile = oldProgram.getSourceFile(t.name) as SourceFileWithText; 111 if (oldFile && oldFile.redirectInfo) { 112 oldFile = oldFile.redirectInfo.unredirected; 113 } 114 if (oldFile && oldFile.sourceText!.getVersion() === t.text.getVersion()) { 115 return oldFile; 116 } 117 } 118 return createSourceFileWithText(t.name, t.text, target); 119 }); 120 const useCaseSensitiveFileNames = sys && sys.useCaseSensitiveFileNames; 121 const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); 122 const trace: string[] = []; 123 const result: TestCompilerHost = { 124 trace: s => trace.push(s), 125 getTrace: () => trace, 126 getSourceFile: fileName => files.get(fileName), 127 getDefaultLibFileName: () => "lib.d.ts", 128 writeFile: notImplemented, 129 getCurrentDirectory: () => "", 130 getDirectories: () => [], 131 getCanonicalFileName, 132 useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, 133 getNewLine: () => sys ? sys.newLine : newLine, 134 fileExists: fileName => files.has(fileName), 135 readFile: fileName => { 136 const file = files.get(fileName); 137 return file && file.text; 138 }, 139 }; 140 if (useGetSourceFileByPath) { 141 const filesByPath = mapEntries(files, (fileName, file) => [toPath(fileName, "", getCanonicalFileName), file]); 142 result.getSourceFileByPath = (_fileName, path) => filesByPath.get(path); 143 } 144 return result; 145 } 146 147 export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions, useGetSourceFileByPath?: boolean): ProgramWithSourceTexts { 148 const host = createTestCompilerHost(texts, options.target!, /*oldProgram*/ undefined, useGetSourceFileByPath); 149 const program = createProgram(rootNames, options, host) as ProgramWithSourceTexts; 150 program.sourceTexts = texts; 151 program.host = host; 152 return program; 153 } 154 155 export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: readonly string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[], useGetSourceFileByPath?: boolean) { 156 if (!newTexts) { 157 newTexts = oldProgram.sourceTexts!.slice(0); 158 } 159 updater(newTexts); 160 const host = createTestCompilerHost(newTexts, options.target!, oldProgram, useGetSourceFileByPath); 161 const program = createProgram(rootNames, options, host, oldProgram) as ProgramWithSourceTexts; 162 program.sourceTexts = newTexts; 163 program.host = host; 164 return program; 165 } 166 167 export function updateProgramText(files: readonly NamedSourceText[], fileName: string, newProgramText: string) { 168 const file = find(files, f => f.name === fileName)!; 169 file.text = file.text.updateProgram(newProgramText); 170 } 171 172 function checkResolvedTypeDirective(actual: ResolvedTypeReferenceDirective, expected: ResolvedTypeReferenceDirective) { 173 assert.equal(actual.resolvedFileName, expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`); 174 assert.equal(actual.primary, expected.primary, `'primary': expected '${actual.primary}' to be equal to '${expected.primary}'`); 175 return true; 176 } 177 178 function checkCache<T>(caption: string, program: Program, fileName: string, expectedContent: ESMap<string, T> | undefined, getCache: (f: SourceFile) => ModeAwareCache<T> | undefined, entryChecker: (expected: T, original: T) => boolean): void { 179 const file = program.getSourceFile(fileName); 180 assert.isTrue(file !== undefined, `cannot find file ${fileName}`); 181 const cache = getCache(file!); 182 if (expectedContent === undefined) { 183 assert.isTrue(cache === undefined, `expected ${caption} to be undefined`); 184 } 185 else { 186 assert.isTrue(cache !== undefined, `expected ${caption} to be set`); 187 assert.isTrue(mapEqualToCache(expectedContent, cache!, entryChecker), `contents of ${caption} did not match the expected contents.`); 188 } 189 } 190 191 /** True if the maps have the same keys and values. */ 192 function mapEqualToCache<T>(left: ESMap<string, T>, right: ModeAwareCache<T>, valuesAreEqual?: (left: T, right: T) => boolean): boolean { 193 if (left as any === right) return true; // given the type mismatch (the tests never pass a cache), this'll never be true 194 if (!left || !right) return false; 195 const someInLeftHasNoMatch = forEachEntry(left, (leftValue, leftKey) => { 196 if (!right.has(leftKey, /*mode*/ undefined)) return true; 197 const rightValue = right.get(leftKey, /*mode*/ undefined)!; 198 return !(valuesAreEqual ? valuesAreEqual(leftValue, rightValue) : leftValue === rightValue); 199 }); 200 if (someInLeftHasNoMatch) return false; 201 let someInRightHasNoMatch = false; 202 right.forEach((_, rightKey) => someInRightHasNoMatch = someInRightHasNoMatch || !left.has(rightKey)); 203 return !someInRightHasNoMatch; 204 } 205 206 function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: ESMap<string, ResolvedModule | undefined> | undefined): void { 207 checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, checkResolvedModule); 208 } 209 210 function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: ESMap<string, ResolvedTypeReferenceDirective> | undefined): void { 211 checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective); 212 } 213 214 describe("unittests:: Reuse program structure:: General", () => { 215 const target = ScriptTarget.Latest; 216 const files: NamedSourceText[] = [ 217 { 218 name: "a.ts", text: SourceText.New( 219 ` 220/// <reference path='b.ts'/> 221/// <reference path='non-existing-file.ts'/> 222/// <reference types="typerefs" /> 223`, "", `var x = 1`) 224 }, 225 { name: "b.ts", text: SourceText.New(`/// <reference path='c.ts'/>`, "", `var y = 2`) }, 226 { name: "c.ts", text: SourceText.New("", "", `var z = 1;`) }, 227 { name: "types/typerefs/index.d.ts", text: SourceText.New("", "", `declare let z: number;`) }, 228 ]; 229 230 it("successful if change does not affect imports", () => { 231 const program1 = newProgram(files, ["a.ts"], { target }); 232 const program2 = updateProgram(program1, ["a.ts"], { target }, files => { 233 files[0].text = files[0].text.updateProgram("var x = 100"); 234 }); 235 assert.equal(program2.structureIsReused, StructureIsReused.Completely); 236 const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); 237 const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts")); 238 assert.equal(program1Diagnostics.length, program2Diagnostics.length); 239 }); 240 241 it("successful if change does not affect type reference directives", () => { 242 const program1 = newProgram(files, ["a.ts"], { target }); 243 const program2 = updateProgram(program1, ["a.ts"], { target }, files => { 244 files[0].text = files[0].text.updateProgram("var x = 100"); 245 }); 246 assert.equal(program2.structureIsReused, StructureIsReused.Completely); 247 const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); 248 const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts")); 249 assert.equal(program1Diagnostics.length, program2Diagnostics.length); 250 }); 251 252 it("successful if change affects a single module of a package", () => { 253 const files = [ 254 { name: "/a.ts", text: SourceText.New("", "import {b} from 'b'", "var a = b;") }, 255 { name: "/node_modules/b/index.d.ts", text: SourceText.New("", "export * from './internal';", "") }, 256 { name: "/node_modules/b/internal.d.ts", text: SourceText.New("", "", "export const b = 1;") }, 257 { name: "/node_modules/b/package.json", text: SourceText.New("", "", JSON.stringify({ name: "b", version: "1.2.3" })) }, 258 ]; 259 260 const options: CompilerOptions = { target, moduleResolution: ModuleResolutionKind.NodeJs }; 261 const program1 = newProgram(files, ["/a.ts"], options); 262 const program2 = updateProgram(program1, ["/a.ts"], options, files => { 263 files[2].text = files[2].text.updateProgram("export const b = 2;"); 264 }); 265 assert.equal(program2.structureIsReused, StructureIsReused.Completely); 266 const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); 267 const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts")); 268 assert.equal(program1Diagnostics.length, program2Diagnostics.length); 269 }); 270 271 it("fails if change affects tripleslash references", () => { 272 const program1 = newProgram(files, ["a.ts"], { target }); 273 const program2 = updateProgram(program1, ["a.ts"], { target }, files => { 274 const newReferences = `/// <reference path='b.ts'/> 275 /// <reference path='c.ts'/> 276 `; 277 files[0].text = files[0].text.updateReferences(newReferences); 278 }); 279 assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); 280 }); 281 282 it("fails if change affects type references", () => { 283 const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); 284 const program2 = updateProgram(program1, ["a.ts"], { types: ["b"] }, noop); 285 assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); 286 }); 287 288 it("succeeds if change doesn't affect type references", () => { 289 const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); 290 const program2 = updateProgram(program1, ["a.ts"], { types: ["a"] }, noop); 291 assert.equal(program2.structureIsReused, StructureIsReused.Completely); 292 }); 293 294 it("fails if change affects imports", () => { 295 const program1 = newProgram(files, ["a.ts"], { target }); 296 const program2 = updateProgram(program1, ["a.ts"], { target }, files => { 297 files[2].text = files[2].text.updateImportsAndExports("import x from 'b'"); 298 }); 299 assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); 300 }); 301 302 it("fails if change affects type directives", () => { 303 const program1 = newProgram(files, ["a.ts"], { target }); 304 const program2 = updateProgram(program1, ["a.ts"], { target }, files => { 305 const newReferences = ` 306/// <reference path='b.ts'/> 307/// <reference path='non-existing-file.ts'/> 308/// <reference types="typerefs1" />`; 309 files[0].text = files[0].text.updateReferences(newReferences); 310 }); 311 assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); 312 }); 313 314 it("fails if module kind changes", () => { 315 const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS }); 316 const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.AMD }, noop); 317 assert.equal(program2.structureIsReused, StructureIsReused.Not); 318 }); 319 320 it("succeeds if rootdir changes", () => { 321 const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/b" }); 322 const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/c" }, noop); 323 assert.equal(program2.structureIsReused, StructureIsReused.Completely); 324 }); 325 326 it("fails if config path changes", () => { 327 const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/b/tsconfig.json" }); 328 const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/c/tsconfig.json" }, noop); 329 assert.equal(program2.structureIsReused, StructureIsReused.Not); 330 }); 331 332 it("succeeds if missing files remain missing", () => { 333 const options: CompilerOptions = { target, noLib: true }; 334 335 const program1 = newProgram(files, ["a.ts"], options); 336 assert.notDeepEqual(emptyArray, program1.getMissingFilePaths()); 337 338 const program2 = updateProgram(program1, ["a.ts"], options, noop); 339 assert.deepEqual(program1.getMissingFilePaths(), program2.getMissingFilePaths()); 340 341 assert.equal(program2.structureIsReused, StructureIsReused.Completely,); 342 }); 343 344 it("fails if missing file is created", () => { 345 const options: CompilerOptions = { target, noLib: true }; 346 347 const program1 = newProgram(files, ["a.ts"], options); 348 assert.notDeepEqual(emptyArray, program1.getMissingFilePaths()); 349 350 const newTexts: NamedSourceText[] = files.concat([{ name: "non-existing-file.ts", text: SourceText.New("", "", `var x = 1`) }]); 351 const program2 = updateProgram(program1, ["a.ts"], options, noop, newTexts); 352 assert.lengthOf(program2.getMissingFilePaths(), 0); 353 354 assert.equal(program2.structureIsReused, StructureIsReused.Not); 355 }); 356 357 it("resolution cache follows imports", () => { 358 (Error as any).stackTraceLimit = Infinity; 359 360 const files = [ 361 { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") }, 362 { name: "b.ts", text: SourceText.New("", "", "var y = 2") }, 363 ]; 364 const options: CompilerOptions = { target }; 365 366 const program1 = newProgram(files, ["a.ts"], options); 367 checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") }))); 368 checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); 369 370 const program2 = updateProgram(program1, ["a.ts"], options, files => { 371 files[0].text = files[0].text.updateProgram("var x = 2"); 372 }); 373 assert.equal(program2.structureIsReused, StructureIsReused.Completely); 374 375 // content of resolution cache should not change 376 checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") }))); 377 checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); 378 379 // imports has changed - program is not reused 380 const program3 = updateProgram(program2, ["a.ts"], options, files => { 381 files[0].text = files[0].text.updateImportsAndExports(""); 382 }); 383 assert.equal(program3.structureIsReused, StructureIsReused.SafeModules); 384 checkResolvedModulesCache(program3, "a.ts", /*expectedContent*/ undefined); 385 386 const program4 = updateProgram(program3, ["a.ts"], options, files => { 387 const newImports = `import x from 'b' 388 import y from 'c' 389 `; 390 files[0].text = files[0].text.updateImportsAndExports(newImports); 391 }); 392 assert.equal(program4.structureIsReused, StructureIsReused.SafeModules); 393 checkResolvedModulesCache(program4, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts"), c: undefined }))); 394 }); 395 396 it("set the resolvedImports after re-using an ambient external module declaration", () => { 397 const files = [ 398 { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";') }, 399 { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, 400 ]; 401 const options: CompilerOptions = { target, typeRoots: ["/types"] }; 402 const program1 = newProgram(files, ["/a.ts"], options); 403 const program2 = updateProgram(program1, ["/a.ts"], options, files => { 404 files[0].text = files[0].text.updateProgram('import * as aa from "a";'); 405 }); 406 assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use"); 407 }); 408 409 it("works with updated SourceFiles", () => { 410 // adapted repro from https://github.com/Microsoft/TypeScript/issues/26166 411 const files = [ 412 { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";a;') }, 413 { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, 414 ]; 415 const host = createTestCompilerHost(files, target); 416 const options: CompilerOptions = { target, typeRoots: ["/types"] }; 417 const program1 = createProgram(["/a.ts"], options, host); 418 let sourceFile = program1.getSourceFile("/a.ts")!; 419 assert.isDefined(sourceFile, "'/a.ts' is included in the program"); 420 sourceFile = updateSourceFile(sourceFile, "'use strict';" + sourceFile.text, { newLength: "'use strict';".length, span: { start: 0, length: 0 } }); 421 assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are updated"); 422 const updateHost: TestCompilerHost = { 423 ...host, 424 getSourceFile(fileName) { 425 return fileName === sourceFile.fileName ? sourceFile : program1.getSourceFile(fileName); 426 } 427 }; 428 const program2 = createProgram(["/a.ts"], options, updateHost, program1); 429 assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use"); 430 assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are not altered"); 431 }); 432 433 it("resolved type directives cache follows type directives", () => { 434 const files = [ 435 { name: "/a.ts", text: SourceText.New("/// <reference types='typedefs'/>", "", "var x = $") }, 436 { name: "/types/typedefs/index.d.ts", text: SourceText.New("", "", "declare var $: number") }, 437 ]; 438 const options: CompilerOptions = { target, typeRoots: ["/types"] }; 439 440 const program1 = newProgram(files, ["/a.ts"], options); 441 checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); 442 checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); 443 444 const program2 = updateProgram(program1, ["/a.ts"], options, files => { 445 files[0].text = files[0].text.updateProgram("var x = 2"); 446 }); 447 assert.equal(program2.structureIsReused, StructureIsReused.Completely); 448 449 // content of resolution cache should not change 450 checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); 451 checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); 452 453 // type reference directives has changed - program is not reused 454 const program3 = updateProgram(program2, ["/a.ts"], options, files => { 455 files[0].text = files[0].text.updateReferences(""); 456 }); 457 458 assert.equal(program3.structureIsReused, StructureIsReused.SafeModules); 459 checkResolvedTypeDirectivesCache(program3, "/a.ts", /*expectedContent*/ undefined); 460 461 const program4 = updateProgram(program3, ["/a.ts"], options, files => { 462 const newReferences = `/// <reference types="typedefs"/> 463 /// <reference types="typedefs2"/> 464 `; 465 files[0].text = files[0].text.updateReferences(newReferences); 466 }); 467 assert.equal(program4.structureIsReused, StructureIsReused.SafeModules); 468 checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); 469 }); 470 471 it("fetches imports after npm install", () => { 472 const file1Ts = { name: "file1.ts", text: SourceText.New("", `import * as a from "a";`, "const myX: number = a.x;") }; 473 const file2Ts = { name: "file2.ts", text: SourceText.New("", "", "") }; 474 const indexDTS = { name: "node_modules/a/index.d.ts", text: SourceText.New("", "export declare let x: number;", "") }; 475 const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.NodeJs }; 476 const rootFiles = [file1Ts, file2Ts]; 477 const filesAfterNpmInstall = [file1Ts, file2Ts, indexDTS]; 478 479 const initialProgram = newProgram(rootFiles, rootFiles.map(f => f.name), options); 480 { 481 assert.deepEqual(initialProgram.host.getTrace(), 482 [ 483 "======== Resolving module 'a' from 'file1.ts'. ========", 484 "Explicitly specified module resolution kind: 'NodeJs'.", 485 "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", 486 "File 'node_modules/a/package.json' does not exist.", 487 "File 'node_modules/a.ts' does not exist.", 488 "File 'node_modules/a.tsx' does not exist.", 489 "File 'node_modules/a.d.ts' does not exist.", 490 "File 'node_modules/a.ets' does not exist.", 491 "File 'node_modules/a.d.ets' does not exist.", 492 "File 'node_modules/a/index.ts' does not exist.", 493 "File 'node_modules/a/index.tsx' does not exist.", 494 "File 'node_modules/a/index.d.ts' does not exist.", 495 "File 'node_modules/a/index.ets' does not exist.", 496 "File 'node_modules/a/index.d.ets' does not exist.", 497 "File 'node_modules/@types/a/package.json' does not exist.", 498 "File 'node_modules/@types/a.d.ts' does not exist.", 499 "File 'node_modules/@types/a/index.d.ts' does not exist.", 500 "Loading module 'a' from 'node_modules' folder, target file type 'JavaScript'.", 501 "File 'node_modules/a/package.json' does not exist according to earlier cached lookups.", 502 "File 'node_modules/a.js' does not exist.", 503 "File 'node_modules/a.jsx' does not exist.", 504 "File 'node_modules/a/index.js' does not exist.", 505 "File 'node_modules/a/index.jsx' does not exist.", 506 "======== Module name 'a' was not resolved. ========" 507 ], 508 "initialProgram: execute module resolution normally."); 509 510 const initialProgramDiagnostics = initialProgram.getSemanticDiagnostics(initialProgram.getSourceFile("file1.ts")); 511 assert.lengthOf(initialProgramDiagnostics, 1, `initialProgram: import should fail.`); 512 } 513 514 const afterNpmInstallProgram = updateProgram(initialProgram, rootFiles.map(f => f.name), options, f => { 515 f[1].text = f[1].text.updateReferences(`/// <reference no-default-lib="true"/>`); 516 }, filesAfterNpmInstall); 517 { 518 assert.deepEqual(afterNpmInstallProgram.host.getTrace(), 519 [ 520 "======== Resolving module 'a' from 'file1.ts'. ========", 521 "Explicitly specified module resolution kind: 'NodeJs'.", 522 "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", 523 "File 'node_modules/a/package.json' does not exist.", 524 "File 'node_modules/a.ts' does not exist.", 525 "File 'node_modules/a.tsx' does not exist.", 526 "File 'node_modules/a.d.ts' does not exist.", 527 "File 'node_modules/a.ets' does not exist.", 528 "File 'node_modules/a.d.ets' does not exist.", 529 "File 'node_modules/a/index.ts' does not exist.", 530 "File 'node_modules/a/index.tsx' does not exist.", 531 "File 'node_modules/a/index.d.ts' exist - use it as a name resolution result.", 532 "======== Module name 'a' was successfully resolved to 'node_modules/a/index.d.ts'. ========" 533 ], 534 "afterNpmInstallProgram: execute module resolution normally."); 535 536 const afterNpmInstallProgramDiagnostics = afterNpmInstallProgram.getSemanticDiagnostics(afterNpmInstallProgram.getSourceFile("file1.ts")); 537 assert.lengthOf(afterNpmInstallProgramDiagnostics, 0, `afterNpmInstallProgram: program is well-formed with import.`); 538 } 539 }); 540 541 it("can reuse ambient module declarations from non-modified files", () => { 542 const files = [ 543 { name: "/a/b/app.ts", text: SourceText.New("", "import * as fs from 'fs'", "") }, 544 { name: "/a/b/node.d.ts", text: SourceText.New("", "", "declare module 'fs' {}") } 545 ]; 546 const options = { target: ScriptTarget.ES2015, traceResolution: true }; 547 const program = newProgram(files, files.map(f => f.name), options); 548 assert.deepEqual(program.host.getTrace(), 549 [ 550 "======== Resolving module 'fs' from '/a/b/app.ts'. ========", 551 "Module resolution kind is not specified, using 'Classic'.", 552 "File '/a/b/fs.ts' does not exist.", 553 "File '/a/b/fs.tsx' does not exist.", 554 "File '/a/b/fs.d.ts' does not exist.", 555 "File '/a/b/fs.ets' does not exist.", 556 "File '/a/b/fs.d.ets' does not exist.", 557 "File '/a/fs.ts' does not exist.", 558 "File '/a/fs.tsx' does not exist.", 559 "File '/a/fs.d.ts' does not exist.", 560 "File '/a/fs.ets' does not exist.", 561 "File '/a/fs.d.ets' does not exist.", 562 "File '/fs.ts' does not exist.", 563 "File '/fs.tsx' does not exist.", 564 "File '/fs.d.ts' does not exist.", 565 "File '/fs.ets' does not exist.", 566 "File '/fs.d.ets' does not exist.", 567 "File '/a/b/node_modules/@types/fs/package.json' does not exist.", 568 "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", 569 "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", 570 "File '/a/node_modules/@types/fs/package.json' does not exist.", 571 "File '/a/node_modules/@types/fs.d.ts' does not exist.", 572 "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", 573 "File '/node_modules/@types/fs/package.json' does not exist.", 574 "File '/node_modules/@types/fs.d.ts' does not exist.", 575 "File '/node_modules/@types/fs/index.d.ts' does not exist.", 576 "File '/a/b/fs.js' does not exist.", 577 "File '/a/b/fs.jsx' does not exist.", 578 "File '/a/fs.js' does not exist.", 579 "File '/a/fs.jsx' does not exist.", 580 "File '/fs.js' does not exist.", 581 "File '/fs.jsx' does not exist.", 582 "======== Module name 'fs' was not resolved. ========", 583 ], "should look for 'fs'"); 584 585 const program2 = updateProgram(program, program.getRootFileNames(), options, f => { 586 f[0].text = f[0].text.updateProgram("var x = 1;"); 587 }); 588 assert.deepEqual(program2.host.getTrace(), [ 589 "Module 'fs' was resolved as ambient module declared in '/a/b/node.d.ts' since this file was not modified." 590 ], "should reuse 'fs' since node.d.ts was not changed"); 591 592 const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => { 593 f[0].text = f[0].text.updateProgram("var y = 1;"); 594 f[1].text = f[1].text.updateProgram("declare var process: any"); 595 }); 596 assert.deepEqual(program3.host.getTrace(), 597 [ 598 "======== Resolving module 'fs' from '/a/b/app.ts'. ========", 599 "Module resolution kind is not specified, using 'Classic'.", 600 "File '/a/b/fs.ts' does not exist.", 601 "File '/a/b/fs.tsx' does not exist.", 602 "File '/a/b/fs.d.ts' does not exist.", 603 "File '/a/b/fs.ets' does not exist.", 604 "File '/a/b/fs.d.ets' does not exist.", 605 "File '/a/fs.ts' does not exist.", 606 "File '/a/fs.tsx' does not exist.", 607 "File '/a/fs.d.ts' does not exist.", 608 "File '/a/fs.ets' does not exist.", 609 "File '/a/fs.d.ets' does not exist.", 610 "File '/fs.ts' does not exist.", 611 "File '/fs.tsx' does not exist.", 612 "File '/fs.d.ts' does not exist.", 613 "File '/fs.ets' does not exist.", 614 "File '/fs.d.ets' does not exist.", 615 "File '/a/b/node_modules/@types/fs/package.json' does not exist.", 616 "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", 617 "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", 618 "File '/a/node_modules/@types/fs/package.json' does not exist.", 619 "File '/a/node_modules/@types/fs.d.ts' does not exist.", 620 "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", 621 "File '/node_modules/@types/fs/package.json' does not exist.", 622 "File '/node_modules/@types/fs.d.ts' does not exist.", 623 "File '/node_modules/@types/fs/index.d.ts' does not exist.", 624 "File '/a/b/fs.js' does not exist.", 625 "File '/a/b/fs.jsx' does not exist.", 626 "File '/a/fs.js' does not exist.", 627 "File '/a/fs.jsx' does not exist.", 628 "File '/fs.js' does not exist.", 629 "File '/fs.jsx' does not exist.", 630 "======== Module name 'fs' was not resolved. ========", 631 ], "should look for 'fs' again since node.d.ts was changed"); 632 }); 633 634 it("can reuse module resolutions from non-modified files", () => { 635 const files = [ 636 { name: "a1.ts", text: SourceText.New("", "", "let x = 1;") }, 637 { name: "a2.ts", text: SourceText.New("", "", "let x = 1;") }, 638 { name: "b1.ts", text: SourceText.New("", "export class B { x: number; }", "") }, 639 { name: "b2.ts", text: SourceText.New("", "export class B { x: number; }", "") }, 640 { name: "node_modules/@types/typerefs1/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, 641 { name: "node_modules/@types/typerefs2/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, 642 { 643 name: "f1.ts", 644 text: 645 SourceText.New( 646 `/// <reference path="a1.ts"/>${newLine}/// <reference types="typerefs1"/>${newLine}/// <reference no-default-lib="true"/>`, 647 `import { B } from './b1';${newLine}export let BB = B;`, 648 "declare module './b1' { interface B { y: string; } }") 649 }, 650 { 651 name: "f2.ts", 652 text: SourceText.New( 653 `/// <reference path="a2.ts"/>${newLine}/// <reference types="typerefs2"/>`, 654 `import { B } from './b2';${newLine}import { BB } from './f1';`, 655 "(new BB).x; (new BB).y;") 656 }, 657 ]; 658 659 const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.Classic }; 660 const program1 = newProgram(files, files.map(f => f.name), options); 661 let expectedErrors = 0; 662 { 663 assert.deepEqual(program1.host.getTrace(), 664 [ 665 "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========", 666 "Resolving with primary search path 'node_modules/@types'.", 667 "File 'node_modules/@types/typerefs1/package.json' does not exist.", 668 "File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.", 669 "======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========", 670 "======== Resolving module './b1' from 'f1.ts'. ========", 671 "Explicitly specified module resolution kind: 'Classic'.", 672 "File 'b1.ts' exist - use it as a name resolution result.", 673 "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", 674 "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", 675 "Resolving with primary search path 'node_modules/@types'.", 676 "File 'node_modules/@types/typerefs2/package.json' does not exist.", 677 "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", 678 "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", 679 "======== Resolving module './b2' from 'f2.ts'. ========", 680 "Explicitly specified module resolution kind: 'Classic'.", 681 "File 'b2.ts' exist - use it as a name resolution result.", 682 "======== Module name './b2' was successfully resolved to 'b2.ts'. ========", 683 "======== Resolving module './f1' from 'f2.ts'. ========", 684 "Explicitly specified module resolution kind: 'Classic'.", 685 "File 'f1.ts' exist - use it as a name resolution result.", 686 "======== Module name './f1' was successfully resolved to 'f1.ts'. ========" 687 ], 688 "program1: execute module resolution normally."); 689 690 const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("f2.ts")); 691 assert.lengthOf(program1Diagnostics, expectedErrors, `initial program should be well-formed`); 692 } 693 const indexOfF1 = 6; 694 const program2 = updateProgram(program1, program1.getRootFileNames(), options, f => { 695 const newSourceText = f[indexOfF1].text.updateReferences(`/// <reference path="a1.ts"/>${newLine}/// <reference types="typerefs1"/>`); 696 f[indexOfF1] = { name: "f1.ts", text: newSourceText }; 697 }); 698 699 { 700 const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("f2.ts")); 701 assert.lengthOf(program2Diagnostics, expectedErrors, `removing no-default-lib shouldn't affect any types used.`); 702 703 assert.deepEqual(program2.host.getTrace(), [ 704 "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========", 705 "Resolving with primary search path 'node_modules/@types'.", 706 "File 'node_modules/@types/typerefs1/package.json' does not exist.", 707 "File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.", 708 "======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========", 709 "======== Resolving module './b1' from 'f1.ts'. ========", 710 "Explicitly specified module resolution kind: 'Classic'.", 711 "File 'b1.ts' exist - use it as a name resolution result.", 712 "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", 713 "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", 714 "Resolving with primary search path 'node_modules/@types'.", 715 "File 'node_modules/@types/typerefs2/package.json' does not exist.", 716 "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", 717 "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", 718 "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", 719 "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'." 720 ], "program2: reuse module resolutions in f2 since it is unchanged"); 721 } 722 723 const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => { 724 const newSourceText = f[indexOfF1].text.updateReferences(`/// <reference path="a1.ts"/>`); 725 f[indexOfF1] = { name: "f1.ts", text: newSourceText }; 726 }); 727 728 { 729 const program3Diagnostics = program3.getSemanticDiagnostics(program3.getSourceFile("f2.ts")); 730 assert.lengthOf(program3Diagnostics, expectedErrors, `typerefs2 was unused, so diagnostics should be unaffected.`); 731 732 assert.deepEqual(program3.host.getTrace(), [ 733 "======== Resolving module './b1' from 'f1.ts'. ========", 734 "Explicitly specified module resolution kind: 'Classic'.", 735 "File 'b1.ts' exist - use it as a name resolution result.", 736 "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", 737 "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", 738 "Resolving with primary search path 'node_modules/@types'.", 739 "File 'node_modules/@types/typerefs2/package.json' does not exist.", 740 "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", 741 "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", 742 "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", 743 "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'." 744 ], "program3: reuse module resolutions in f2 since it is unchanged"); 745 } 746 747 748 const program4 = updateProgram(program3, program3.getRootFileNames(), options, f => { 749 const newSourceText = f[indexOfF1].text.updateReferences(""); 750 f[indexOfF1] = { name: "f1.ts", text: newSourceText }; 751 }); 752 753 { 754 const program4Diagnostics = program4.getSemanticDiagnostics(program4.getSourceFile("f2.ts")); 755 assert.lengthOf(program4Diagnostics, expectedErrors, `a1.ts was unused, so diagnostics should be unaffected.`); 756 757 assert.deepEqual(program4.host.getTrace(), [ 758 "======== Resolving module './b1' from 'f1.ts'. ========", 759 "Explicitly specified module resolution kind: 'Classic'.", 760 "File 'b1.ts' exist - use it as a name resolution result.", 761 "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", 762 "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", 763 "Resolving with primary search path 'node_modules/@types'.", 764 "File 'node_modules/@types/typerefs2/package.json' does not exist.", 765 "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", 766 "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", 767 "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", 768 "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", 769 ], "program_4: reuse module resolutions in f2 since it is unchanged"); 770 } 771 772 const program5 = updateProgram(program4, program4.getRootFileNames(), options, f => { 773 const newSourceText = f[indexOfF1].text.updateImportsAndExports(`import { B } from './b1';`); 774 f[indexOfF1] = { name: "f1.ts", text: newSourceText }; 775 }); 776 777 { 778 const program5Diagnostics = program5.getSemanticDiagnostics(program5.getSourceFile("f2.ts")); 779 assert.lengthOf(program5Diagnostics, ++expectedErrors, `import of BB in f1 fails. BB is of type any. Add one error`); 780 781 assert.deepEqual(program5.host.getTrace(), [ 782 "======== Resolving module './b1' from 'f1.ts'. ========", 783 "Explicitly specified module resolution kind: 'Classic'.", 784 "File 'b1.ts' exist - use it as a name resolution result.", 785 "======== Module name './b1' was successfully resolved to 'b1.ts'. ========" 786 ], "program_5: exports do not affect program structure, so f2's resolutions are silently reused."); 787 } 788 789 const program6 = updateProgram(program5, program5.getRootFileNames(), options, f => { 790 const newSourceText = f[indexOfF1].text.updateProgram(""); 791 f[indexOfF1] = { name: "f1.ts", text: newSourceText }; 792 }); 793 794 { 795 const program6Diagnostics = program6.getSemanticDiagnostics(program6.getSourceFile("f2.ts")); 796 assert.lengthOf(program6Diagnostics, expectedErrors, `import of BB in f1 fails.`); 797 798 assert.deepEqual(program6.host.getTrace(), [ 799 "======== Resolving module './b1' from 'f1.ts'. ========", 800 "Explicitly specified module resolution kind: 'Classic'.", 801 "File 'b1.ts' exist - use it as a name resolution result.", 802 "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", 803 "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", 804 "Resolving with primary search path 'node_modules/@types'.", 805 "File 'node_modules/@types/typerefs2/package.json' does not exist.", 806 "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", 807 "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", 808 "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", 809 "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", 810 ], "program_6: reuse module resolutions in f2 since it is unchanged"); 811 } 812 813 const program7 = updateProgram(program6, program6.getRootFileNames(), options, f => { 814 const newSourceText = f[indexOfF1].text.updateImportsAndExports(""); 815 f[indexOfF1] = { name: "f1.ts", text: newSourceText }; 816 }); 817 818 { 819 const program7Diagnostics = program7.getSemanticDiagnostics(program7.getSourceFile("f2.ts")); 820 assert.lengthOf(program7Diagnostics, expectedErrors, `removing import is noop with respect to program, so no change in diagnostics.`); 821 822 assert.deepEqual(program7.host.getTrace(), [ 823 "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", 824 "Resolving with primary search path 'node_modules/@types'.", 825 "File 'node_modules/@types/typerefs2/package.json' does not exist.", 826 "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", 827 "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", 828 "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", 829 "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", 830 ], "program_7 should reuse module resolutions in f2 since it is unchanged"); 831 } 832 }); 833 834 describe("redirects", () => { 835 const axIndex = "/node_modules/a/node_modules/x/index.d.ts"; 836 const axPackage = "/node_modules/a/node_modules/x/package.json"; 837 const bxIndex = "/node_modules/b/node_modules/x/index.d.ts"; 838 const bxPackage = "/node_modules/b/node_modules/x/package.json"; 839 const root = "/a.ts"; 840 const compilerOptions = { target, moduleResolution: ModuleResolutionKind.NodeJs }; 841 842 function createRedirectProgram(useGetSourceFileByPath: boolean, options?: { bText: string, bVersion: string }): ProgramWithSourceTexts { 843 const files: NamedSourceText[] = [ 844 { 845 name: "/node_modules/a/index.d.ts", 846 text: SourceText.New("", 'import X from "x";', "export function a(x: X): void;"), 847 }, 848 { 849 name: axIndex, 850 text: SourceText.New("", "", "export default class X { private x: number; }"), 851 }, 852 { 853 name: axPackage, 854 text: SourceText.New("", "", JSON.stringify({ name: "x", version: "1.2.3" })), 855 }, 856 { 857 name: "/node_modules/b/index.d.ts", 858 text: SourceText.New("", 'import X from "x";', "export const b: X;"), 859 }, 860 { 861 name: bxIndex, 862 text: SourceText.New("", "", options ? options.bText : "export default class X { private x: number; }"), 863 }, 864 { 865 name: bxPackage, 866 text: SourceText.New("", "", JSON.stringify({ name: "x", version: options ? options.bVersion : "1.2.3" })), 867 }, 868 { 869 name: root, 870 text: SourceText.New("", 'import { a } from "a"; import { b } from "b";', "a(b)"), 871 }, 872 ]; 873 874 return newProgram(files, [root], compilerOptions, useGetSourceFileByPath); 875 } 876 877 function updateRedirectProgram(program: ProgramWithSourceTexts, updater: (files: NamedSourceText[]) => void, useGetSourceFileByPath: boolean): ProgramWithSourceTexts { 878 return updateProgram(program, [root], compilerOptions, updater, /*newTexts*/ undefined, useGetSourceFileByPath); 879 } 880 881 function verifyRedirects(useGetSourceFileByPath: boolean) { 882 it("No changes -> redirect not broken", () => { 883 const program1 = createRedirectProgram(useGetSourceFileByPath); 884 885 const program2 = updateRedirectProgram(program1, files => { 886 updateProgramText(files, root, "const x = 1;"); 887 }, useGetSourceFileByPath); 888 assert.equal(program2.structureIsReused, StructureIsReused.Completely); 889 assert.lengthOf(program2.getSemanticDiagnostics(), 0); 890 }); 891 892 it("Target changes -> redirect broken", () => { 893 const program1 = createRedirectProgram(useGetSourceFileByPath); 894 assert.lengthOf(program1.getSemanticDiagnostics(), 0); 895 896 const program2 = updateRedirectProgram(program1, files => { 897 updateProgramText(files, axIndex, "export default class X { private x: number; private y: number; }"); 898 updateProgramText(files, axPackage, JSON.stringify('{ name: "x", version: "1.2.4" }')); 899 }, useGetSourceFileByPath); 900 assert.equal(program2.structureIsReused, StructureIsReused.Not); 901 assert.lengthOf(program2.getSemanticDiagnostics(), 1); 902 }); 903 904 it("Underlying changes -> redirect broken", () => { 905 const program1 = createRedirectProgram(useGetSourceFileByPath); 906 907 const program2 = updateRedirectProgram(program1, files => { 908 updateProgramText(files, bxIndex, "export default class X { private x: number; private y: number; }"); 909 updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.4" })); 910 }, useGetSourceFileByPath); 911 assert.equal(program2.structureIsReused, StructureIsReused.Not); 912 assert.lengthOf(program2.getSemanticDiagnostics(), 1); 913 }); 914 915 it("Previously duplicate packages -> program structure not reused", () => { 916 const program1 = createRedirectProgram(useGetSourceFileByPath, { bVersion: "1.2.4", bText: "export = class X { private x: number; }" }); 917 918 const program2 = updateRedirectProgram(program1, files => { 919 updateProgramText(files, bxIndex, "export default class X { private x: number; }"); 920 updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" })); 921 }, useGetSourceFileByPath); 922 assert.equal(program2.structureIsReused, StructureIsReused.Not); 923 assert.deepEqual(program2.getSemanticDiagnostics(), []); 924 }); 925 } 926 927 describe("when host implements getSourceFile", () => { 928 verifyRedirects(/*useGetSourceFileByPath*/ false); 929 }); 930 describe("when host implements getSourceFileByPath", () => { 931 verifyRedirects(/*useGetSourceFileByPath*/ true); 932 }); 933 }); 934 }); 935 936 describe("unittests:: Reuse program structure:: host is optional", () => { 937 it("should work if host is not provided", () => { 938 createProgram([], {}); 939 }); 940 }); 941 942 type File = TestFSWithWatch.File; 943 import createTestSystem = TestFSWithWatch.createWatchedSystem; 944 import libFile = TestFSWithWatch.libFile; 945 946 describe("unittests:: Reuse program structure:: isProgramUptoDate", () => { 947 function getWhetherProgramIsUptoDate( 948 program: Program, 949 newRootFileNames: string[], 950 newOptions: CompilerOptions 951 ) { 952 return isProgramUptoDate( 953 program, newRootFileNames, newOptions, 954 path => program.getSourceFileByPath(path)!.version, /*fileExists*/ returnFalse, 955 /*hasInvalidatedResolutions*/ returnFalse, 956 /*hasChangedAutomaticTypeDirectiveNames*/ undefined, 957 /*getParsedCommandLine*/ returnUndefined, 958 /*projectReferences*/ undefined 959 ); 960 } 961 962 function duplicate(options: CompilerOptions): CompilerOptions; 963 function duplicate(fileNames: string[]): string[]; 964 function duplicate(filesOrOptions: CompilerOptions | string[]) { 965 return JSON.parse(JSON.stringify(filesOrOptions)); 966 } 967 968 describe("should return true when there is no change in compiler options and", () => { 969 function verifyProgramIsUptoDate( 970 program: Program, 971 newRootFileNames: string[], 972 newOptions: CompilerOptions 973 ) { 974 const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions); 975 assert.isTrue(actual); 976 } 977 978 function verifyProgramWithoutConfigFile(system: System, rootFiles: string[], options: CompilerOptions) { 979 const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ 980 rootFiles, 981 options, 982 watchOptions: undefined, 983 system 984 })).getCurrentProgram().getProgram(); 985 verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); 986 } 987 988 function verifyProgramWithConfigFile(system: System, configFileName: string) { 989 const program = createWatchProgram(createWatchCompilerHostOfConfigFile({ 990 configFileName, 991 system 992 })).getCurrentProgram().getProgram(); 993 const { fileNames, options } = parseConfigFileWithSystem(configFileName, {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, notImplemented)!; // TODO: GH#18217 994 verifyProgramIsUptoDate(program, fileNames, options); 995 } 996 997 function verifyProgram(files: File[], rootFiles: string[], options: CompilerOptions, configFile: string) { 998 const system = createTestSystem(files); 999 verifyProgramWithoutConfigFile(system, rootFiles, options); 1000 verifyProgramWithConfigFile(system, configFile); 1001 } 1002 1003 it("has empty options", () => { 1004 const file1: File = { 1005 path: "/a/b/file1.ts", 1006 content: "let x = 1" 1007 }; 1008 const file2: File = { 1009 path: "/a/b/file2.ts", 1010 content: "let y = 1" 1011 }; 1012 const configFile: File = { 1013 path: "/a/b/tsconfig.json", 1014 content: "{}" 1015 }; 1016 verifyProgram([file1, file2, libFile, configFile], [file1.path, file2.path], {}, configFile.path); 1017 }); 1018 1019 it("has lib specified in the options", () => { 1020 const compilerOptions: CompilerOptions = { lib: ["es5", "es2015.promise"] }; 1021 const app: File = { 1022 path: "/src/app.ts", 1023 content: "var x: Promise<string>;" 1024 }; 1025 const configFile: File = { 1026 path: "/src/tsconfig.json", 1027 content: JSON.stringify({ compilerOptions }) 1028 }; 1029 const es5Lib: File = { 1030 path: "/compiler/lib.es5.d.ts", 1031 content: "declare const eval: any" 1032 }; 1033 const es2015Promise: File = { 1034 path: "/compiler/lib.es2015.promise.d.ts", 1035 content: "declare class Promise<T> {}" 1036 }; 1037 1038 verifyProgram([app, configFile, es5Lib, es2015Promise], [app.path], compilerOptions, configFile.path); 1039 }); 1040 1041 it("has paths specified in the options", () => { 1042 const compilerOptions: CompilerOptions = { 1043 baseUrl: ".", 1044 paths: { 1045 "*": [ 1046 "packages/mail/data/*", 1047 "packages/styles/*", 1048 "*" 1049 ] 1050 } 1051 }; 1052 const app: File = { 1053 path: "/src/packages/framework/app.ts", 1054 content: 'import classc from "module1/lib/file1";\ 1055 import classD from "module3/file3";\ 1056 let x = new classc();\ 1057 let y = new classD();' 1058 }; 1059 const module1: File = { 1060 path: "/src/packages/mail/data/module1/lib/file1.ts", 1061 content: 'import classc from "module2/file2";export default classc;', 1062 }; 1063 const module2: File = { 1064 path: "/src/packages/mail/data/module1/lib/module2/file2.ts", 1065 content: 'class classc { method2() { return "hello"; } }\nexport default classc', 1066 }; 1067 const module3: File = { 1068 path: "/src/packages/styles/module3/file3.ts", 1069 content: "class classD { method() { return 10; } }\nexport default classD;" 1070 }; 1071 const configFile: File = { 1072 path: "/src/tsconfig.json", 1073 content: JSON.stringify({ compilerOptions }) 1074 }; 1075 1076 verifyProgram([app, module1, module2, module3, libFile, configFile], [app.path], compilerOptions, configFile.path); 1077 }); 1078 1079 it("has include paths specified in tsconfig file", () => { 1080 const compilerOptions: CompilerOptions = { 1081 baseUrl: ".", 1082 paths: { 1083 "*": [ 1084 "packages/mail/data/*", 1085 "packages/styles/*", 1086 "*" 1087 ] 1088 } 1089 }; 1090 const app: File = { 1091 path: "/src/packages/framework/app.ts", 1092 content: 'import classc from "module1/lib/file1";\ 1093 import classD from "module3/file3";\ 1094 let x = new classc();\ 1095 let y = new classD();' 1096 }; 1097 const module1: File = { 1098 path: "/src/packages/mail/data/module1/lib/file1.ts", 1099 content: 'import classc from "module2/file2";export default classc;', 1100 }; 1101 const module2: File = { 1102 path: "/src/packages/mail/data/module1/lib/module2/file2.ts", 1103 content: 'class classc { method2() { return "hello"; } }\nexport default classc', 1104 }; 1105 const module3: File = { 1106 path: "/src/packages/styles/module3/file3.ts", 1107 content: "class classD { method() { return 10; } }\nexport default classD;" 1108 }; 1109 const configFile: File = { 1110 path: "/src/tsconfig.json", 1111 content: JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] }) 1112 }; 1113 verifyProgramWithConfigFile(createTestSystem([app, module1, module2, module3, libFile, configFile]), configFile.path); 1114 }); 1115 it("has the same root file names", () => { 1116 const module1: File = { 1117 path: "/src/packages/mail/data/module1/lib/file1.ts", 1118 content: 'import classc from "module2/file2";export default classc;', 1119 }; 1120 const module2: File = { 1121 path: "/src/packages/mail/data/module1/lib/module2/file2.ts", 1122 content: 'class classc { method2() { return "hello"; } }\nexport default classc', 1123 }; 1124 const module3: File = { 1125 path: "/src/packages/styles/module3/file3.ts", 1126 content: "class classD { method() { return 10; } }\nexport default classD;" 1127 }; 1128 const rootFiles = [module1.path, module2.path, module3.path]; 1129 const system = createTestSystem([module1, module2, module3]); 1130 const options = {}; 1131 const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ 1132 rootFiles, 1133 options, 1134 watchOptions: undefined, 1135 system 1136 })).getCurrentProgram().getProgram(); 1137 verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); 1138 }); 1139 1140 }); 1141 describe("should return false when there is no change in compiler options but", () => { 1142 function verifyProgramIsNotUptoDate( 1143 program: Program, 1144 newRootFileNames: string[], 1145 newOptions: CompilerOptions 1146 ) { 1147 const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions); 1148 assert.isFalse(actual); 1149 } 1150 it("has more root file names", () => { 1151 const module1: File = { 1152 path: "/src/packages/mail/data/module1/lib/file1.ts", 1153 content: 'import classc from "module2/file2";export default classc;', 1154 }; 1155 const module2: File = { 1156 path: "/src/packages/mail/data/module1/lib/module2/file2.ts", 1157 content: 'class classc { method2() { return "hello"; } }\nexport default classc', 1158 }; 1159 const module3: File = { 1160 path: "/src/packages/styles/module3/file3.ts", 1161 content: "class classD { method() { return 10; } }\nexport default classD;" 1162 }; 1163 const rootFiles = [module1.path, module2.path]; 1164 const newRootFiles = [module1.path, module2.path, module3.path]; 1165 const system = createTestSystem([module1, module2, module3]); 1166 const options = {}; 1167 const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ 1168 rootFiles, 1169 options, 1170 watchOptions: undefined, 1171 system 1172 })).getCurrentProgram().getProgram(); 1173 verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options)); 1174 }); 1175 it("has one root file replaced by another", () => { 1176 const module1: File = { 1177 path: "/src/packages/mail/data/module1/lib/file1.ts", 1178 content: 'import classc from "module2/file2";export default classc;', 1179 }; 1180 const module2: File = { 1181 path: "/src/packages/mail/data/module1/lib/module2/file2.ts", 1182 content: 'class classc { method2() { return "hello"; } }\nexport default classc', 1183 }; 1184 const module3: File = { 1185 path: "/src/packages/styles/module3/file3.ts", 1186 content: "class classD { method() { return 10; } }\nexport default classD;" 1187 }; 1188 const rootFiles = [module1.path, module2.path]; 1189 const newRootFiles = [module2.path, module3.path]; 1190 const system = createTestSystem([module1, module2, module3]); 1191 const options = {}; 1192 const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ 1193 rootFiles, 1194 options, 1195 watchOptions: undefined, 1196 system 1197 })).getCurrentProgram().getProgram(); 1198 verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options)); 1199 }); 1200 }); 1201 }); 1202} 1203