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