1import * as ts from "../../_namespaces/ts"; 2import * as fakes from "../../_namespaces/fakes"; 3import * as vfs from "../../_namespaces/vfs"; 4import * as Harness from "../../_namespaces/Harness"; 5 6export type TscCompileSystem = fakes.System & { 7 writtenFiles: ts.Set<ts.Path>; 8 baseLine(): { file: string; text: string; }; 9 disableUseFileVersionAsSignature?: boolean; 10 storeFilesChangingSignatureDuringEmit?: boolean; 11}; 12 13export const noChangeRun: ts.TestTscEdit = { 14 subScenario: "no-change-run", 15 modifyFs: ts.noop 16}; 17export const noChangeWithExportsDiscrepancyRun: ts.TestTscEdit = { 18 ...noChangeRun, 19 discrepancyExplanation: () => [ 20 "Incremental build did not emit and has .ts as signature so exports has all imported modules/referenced files", 21 "Clean build always uses d.ts for signature for testing thus does not contain non exported modules/referenced files that arent needed" 22 ] 23}; 24export const noChangeOnlyRuns = [noChangeRun]; 25export const noChangeWithExportsDiscrepancyOnlyRuns = [noChangeWithExportsDiscrepancyRun]; 26 27export interface TestTscCompile extends TestTscCompileLikeBase { 28 baselineSourceMap?: boolean; 29 baselineReadFileCalls?: boolean; 30 baselinePrograms?: boolean; 31 baselineDependencies?: boolean; 32} 33 34export type CommandLineProgram = [ts.Program, ts.BuilderProgram?]; 35export interface CommandLineCallbacks { 36 cb: ts.ExecuteCommandLineCallbacks; 37 getPrograms: () => readonly CommandLineProgram[]; 38} 39 40function isAnyProgram(program: ts.Program | ts.BuilderProgram | ts.ParsedCommandLine): program is ts.Program | ts.BuilderProgram { 41 return !!(program as ts.Program | ts.BuilderProgram).getCompilerOptions; 42} 43export function commandLineCallbacks( 44 sys: TscCompileSystem | ts.tscWatch.WatchedSystem, 45 originalReadCall?: ts.System["readFile"], 46): CommandLineCallbacks { 47 let programs: CommandLineProgram[] | undefined; 48 49 return { 50 cb: program => { 51 if (isAnyProgram(program)) { 52 ts.baselineBuildInfo(program.getCompilerOptions(), sys, originalReadCall); 53 (programs || (programs = [])).push(ts.isBuilderProgram(program) ? 54 [program.getProgram(), program] : 55 [program] 56 ); 57 } 58 else { 59 ts.baselineBuildInfo(program.options, sys, originalReadCall); 60 } 61 }, 62 getPrograms: () => { 63 const result = programs || ts.emptyArray; 64 programs = undefined; 65 return result; 66 } 67 }; 68} 69export interface TestTscCompileLikeBase extends VerifyTscCompileLike { 70 diffWithInitial?: boolean; 71 modifyFs?: (fs: vfs.FileSystem) => void; 72 disableUseFileVersionAsSignature?: boolean; 73 environmentVariables?: Record<string, string>; 74} 75 76export interface TestTscCompileLike extends TestTscCompileLikeBase { 77 compile: (sys: TscCompileSystem) => void; 78 additionalBaseline?: (sys: TscCompileSystem) => void; 79} 80/** 81 * Initialize FS, run compile function and save baseline 82 */ 83export function testTscCompileLike(input: TestTscCompileLike) { 84 const initialFs = input.fs(); 85 const inputFs = initialFs.shadow(); 86 const { 87 scenario, subScenario, diffWithInitial, 88 commandLineArgs, modifyFs, 89 environmentVariables, 90 compile: worker, additionalBaseline, 91 } = input; 92 if (modifyFs) modifyFs(inputFs); 93 inputFs.makeReadonly(); 94 const fs = inputFs.shadow(); 95 96 // Create system 97 const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc", env: environmentVariables }) as TscCompileSystem; 98 if (input.disableUseFileVersionAsSignature) sys.disableUseFileVersionAsSignature = true; 99 sys.storeFilesChangingSignatureDuringEmit = true; 100 sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); 101 sys.exit = exitCode => sys.exitCode = exitCode; 102 worker(sys); 103 sys.write(`exitCode:: ExitStatus.${ts.ExitStatus[sys.exitCode as ts.ExitStatus]}\n`); 104 additionalBaseline?.(sys); 105 fs.makeReadonly(); 106 sys.baseLine = () => { 107 const baseFsPatch = diffWithInitial ? 108 inputFs.diff(initialFs, { includeChangedFileWithSameContent: true }) : 109 inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }); 110 const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); 111 return { 112 file: `${ts.isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${subScenario.split(" ").join("-")}.js`, 113 text: `Input:: 114${baseFsPatch ? vfs.formatPatch(baseFsPatch) : ""} 115 116Output:: 117${sys.output.join("")} 118 119${patch ? vfs.formatPatch(patch) : ""}` 120 }; 121 }; 122 return sys; 123} 124 125function makeSystemReadyForBaseline(sys: TscCompileSystem, versionToWrite?: string) { 126 if (versionToWrite) { 127 fakes.patchHostForBuildInfoWrite(sys, versionToWrite); 128 } 129 else { 130 fakes.patchHostForBuildInfoReadWrite(sys); 131 } 132 const writtenFiles = sys.writtenFiles = new ts.Set(); 133 const originalWriteFile = sys.writeFile; 134 sys.writeFile = (fileName, content, writeByteOrderMark) => { 135 const path = ts.toPathWithSystem(sys, fileName); 136 // When buildinfo is same for two projects, 137 // it gives error and doesnt write buildinfo but because buildInfo is written for one project, 138 // readable baseline will be written two times for those two projects with same contents and is ok 139 ts.Debug.assert(!writtenFiles.has(path) || ts.endsWith(path, "baseline.txt")); 140 writtenFiles.add(path); 141 return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); 142 }; 143} 144 145export function createSolutionBuilderHostForBaseline( 146 sys: TscCompileSystem | ts.tscWatch.WatchedSystem, 147 versionToWrite?: string, 148 originalRead?: (TscCompileSystem | ts.tscWatch.WatchedSystem)["readFile"] 149) { 150 if (sys instanceof fakes.System) makeSystemReadyForBaseline(sys, versionToWrite); 151 const { cb } = commandLineCallbacks(sys, originalRead); 152 const host = ts.createSolutionBuilderHost(sys, 153 /*createProgram*/ undefined, 154 ts.createDiagnosticReporter(sys, /*pretty*/ true), 155 ts.createBuilderStatusReporter(sys, /*pretty*/ true), 156 ); 157 host.afterProgramEmitAndDiagnostics = cb; 158 host.afterEmitBundle = cb; 159 return host; 160} 161 162/** 163 * Initialize Fs, execute command line and save baseline 164 */ 165export function testTscCompile(input: TestTscCompile) { 166 let actualReadFileMap: ts.MapLike<number> | undefined; 167 let getPrograms: CommandLineCallbacks["getPrograms"] | undefined; 168 return testTscCompileLike({ 169 ...input, 170 compile: commandLineCompile, 171 additionalBaseline 172 }); 173 174 function commandLineCompile(sys: TscCompileSystem) { 175 makeSystemReadyForBaseline(sys); 176 actualReadFileMap = {}; 177 const originalReadFile = sys.readFile; 178 sys.readFile = path => { 179 // Dont record libs 180 if (path.startsWith("/src/")) { 181 actualReadFileMap![path] = (ts.getProperty(actualReadFileMap!, path) || 0) + 1; 182 } 183 return originalReadFile.call(sys, path); 184 }; 185 186 const result = commandLineCallbacks(sys, originalReadFile); 187 ts.executeCommandLine( 188 sys, 189 result.cb, 190 input.commandLineArgs, 191 ); 192 sys.readFile = originalReadFile; 193 getPrograms = result.getPrograms; 194 } 195 196 function additionalBaseline(sys: TscCompileSystem) { 197 const { baselineSourceMap, baselineReadFileCalls, baselinePrograms, baselineDependencies } = input; 198 if (baselinePrograms) { 199 const baseline: string[] = []; 200 ts.tscWatch.baselinePrograms(baseline, getPrograms!, ts.emptyArray, baselineDependencies); 201 sys.write(baseline.join("\n")); 202 } 203 if (baselineReadFileCalls) { 204 sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `); 205 } 206 if (baselineSourceMap) ts.generateSourceMapBaselineFiles(sys); 207 actualReadFileMap = undefined; 208 getPrograms = undefined; 209 } 210} 211 212export function verifyTscBaseline(sys: () => { baseLine: TscCompileSystem["baseLine"]; }) { 213 it(`Generates files matching the baseline`, () => { 214 const { file, text } = sys().baseLine(); 215 Harness.Baseline.runBaseline(file, text); 216 }); 217} 218export interface VerifyTscCompileLike { 219 scenario: string; 220 subScenario: string; 221 commandLineArgs: readonly string[]; 222 fs: () => vfs.FileSystem; 223} 224 225/** 226 * Verify by baselining after initializing FS and custom compile 227 */ 228export function verifyTscCompileLike<T extends VerifyTscCompileLike>(verifier: (input: T) => { baseLine: TscCompileSystem["baseLine"]; }, input: T) { 229 describe(`tsc ${input.commandLineArgs.join(" ")} ${input.scenario}:: ${input.subScenario}`, () => { 230 describe(input.scenario, () => { 231 describe(input.subScenario, () => { 232 verifyTscBaseline(() => verifier({ 233 ...input, 234 fs: () => input.fs().makeReadonly() 235 })); 236 }); 237 }); 238 }); 239} 240 241/** 242 * Verify by baselining after initializing FS and command line compile 243 */ 244 export function verifyTsc(input: TestTscCompile) { 245 verifyTscCompileLike(testTscCompile, input); 246} 247