1namespace ts { 2 describe("unittests:: builder", () => { 3 it("emits dependent files", () => { 4 const files: NamedSourceText[] = [ 5 { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, 6 { name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, 7 { name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") }, 8 ]; 9 10 let program = newProgram(files, ["/a.ts"], {}); 11 const assertChanges = makeAssertChanges(() => program); 12 13 assertChanges(["/c.js", "/b.js", "/a.js"]); 14 15 program = updateProgramFile(program, "/a.ts", "//comment"); 16 assertChanges(["/a.js"]); 17 18 program = updateProgramFile(program, "/b.ts", "export const b = c + 1;"); 19 assertChanges(["/b.js", "/a.js"]); 20 21 program = updateProgramFile(program, "/c.ts", "export const c = 1;"); 22 assertChanges(["/c.js", "/b.js"]); 23 }); 24 25 it("if emitting all files, emits the changed file first", () => { 26 const files: NamedSourceText[] = [ 27 { name: "/a.ts", text: SourceText.New("", "", "namespace A { export const x = 0; }") }, 28 { name: "/b.ts", text: SourceText.New("", "", "namespace B { export const x = 0; }") }, 29 ]; 30 31 let program = newProgram(files, ["/a.ts", "/b.ts"], {}); 32 const assertChanges = makeAssertChanges(() => program); 33 34 assertChanges(["/a.js", "/b.js"]); 35 36 program = updateProgramFile(program, "/a.ts", "namespace A { export const x = 1; }"); 37 assertChanges(["/a.js", "/b.js"]); 38 39 program = updateProgramFile(program, "/b.ts", "namespace B { export const x = 1; }"); 40 assertChanges(["/b.js", "/a.js"]); 41 }); 42 43 it("keeps the file in affected files if cancellation token throws during the operation", () => { 44 const files: NamedSourceText[] = [ 45 { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, 46 { name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, 47 { name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") }, 48 { name: "/d.ts", text: SourceText.New("", "", "export const dd = 0;") }, 49 { name: "/e.ts", text: SourceText.New("", "", "export const ee = 0;") }, 50 ]; 51 52 let program = newProgram(files, ["/d.ts", "/e.ts", "/a.ts"], {}); 53 const assertChanges = makeAssertChangesWithCancellationToken(() => program); 54 // No cancellation 55 assertChanges(["/d.js", "/e.js", "/c.js", "/b.js", "/a.js"]); 56 57 // cancel when emitting a.ts 58 program = updateProgramFile(program, "/a.ts", "export function foo() { }"); 59 assertChanges(["/a.js"], 0); 60 // Change d.ts and verify previously pending a.ts is emitted as well 61 program = updateProgramFile(program, "/d.ts", "export function bar() { }"); 62 assertChanges(["/a.js", "/d.js"]); 63 64 // Cancel when emitting b.js 65 program = updateProgramFile(program, "/b.ts", "export class b { foo() { c + 1; } }"); 66 program = updateProgramFile(program, "/d.ts", "export function bar2() { }"); 67 assertChanges(["/d.js", "/b.js", "/a.js"], 1); 68 // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts 69 program = updateProgramFile(program, "/e.ts", "export function bar3() { }"); 70 assertChanges(["/b.js", "/a.js", "/e.js"]); 71 }); 72 }); 73 74 function makeAssertChanges(getProgram: () => Program): (fileNames: readonly string[]) => void { 75 const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; 76 let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; 77 return fileNames => { 78 const program = getProgram(); 79 builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); 80 const outputFileNames: string[] = []; 81 // eslint-disable-next-line no-empty 82 while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName))) { 83 } 84 assert.deepEqual(outputFileNames, fileNames); 85 }; 86 } 87 88 function makeAssertChangesWithCancellationToken(getProgram: () => Program): (fileNames: readonly string[], cancelAfterEmitLength?: number) => void { 89 const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; 90 let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; 91 let cancel = false; 92 const cancellationToken: CancellationToken = { 93 isCancellationRequested: () => cancel, 94 throwIfCancellationRequested: () => { 95 if (cancel) { 96 throw new OperationCanceledException(); 97 } 98 }, 99 }; 100 return (fileNames, cancelAfterEmitLength?: number) => { 101 cancel = false; 102 let operationWasCancelled = false; 103 const program = getProgram(); 104 builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); 105 const outputFileNames: string[] = []; 106 try { 107 do { 108 assert.isFalse(cancel); 109 if (outputFileNames.length === cancelAfterEmitLength) { 110 cancel = true; 111 } 112 } while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName), cancellationToken)); 113 } 114 catch (e) { 115 assert.isFalse(operationWasCancelled); 116 assert(e instanceof OperationCanceledException, e.toString()); 117 operationWasCancelled = true; 118 } 119 assert.equal(cancel, operationWasCancelled); 120 assert.equal(operationWasCancelled, fileNames.length > cancelAfterEmitLength!); 121 assert.deepEqual(outputFileNames, fileNames.slice(0, cancelAfterEmitLength)); 122 }; 123 } 124 125 function updateProgramFile(program: ProgramWithSourceTexts, fileName: string, fileContent: string): ProgramWithSourceTexts { 126 return updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => { 127 updateProgramText(files, fileName, fileContent); 128 }); 129 } 130} 131