• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    export function errorDiagnostic(message: fakes.ExpectedDiagnosticMessage): fakes.ExpectedErrorDiagnostic {
3        return { message };
4    }
5
6    export function getExpectedDiagnosticForProjectsInBuild(...projects: string[]): fakes.ExpectedDiagnostic {
7        return [Diagnostics.Projects_in_this_build_Colon_0, projects.map(p => "\r\n    * " + p).join("")];
8    }
9
10    export function changeCompilerVersion(host: fakes.SolutionBuilderHost) {
11        const originalReadFile = host.readFile;
12        host.readFile = path => {
13            const value = originalReadFile.call(host, path);
14            if (!value || !isBuildInfoFile(path)) return value;
15            const buildInfo = getBuildInfo(value);
16            buildInfo.version = fakes.version;
17            return getBuildInfoText(buildInfo);
18        };
19    }
20
21    export function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) {
22        if (!fs.statSync(path).isFile()) {
23            throw new Error(`File ${path} does not exist`);
24        }
25        const old = fs.readFileSync(path, "utf-8");
26        if (old.indexOf(oldText) < 0) {
27            throw new Error(`Text "${oldText}" does not exist in file ${path}`);
28        }
29        const newContent = old.replace(oldText, newText);
30        fs.writeFileSync(path, newContent, "utf-8");
31    }
32
33    export function prependText(fs: vfs.FileSystem, path: string, additionalContent: string) {
34        if (!fs.statSync(path).isFile()) {
35            throw new Error(`File ${path} does not exist`);
36        }
37        const old = fs.readFileSync(path, "utf-8");
38        fs.writeFileSync(path, `${additionalContent}${old}`, "utf-8");
39    }
40
41    export function appendText(fs: vfs.FileSystem, path: string, additionalContent: string) {
42        if (!fs.statSync(path).isFile()) {
43            throw new Error(`File ${path} does not exist`);
44        }
45        const old = fs.readFileSync(path, "utf-8");
46        fs.writeFileSync(path, `${old}${additionalContent}`);
47    }
48
49    export function indexOf(fs: vfs.FileSystem, path: string, searchStr: string) {
50        if (!fs.statSync(path).isFile()) {
51            throw new Error(`File ${path} does not exist`);
52        }
53        const content = fs.readFileSync(path, "utf-8");
54        return content.indexOf(searchStr);
55    }
56
57    export function lastIndexOf(fs: vfs.FileSystem, path: string, searchStr: string) {
58        if (!fs.statSync(path).isFile()) {
59            throw new Error(`File ${path} does not exist`);
60        }
61        const content = fs.readFileSync(path, "utf-8");
62        return content.lastIndexOf(searchStr);
63    }
64
65    export function expectedLocationIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation {
66        return {
67            file,
68            start: indexOf(fs, file, searchStr),
69            length: searchStr.length
70        };
71    }
72
73    export function expectedLocationLastIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation {
74        return {
75            file,
76            start: lastIndexOf(fs, file, searchStr),
77            length: searchStr.length
78        };
79    }
80
81    export function getTime() {
82        let currentTime = 100;
83        return { tick, time, touch };
84
85        function tick() {
86            currentTime += 60_000;
87        }
88
89        function time() {
90            return currentTime;
91        }
92
93        function touch(fs: vfs.FileSystem, path: string) {
94            if (!fs.statSync(path).isFile()) {
95                throw new Error(`File ${path} does not exist`);
96            }
97            fs.utimesSync(path, new Date(time()), new Date(time()));
98        }
99    }
100
101    export const libContent = `${TestFSWithWatch.libFile.content}
102interface ReadonlyArray<T> {}
103declare const console: { log(msg: any): void; };`;
104
105    export const symbolLibContent = `
106interface SymbolConstructor {
107    readonly species: symbol;
108    readonly toStringTag: symbol;
109}
110declare var Symbol: SymbolConstructor;
111interface Symbol {
112    readonly [Symbol.toStringTag]: string;
113}
114`;
115
116    /**
117     * Load project from disk into /src folder
118     */
119    export function loadProjectFromDisk(
120        root: string,
121        libContentToAppend?: string
122    ): vfs.FileSystem {
123        const resolver = vfs.createResolver(Harness.IO);
124        const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
125            files: {
126                ["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver)
127            },
128            cwd: "/",
129            meta: { defaultLibLocation: "/lib" },
130        });
131        addLibAndMakeReadonly(fs, libContentToAppend);
132        return fs;
133    }
134
135    /**
136     * All the files must be in /src
137     */
138    export function loadProjectFromFiles(
139        files: vfs.FileSet,
140        libContentToAppend?: string
141    ): vfs.FileSystem {
142        const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
143            files,
144            cwd: "/",
145            meta: { defaultLibLocation: "/lib" },
146        });
147        addLibAndMakeReadonly(fs, libContentToAppend);
148        return fs;
149    }
150
151    function addLibAndMakeReadonly(fs: vfs.FileSystem, libContentToAppend?: string) {
152        fs.mkdirSync("/lib");
153        fs.writeFileSync("/lib/lib.d.ts", libContentToAppend ? `${libContent}${libContentToAppend}` : libContent);
154        fs.makeReadonly();
155    }
156
157    /**
158     * Gets the FS mountuing existing fs's /src and /lib folder
159     */
160    export function getFsWithTime(baseFs: vfs.FileSystem) {
161        const { time, tick } = getTime();
162        const host = new fakes.System(baseFs) as any as vfs.FileSystemResolverHost;
163        host.getWorkspaceRoot = notImplemented;
164        const resolver = vfs.createResolver(host);
165        const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
166            files: {
167                ["/src"]: new vfs.Mount("/src", resolver),
168                ["/lib"]: new vfs.Mount("/lib", resolver)
169            },
170            cwd: "/",
171            meta: { defaultLibLocation: "/lib" },
172            time
173        });
174        return { fs, time, tick };
175    }
176
177    export function verifyOutputsPresent(fs: vfs.FileSystem, outputs: readonly string[]) {
178        for (const output of outputs) {
179            assert(fs.existsSync(output), `Expect file ${output} to exist`);
180        }
181    }
182
183    export function verifyOutputsAbsent(fs: vfs.FileSystem, outputs: readonly string[]) {
184        for (const output of outputs) {
185            assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`);
186        }
187    }
188
189    export function generateSourceMapBaselineFiles(sys: System & { writtenFiles: ReadonlyCollection<string>; }) {
190        const mapFileNames = mapDefinedIterator(sys.writtenFiles.keys(), f => f.endsWith(".map") ? f : undefined);
191        while (true) {
192            const result = mapFileNames.next();
193            if (result.done) break;
194            const mapFile = result.value;
195            const text = Harness.SourceMapRecorder.getSourceMapRecordWithSystem(sys, mapFile);
196            sys.writeFile(`${mapFile}.baseline.txt`, text);
197        }
198    }
199
200    function generateBundleFileSectionInfo(sys: System, originalReadCall: System["readFile"], baselineRecorder: Harness.Compiler.WriterAggregator, bundleFileInfo: BundleFileInfo | undefined, outFile: string | undefined) {
201        if (!length(bundleFileInfo && bundleFileInfo.sections) && !outFile) return; // Nothing to baseline
202
203        const content = outFile && sys.fileExists(outFile) ? originalReadCall.call(sys, outFile, "utf8")! : "";
204        baselineRecorder.WriteLine("======================================================================");
205        baselineRecorder.WriteLine(`File:: ${outFile}`);
206        for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) {
207            baselineRecorder.WriteLine("----------------------------------------------------------------------");
208            writeSectionHeader(section);
209            if (section.kind !== BundleFileSectionKind.Prepend) {
210                writeTextOfSection(section.pos, section.end);
211            }
212            else if (section.texts.length > 0) {
213                Debug.assert(section.pos === first(section.texts).pos);
214                Debug.assert(section.end === last(section.texts).end);
215                for (const text of section.texts) {
216                    baselineRecorder.WriteLine(">>--------------------------------------------------------------------");
217                    writeSectionHeader(text);
218                    writeTextOfSection(text.pos, text.end);
219                }
220            }
221            else {
222                Debug.assert(section.pos === section.end);
223            }
224        }
225        baselineRecorder.WriteLine("======================================================================");
226
227        function writeTextOfSection(pos: number, end: number) {
228            const textLines = content.substring(pos, end).split(/\r?\n/);
229            for (const line of textLines) {
230                baselineRecorder.WriteLine(line);
231            }
232        }
233
234        function writeSectionHeader(section: BundleFileSection) {
235            baselineRecorder.WriteLine(`${section.kind}: (${section.pos}-${section.end})${section.data ? ":: " + section.data : ""}${section.kind === BundleFileSectionKind.Prepend ? " texts:: " + section.texts.length : ""}`);
236        }
237    }
238
239    export function baselineBuildInfo(
240        options: CompilerOptions,
241        sys: System & { writtenFiles: ReadonlyCollection<string>; },
242        originalReadCall?: System["readFile"]
243    ) {
244        const out = outFile(options);
245        if (!out) return;
246        const { buildInfoPath, jsFilePath, declarationFilePath } = getOutputPathsForBundle(options, /*forceDts*/ false);
247        if (!buildInfoPath || !sys.writtenFiles.has(buildInfoPath)) return;
248        if (!sys.fileExists(buildInfoPath)) return;
249
250        const buildInfo = getBuildInfo((originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!);
251        const bundle = buildInfo.bundle;
252        if (!bundle || (!length(bundle.js && bundle.js.sections) && !length(bundle.dts && bundle.dts.sections))) return;
253
254        // Write the baselines:
255        const baselineRecorder = new Harness.Compiler.WriterAggregator();
256        generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.js, jsFilePath);
257        generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.dts, declarationFilePath);
258        baselineRecorder.Close();
259
260        const text = baselineRecorder.lines.join("\r\n");
261        sys.writeFile(`${buildInfoPath}.baseline.txt`, text);
262    }
263
264    interface VerifyIncrementalCorrectness {
265        scenario: TscCompile["scenario"];
266        subScenario: TscCompile["subScenario"];
267        commandLineArgs: TscCompile["commandLineArgs"];
268        modifyFs: TscCompile["modifyFs"];
269        incrementalModifyFs: TscIncremental["modifyFs"];
270        tick: () => void;
271        baseFs: vfs.FileSystem;
272        newSys: TscCompileSystem;
273        cleanBuildDiscrepancies: TscIncremental["cleanBuildDiscrepancies"];
274    }
275    function verifyIncrementalCorrectness(input: () => VerifyIncrementalCorrectness, index: number) {
276        it(`Verify emit output file text is same when built clean for incremental scenario at:: ${index}`, () => {
277            const {
278                scenario, subScenario, commandLineArgs, cleanBuildDiscrepancies,
279                modifyFs, incrementalModifyFs,
280                tick, baseFs, newSys
281            } = input();
282            const sys = tscCompile({
283                scenario,
284                subScenario,
285                fs: () => baseFs.makeReadonly(),
286                commandLineArgs,
287                modifyFs: fs => {
288                    tick();
289                    if (modifyFs) modifyFs(fs);
290                    incrementalModifyFs(fs);
291                },
292            });
293            const discrepancies = cleanBuildDiscrepancies?.();
294            for (const outputFile of arrayFrom(sys.writtenFiles.keys())) {
295                const cleanBuildText = sys.readFile(outputFile);
296                const incrementalBuildText = newSys.readFile(outputFile);
297                const descrepancyInClean = discrepancies?.get(outputFile);
298                if (!isBuildInfoFile(outputFile)) {
299                    verifyTextEqual(incrementalBuildText, cleanBuildText, descrepancyInClean, `File: ${outputFile}`);
300                }
301                else if (incrementalBuildText !== cleanBuildText) {
302                    // Verify build info without affectedFilesPendingEmit
303                    const { buildInfo: incrementalBuildInfo, affectedFilesPendingEmit: incrementalBuildAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText);
304                    const { buildInfo: cleanBuildInfo, affectedFilesPendingEmit: incrementalAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText);
305                    verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, descrepancyInClean, `TsBuild info text without affectedFilesPendingEmit ${subScenario}:: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`);
306                    // Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option
307                    if (incrementalBuildAffectedFilesPendingEmit && descrepancyInClean === undefined) {
308                        assert.isDefined(incrementalAffectedFilesPendingEmit, `Incremental build contains affectedFilesPendingEmit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`);
309                        let expectedIndex = 0;
310                        incrementalBuildAffectedFilesPendingEmit.forEach(([actualFile]) => {
311                            expectedIndex = findIndex(incrementalAffectedFilesPendingEmit!, ([expectedFile]) => actualFile === expectedFile, expectedIndex);
312                            assert.notEqual(expectedIndex, -1, `Incremental build contains ${actualFile} file as pending emit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`);
313                            expectedIndex++;
314                        });
315                    }
316                }
317            }
318
319            function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, descrepancyInClean: CleanBuildDescrepancy | undefined, message: string) {
320                if (descrepancyInClean === undefined) {
321                    assert.equal(incrementalText, cleanText, message);
322                    return;
323                }
324                switch (descrepancyInClean) {
325                    case CleanBuildDescrepancy.CleanFileTextDifferent:
326                        assert.isDefined(incrementalText, `Incremental file should be present:: ${message}`);
327                        assert.isDefined(cleanText, `Clean file should be present present:: ${message}`);
328                        assert.notEqual(incrementalText, cleanText, message);
329                        return;
330                    case CleanBuildDescrepancy.CleanFilePresent:
331                        assert.isUndefined(incrementalText, `Incremental file should be absent:: ${message}`);
332                        assert.isDefined(cleanText, `Clean file should be present:: ${message}`);
333                        return;
334                    default:
335                        Debug.assertNever(descrepancyInClean);
336                }
337            }
338        });
339    }
340
341    function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { buildInfo: string | undefined; affectedFilesPendingEmit?: ProgramBuildInfo["affectedFilesPendingEmit"]; } {
342        const buildInfo = text ? getBuildInfo(text) : undefined;
343        if (!buildInfo?.program) return { buildInfo: text };
344        // Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
345        const { program: { affectedFilesPendingEmit, options: { noEmit, ...optionsRest}, ...programRest }, ...rest } = buildInfo;
346        return {
347            buildInfo: getBuildInfoText({
348                ...rest,
349                program: {
350                    options: optionsRest,
351                    ...programRest
352                }
353            }),
354            affectedFilesPendingEmit
355        };
356    }
357
358    export enum CleanBuildDescrepancy {
359        CleanFileTextDifferent,
360        CleanFilePresent,
361    }
362
363    export interface TscIncremental {
364        buildKind: BuildKind;
365        modifyFs: (fs: vfs.FileSystem) => void;
366        subScenario?: string;
367        commandLineArgs?: readonly string[];
368        cleanBuildDiscrepancies?: () => ESMap<string, CleanBuildDescrepancy>;
369    }
370
371    export interface VerifyTsBuildInput extends VerifyTsBuildInputWorker {
372        baselineIncremental?: boolean;
373    }
374
375    export function verifyTscIncrementalEdits(input: VerifyTsBuildInput) {
376        verifyTscIncrementalEditsWorker(input);
377        if (input.baselineIncremental) {
378            verifyTscIncrementalEditsWorker({
379                ...input,
380                subScenario: `${input.subScenario} with incremental`,
381                commandLineArgs: [...input.commandLineArgs, "--incremental"],
382            });
383        }
384    }
385
386    export interface VerifyTsBuildInputWorker extends TscCompile {
387        incrementalScenarios: TscIncremental[];
388    }
389    function verifyTscIncrementalEditsWorker({
390        subScenario, fs, scenario, commandLineArgs,
391        baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms,
392        incrementalScenarios
393    }: VerifyTsBuildInputWorker) {
394        describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario}`, () => {
395            let tick: () => void;
396            let sys: TscCompileSystem;
397            let baseFs: vfs.FileSystem;
398            before(() => {
399                ({ fs: baseFs, tick } = getFsWithTime(fs()));
400                sys = tscCompile({
401                    scenario,
402                    subScenario,
403                    fs: () => baseFs.makeReadonly(),
404                    commandLineArgs,
405                    modifyFs: fs => {
406                        if (modifyFs) modifyFs(fs);
407                        tick();
408                    },
409                    baselineSourceMap,
410                    baselineReadFileCalls,
411                    baselinePrograms
412                });
413                Debug.assert(!!incrementalScenarios.length, `${scenario}/${subScenario}:: No incremental scenarios, you probably want to use verifyTsc instead.`);
414            });
415            after(() => {
416                baseFs = undefined!;
417                sys = undefined!;
418                tick = undefined!;
419            });
420            describe("initialBuild", () => {
421                verifyTscBaseline(() => sys);
422            });
423
424            incrementalScenarios.forEach(({
425                buildKind,
426                modifyFs: incrementalModifyFs,
427                subScenario: incrementalSubScenario,
428                commandLineArgs: incrementalCommandLineArgs,
429                cleanBuildDiscrepancies,
430            }, index) => {
431                describe(incrementalSubScenario || buildKind, () => {
432                    let newSys: TscCompileSystem;
433                    before(() => {
434                        Debug.assert(buildKind !== BuildKind.Initial, "Incremental edit cannot be initial compilation");
435                        tick();
436                        newSys = tscCompile({
437                            scenario,
438                            subScenario: incrementalSubScenario || subScenario,
439                            buildKind,
440                            fs: () => sys.vfs,
441                            commandLineArgs: incrementalCommandLineArgs || commandLineArgs,
442                            modifyFs: fs => {
443                                tick();
444                                incrementalModifyFs(fs);
445                                tick();
446                            },
447                            baselineSourceMap,
448                            baselineReadFileCalls,
449                            baselinePrograms
450                        });
451                    });
452                    after(() => {
453                        newSys = undefined!;
454                    });
455                    verifyTscBaseline(() => newSys);
456                    verifyIncrementalCorrectness(() => ({
457                        scenario,
458                        subScenario: incrementalSubScenario || subScenario,
459                        baseFs,
460                        newSys,
461                        commandLineArgs: incrementalCommandLineArgs || commandLineArgs,
462                        cleanBuildDiscrepancies,
463                        incrementalModifyFs,
464                        modifyFs,
465                        tick
466                    }), index);
467                });
468            });
469        });
470    }
471
472    export function verifyTscSerializedIncrementalEdits(input: VerifyTsBuildInput) {
473        verifyTscSerializedIncrementalEditsWorker(input);
474        if (input.baselineIncremental) {
475            verifyTscSerializedIncrementalEditsWorker({
476                ...input,
477                subScenario: `${input.subScenario} with incremental`,
478                commandLineArgs: [...input.commandLineArgs, "--incremental"],
479            });
480        }
481    }
482    function verifyTscSerializedIncrementalEditsWorker({
483        subScenario, fs, scenario, commandLineArgs,
484        baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms,
485        incrementalScenarios
486    }: VerifyTsBuildInputWorker) {
487        describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario} serializedEdits`, () => {
488            Debug.assert(!!incrementalScenarios.length, `${scenario}/${subScenario}:: No incremental scenarios, you probably want to use verifyTsc instead.`);
489            let tick: () => void;
490            let sys: TscCompileSystem;
491            let baseFs: vfs.FileSystem;
492            let incrementalSys: TscCompileSystem[];
493            before(() => {
494                ({ fs: baseFs, tick } = getFsWithTime(fs()));
495                sys = tscCompile({
496                    scenario,
497                    subScenario,
498                    fs: () => baseFs.makeReadonly(),
499                    commandLineArgs,
500                    modifyFs: fs => {
501                        if (modifyFs) modifyFs(fs);
502                        tick();
503                    },
504                    baselineSourceMap,
505                    baselineReadFileCalls,
506                    baselinePrograms
507                });
508                incrementalScenarios.forEach((
509                    { buildKind, modifyFs, subScenario: incrementalSubScenario, commandLineArgs: incrementalCommandLineArgs },
510                    index
511                ) => {
512                    Debug.assert(buildKind !== BuildKind.Initial, "Incremental edit cannot be initial compilation");
513                    tick();
514                    (incrementalSys || (incrementalSys = [])).push(tscCompile({
515                        scenario,
516                        subScenario: incrementalSubScenario || subScenario,
517                        buildKind,
518                        fs: () => index === 0 ? sys.vfs : incrementalSys[index - 1].vfs,
519                        commandLineArgs: incrementalCommandLineArgs || commandLineArgs,
520                        modifyFs: fs => {
521                            tick();
522                            modifyFs(fs);
523                            tick();
524                        },
525                        baselineSourceMap,
526                        baselineReadFileCalls,
527                        baselinePrograms
528                    }));
529                });
530            });
531            after(() => {
532                baseFs = undefined!;
533                sys = undefined!;
534                tick = undefined!;
535                incrementalSys = undefined!;
536            });
537            describe("serializedBuild", () => {
538
539                verifyTscBaseline(() => ({
540                    baseLine: () => {
541                        const { file, text } = sys.baseLine();
542                        const texts: string[] = [text];
543                        incrementalSys.forEach((sys, index) => {
544                            const incrementalScenario = incrementalScenarios[index];
545                            texts.push("");
546                            texts.push(`Change:: ${incrementalScenario.subScenario || incrementalScenario.buildKind}`);
547                            texts.push(sys.baseLine().text);
548                        });
549                        return { file, text: texts.join("\r\n") };
550                    }
551                }));
552            });
553            describe("incremental correctness", () => {
554                incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs, subScenario, buildKind, cleanBuildDiscrepancies }, index) => verifyIncrementalCorrectness(() => ({
555                    scenario,
556                    subScenario: subScenario || buildKind,
557                    baseFs,
558                    newSys: incrementalSys[index],
559                    commandLineArgs: incrementalCommandLineArgs || commandLineArgs,
560                    cleanBuildDiscrepancies,
561                    incrementalModifyFs: fs => {
562                        for (let i = 0; i <= index; i++) {
563                            incrementalScenarios[i].modifyFs(fs);
564                        }
565                    },
566                    modifyFs,
567                    tick
568                }), index));
569            });
570        });
571    }
572
573    export function enableStrict(fs: vfs.FileSystem, path: string) {
574        replaceText(fs, path, `"strict": false`, `"strict": true`);
575    }
576
577    export function addTestPrologue(fs: vfs.FileSystem, path: string, prologue: string) {
578        prependText(fs, path, `${prologue}
579`);
580    }
581
582    export function addShebang(fs: vfs.FileSystem, project: string, file: string) {
583        prependText(fs, `src/${project}/${file}.ts`, `#!someshebang ${project} ${file}
584`);
585    }
586
587    export function restContent(project: string, file: string) {
588        return `function for${project}${file}Rest() {
589const { b, ...rest } = { a: 10, b: 30, yy: 30 };
590}`;
591    }
592
593    function nonrestContent(project: string, file: string) {
594        return `function for${project}${file}Rest() { }`;
595    }
596
597    export function addRest(fs: vfs.FileSystem, project: string, file: string) {
598        appendText(fs, `src/${project}/${file}.ts`, restContent(project, file));
599    }
600
601    export function removeRest(fs: vfs.FileSystem, project: string, file: string) {
602        replaceText(fs, `src/${project}/${file}.ts`, restContent(project, file), nonrestContent(project, file));
603    }
604
605    export function addStubFoo(fs: vfs.FileSystem, project: string, file: string) {
606        appendText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file));
607    }
608
609    export function changeStubToRest(fs: vfs.FileSystem, project: string, file: string) {
610        replaceText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file), restContent(project, file));
611    }
612
613    export function addSpread(fs: vfs.FileSystem, project: string, file: string) {
614        const path = `src/${project}/${file}.ts`;
615        const content = fs.readFileSync(path, "utf8");
616        fs.writeFileSync(path, `${content}
617function ${project}${file}Spread(...b: number[]) { }
618const ${project}${file}_ar = [20, 30];
619${project}${file}Spread(10, ...${project}${file}_ar);`);
620
621        replaceText(fs, `src/${project}/tsconfig.json`, `"strict": false,`, `"strict": false,
622    "downlevelIteration": true,`);
623    }
624
625    export function getTripleSlashRef(project: string) {
626        return `/src/${project}/tripleRef.d.ts`;
627    }
628
629    export function addTripleSlashRef(fs: vfs.FileSystem, project: string, file: string) {
630        fs.writeFileSync(getTripleSlashRef(project), `declare class ${project}${file} { }`);
631        prependText(fs, `src/${project}/${file}.ts`, `///<reference path="./tripleRef.d.ts"/>
632const ${file}Const = new ${project}${file}();
633`);
634    }
635}
636