• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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