1namespace ts { 2 export type TscCompileSystem = fakes.System & { 3 writtenFiles: Set<string>; 4 baseLine(): { file: string; text: string; }; 5 }; 6 7 export enum BuildKind { 8 Initial = "initial-build", 9 IncrementalDtsChange = "incremental-declaration-changes", 10 IncrementalDtsUnchanged = "incremental-declaration-doesnt-change", 11 IncrementalHeadersChange = "incremental-headers-change-without-dts-changes", 12 NoChangeRun ="no-change-run" 13 } 14 15 export const noChangeRun: TscIncremental = { 16 buildKind: BuildKind.NoChangeRun, 17 modifyFs: noop 18 }; 19 export const noChangeOnlyRuns = [noChangeRun]; 20 21 export interface TscCompile { 22 scenario: string; 23 subScenario: string; 24 buildKind?: BuildKind; // Should be defined for tsc --b 25 fs: () => vfs.FileSystem; 26 commandLineArgs: readonly string[]; 27 28 modifyFs?: (fs: vfs.FileSystem) => void; 29 baselineSourceMap?: boolean; 30 baselineReadFileCalls?: boolean; 31 baselinePrograms?: boolean; 32 } 33 34 export type CommandLineProgram = [Program, EmitAndSemanticDiagnosticsBuilderProgram?]; 35 export interface CommandLineCallbacks { 36 cb: ExecuteCommandLineCallbacks; 37 getPrograms: () => readonly CommandLineProgram[]; 38 } 39 40 function isAnyProgram(program: Program | EmitAndSemanticDiagnosticsBuilderProgram | ParsedCommandLine): program is Program | EmitAndSemanticDiagnosticsBuilderProgram { 41 return !!(program as Program | EmitAndSemanticDiagnosticsBuilderProgram).getCompilerOptions; 42 } 43 export function commandLineCallbacks( 44 sys: System & { writtenFiles: ReadonlyCollection<string>; }, 45 originalReadCall?: System["readFile"] 46 ): CommandLineCallbacks { 47 let programs: CommandLineProgram[] | undefined; 48 49 return { 50 cb: program => { 51 if (isAnyProgram(program)) { 52 baselineBuildInfo(program.getCompilerOptions(), sys, originalReadCall); 53 (programs || (programs = [])).push(isBuilderProgram(program) ? 54 [program.getProgram(), program] : 55 [program] 56 ); 57 } 58 else { 59 baselineBuildInfo(program.options, sys, originalReadCall); 60 } 61 }, 62 getPrograms: () => { 63 const result = programs || emptyArray; 64 programs = undefined; 65 return result; 66 } 67 }; 68 } 69 70 export function tscCompile(input: TscCompile) { 71 const initialFs = input.fs(); 72 const inputFs = initialFs.shadow(); 73 const { 74 scenario, subScenario, buildKind, 75 commandLineArgs, modifyFs, 76 baselineSourceMap, baselineReadFileCalls, baselinePrograms 77 } = input; 78 if (modifyFs) modifyFs(inputFs); 79 inputFs.makeReadonly(); 80 const fs = inputFs.shadow(); 81 82 // Create system 83 const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }) as TscCompileSystem; 84 fakes.patchHostForBuildInfoReadWrite(sys); 85 const writtenFiles = sys.writtenFiles = new Set<string>(); 86 const originalWriteFile = sys.writeFile; 87 sys.writeFile = (fileName, content, writeByteOrderMark) => { 88 assert.isFalse(writtenFiles.has(fileName)); 89 writtenFiles.add(fileName); 90 return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); 91 }; 92 const actualReadFileMap: MapLike<number> = {}; 93 const originalReadFile = sys.readFile; 94 sys.readFile = path => { 95 // Dont record libs 96 if (path.startsWith("/src/")) { 97 actualReadFileMap[path] = (getProperty(actualReadFileMap, path) || 0) + 1; 98 } 99 return originalReadFile.call(sys, path); 100 }; 101 102 sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); 103 sys.exit = exitCode => sys.exitCode = exitCode; 104 const { cb, getPrograms } = commandLineCallbacks(sys, originalReadFile); 105 executeCommandLine( 106 sys, 107 cb, 108 commandLineArgs, 109 ); 110 sys.write(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}\n`); 111 if (baselinePrograms) { 112 const baseline: string[] = []; 113 tscWatch.baselinePrograms(baseline, getPrograms); 114 sys.write(baseline.join("\n")); 115 } 116 if (baselineReadFileCalls) { 117 sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `); 118 } 119 if (baselineSourceMap) generateSourceMapBaselineFiles(sys); 120 121 fs.makeReadonly(); 122 123 sys.baseLine = () => { 124 const baseFsPatch = !buildKind || buildKind === BuildKind.Initial ? 125 inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }) : 126 inputFs.diff(initialFs, { includeChangedFileWithSameContent: true }); 127 const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); 128 return { 129 file: `${isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${buildKind || BuildKind.Initial}/${subScenario.split(" ").join("-")}.js`, 130 text: `Input:: 131${baseFsPatch ? vfs.formatPatch(baseFsPatch) : ""} 132 133Output:: 134${sys.output.join("")} 135 136${patch ? vfs.formatPatch(patch) : ""}` 137 }; 138 }; 139 return sys; 140 } 141 142 export function verifyTscBaseline(sys: () => { baseLine: TscCompileSystem["baseLine"]; }) { 143 it(`Generates files matching the baseline`, () => { 144 const { file, text } = sys().baseLine(); 145 Harness.Baseline.runBaseline(file, text); 146 }); 147 } 148 149 export function verifyTsc(input: TscCompile) { 150 describe(`tsc ${input.commandLineArgs.join(" ")} ${input.scenario}:: ${input.subScenario}`, () => { 151 describe(input.scenario, () => { 152 describe(input.subScenario, () => { 153 let sys: TscCompileSystem; 154 before(() => { 155 sys = tscCompile({ 156 ...input, 157 fs: () => getFsWithTime(input.fs()).fs.makeReadonly() 158 }); 159 }); 160 after(() => { 161 sys = undefined!; 162 }); 163 verifyTscBaseline(() => sys); 164 }); 165 }); 166 }); 167 } 168} 169