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