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