tsc-ts 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +1 -1
  2. package/dist/api/compiler/ast.d.ts +9 -3
  3. package/dist/api/compiler/ast.js +17 -12
  4. package/dist/api/compiler/checker-classes.js +2 -1
  5. package/dist/api/compiler/checker-contextual.d.ts +20 -0
  6. package/dist/api/compiler/checker-contextual.js +111 -1
  7. package/dist/api/compiler/checker-expressions.js +14 -1
  8. package/dist/api/compiler/checker-jsx.js +2 -1
  9. package/dist/api/compiler/checker-type-factory.js +53 -12
  10. package/dist/api/compiler/checker.d.ts +5 -1
  11. package/dist/api/compiler/checker.js +10 -4
  12. package/dist/api/compiler/compile-program.d.ts +5 -0
  13. package/dist/api/compiler/compile-program.js +8 -0
  14. package/dist/api/compiler/expr-record.d.ts +11 -0
  15. package/dist/api/compiler/expr-record.js +53 -0
  16. package/dist/api/compiler/identifier-name.d.ts +9 -0
  17. package/dist/api/compiler/identifier-name.js +10 -0
  18. package/dist/api/compiler/jsdoc.js +4 -2
  19. package/dist/api/compiler/parser.d.ts +2 -1
  20. package/dist/api/compiler/parser.js +10 -8
  21. package/dist/api/compiler/printer/factory.js +3 -2
  22. package/dist/api/compiler/printer/printer.js +3 -2
  23. package/dist/api/compiler/reparser.js +4 -3
  24. package/dist/api/compiler/scanner.d.ts +8 -0
  25. package/dist/api/compiler/scanner.js +58 -42
  26. package/dist/api/compiler/shared-results.d.ts +376 -143
  27. package/dist/api/compiler/shared-results.js +1550 -773
  28. package/dist/api/compiler/transformers/declarations.js +2 -1
  29. package/package.json +9 -9
  30. package/src/compiler/ast.ts +19 -12
  31. package/src/compiler/cgroup.ts +157 -0
  32. package/src/compiler/checker-classes.ts +2 -1
  33. package/src/compiler/checker-contextual.ts +104 -0
  34. package/src/compiler/checker-expressions.ts +16 -1
  35. package/src/compiler/checker-jsx.ts +2 -1
  36. package/src/compiler/checker-type-factory.ts +53 -14
  37. package/src/compiler/checker.ts +11 -4
  38. package/src/compiler/compile-program.ts +8 -0
  39. package/src/compiler/decoder.ts +2 -1
  40. package/src/compiler/doctor-report.ts +133 -0
  41. package/src/compiler/expr-record.ts +59 -0
  42. package/src/compiler/identifier-name.ts +13 -0
  43. package/src/compiler/jsdoc.ts +4 -2
  44. package/src/compiler/parallel-check.ts +353 -146
  45. package/src/compiler/parallel-emit.ts +3 -3
  46. package/src/compiler/parser.ts +10 -8
  47. package/src/compiler/printer/factory.ts +3 -2
  48. package/src/compiler/printer/printer.ts +4 -2
  49. package/src/compiler/reparser.ts +7 -3
  50. package/src/compiler/run-config.ts +1383 -0
  51. package/src/compiler/run-record-read.ts +87 -0
  52. package/src/compiler/run-records.ts +575 -0
  53. package/src/compiler/scanner.ts +57 -41
  54. package/src/compiler/shared-results.ts +1626 -896
  55. package/src/compiler/thread-entry.ts +3 -1
  56. package/src/compiler/transformers/declarations.ts +2 -1
  57. package/src/compiler/tsc-build.ts +64 -6
  58. package/src/compiler/tsc-cli.ts +33 -4
  59. package/src/compiler/tsc-compile.ts +213 -8
  60. package/src/compiler/tsc-watch.ts +25 -4
@@ -257,7 +257,9 @@ function runParseThread(job: ParseThreadData, drivers: ThreadDrivers): void {
257
257
  const port = parentPort!;
258
258
  port.on('message', (message: unknown) => {
259
259
  const chunk = message as LoadMessage;
260
- if (chunk.stop) port.close();
260
+ if (chunk.stop) {
261
+ port.close();
262
+ }
261
263
  // The main thread read the project, so this one can too; if not, the program's FileLoader
262
264
  // parses the files of the chunk (the main thread sees this thread fail).
263
265
  else if (thread === undefined) throw new Error('A parse thread could not read the project');
@@ -161,6 +161,7 @@ import { ConstantKind } from './reference-resolver.js';
161
161
  import { DeclarationTracker } from './declarations-tracker.js';
162
162
  import { Transformer } from './transformer.js';
163
163
  import { declarationCount, getDeclarations, SymbolFlags } from '../symbol.js';
164
+ import { IdentifierName } from '../identifier-name.js';
164
165
 
165
166
  export class DeclarationTransformer extends Transformer {
166
167
  readonly host: OutputPathsHost;
@@ -1934,7 +1935,7 @@ export class DeclarationTransformer extends Transformer {
1934
1935
  keyword === SyntaxKind.DefaultKeyword
1935
1936
  ) {
1936
1937
  // A parse tree node, so names it uses are resolved where it's written.
1937
- const identifier = new Identifier(text);
1938
+ const identifier = new Identifier(new IdentifierName(text));
1938
1939
  identifier.parent = name.parent;
1939
1940
  return identifier;
1940
1941
  }
@@ -62,12 +62,15 @@ import {
62
62
  exitStatusOf,
63
63
  rechecksOnThreads,
64
64
  reportEmitAndErrors,
65
+ saveRunRecord,
65
66
  Statistics,
66
67
  statisticsOf,
67
68
  SystemTimeStamps,
68
69
  } from './tsc-compile.js';
69
70
  import { sharesPublishedGraphs } from '@scriptc/threads';
70
- import { checkerThreads, parseThreads, shareWithCheckers, startParallelCheck } from './parallel-check.js';
71
+ import { checkerThreads, shareWithCheckers, startParallelCheck } from './parallel-check.js';
72
+ import { preParseConfig, reportRunConfig, type RunConfig } from './run-config.js';
73
+ import { openRecord } from './run-record-read.js';
71
74
  import { emitThreads } from './parallel-emit.js';
72
75
  import { LoadKind, ParseSession, clearPreparedFiles, parallelParse, reportPreparedFiles } from './parallel-parse.js';
73
76
  import { assignSymbolIdsAtCreation } from './symbol.js';
@@ -182,6 +185,8 @@ class BuildTask {
182
185
  buildKind = BuildKind.None;
183
186
  filesToDelete: string[] = [];
184
187
  configTime = 0;
188
+ /** The project's run configuration (run-config.ts), one per project of the build. */
189
+ run: RunConfig | undefined = undefined;
185
190
 
186
191
  constructor(readonly config: string) {}
187
192
  }
@@ -726,6 +731,33 @@ export class Orchestrator {
726
731
  const host = new BuildCompilerHost(sys, this.fs, this.locale);
727
732
  const configOptions = config.options;
728
733
  const checkers = threads ? checkerThreads(configOptions.checkers, configOptions.singleThreaded) : 1;
734
+ // The projects of a build that builds several at once run beside each other, and each one's timings
735
+ // are the machine's, not its own: none keeps a record (run-records.ts). A watcher's builds measure a
736
+ // change, not the project: they keep none either, and read none.
737
+ const builders = this.command.buildOptions.builders ?? (configOptions.singleThreaded === true ? 1 : 4);
738
+ const learning = builders === 1 && !this.watching;
739
+ const opened = learning
740
+ ? openRecord({
741
+ configFile: configOptions.configFilePath ?? task.config,
742
+ rootNames: config.fileNames,
743
+ options: configOptions,
744
+ })
745
+ : undefined;
746
+ // One configuration per project: its parse threads, and the pins of its checker count and sharing.
747
+ const run = (task.run = preParseConfig(configOptions, checkers, process.env, opened?.record, learning));
748
+ if (opened !== undefined) {
749
+ run.recordDirectory = opened.directory;
750
+ run.recordKey = opened.key;
751
+ run.recordFile = opened.file;
752
+ }
753
+ if (builders > 1) {
754
+ run.records = false;
755
+ run.recordsReason = 'concurrent projects';
756
+ }
757
+ if (this.watching) {
758
+ run.records = false;
759
+ run.recordsReason = 'watch mode';
760
+ }
729
761
  if (threads && configOptions.singleThreaded !== true && parallelParse.spawn !== undefined) {
730
762
  // Threads parse and bind the program's files ahead of it (parallel-parse.ts), as for plain
731
763
  // compiles. Here also when the program is checked on this thread, with one checker or after a
@@ -735,7 +767,7 @@ export class Orchestrator {
735
767
  // and parse threads don't load them again.
736
768
  const libraryDirectory = getNormalizedAbsolutePath(sys.defaultLibraryPath, sys.currentDirectory);
737
769
  const session = new ParseSession(
738
- parseThreads(checkers),
770
+ run.parseThreads,
739
771
  configOptions,
740
772
  sys.currentDirectory,
741
773
  task.config,
@@ -783,6 +815,15 @@ export class Orchestrator {
783
815
  times.bindTime = performance.now() - bindStart;
784
816
  const options = program.options;
785
817
  const count = mayUseThreads ? checkerThreads(options.checkers, options.singleThreaded, program) : 1;
818
+ // The run's configuration reports the count that checks: a checker thread run decides again once
819
+ // the plan is made (`startParallelCheck`, run-config.ts).
820
+ if (task.run !== undefined) task.run.checkers = count;
821
+ // A project rebuilt on its old build info checks the files a change affects, not the project: it
822
+ // keeps no sample, which records only keep full checks of (`run-records.ts`).
823
+ if (task.run !== undefined && oldSnapshot !== undefined && isIncremental(options)) {
824
+ task.run.records = false;
825
+ task.run.recordsReason = 'incremental recheck';
826
+ }
786
827
  if (count > 1 && oldSnapshot !== undefined) {
787
828
  if (isIncremental(options)) return this.recheckOnThreads(task, checked, times, oldSnapshot, count, then);
788
829
  // Build info of a project that is no longer incremental has no pending emit to give the threads,
@@ -794,10 +835,20 @@ export class Orchestrator {
794
835
  if (count > 1) {
795
836
  // Checker threads also emit the files they check; the incremental program records their
796
837
  // declaration signatures on this thread.
797
- startParallelCheck(checked, count, (checkTime) => {
798
- this.emitAndReport(task, checked, times, oldSnapshot, checkTime);
799
- then();
800
- });
838
+ startParallelCheck(
839
+ checked,
840
+ count,
841
+ (checkTime) => {
842
+ this.emitAndReport(task, checked, times, oldSnapshot, checkTime);
843
+ then();
844
+ },
845
+ true,
846
+ undefined,
847
+ true,
848
+ undefined,
849
+ undefined,
850
+ task.run,
851
+ );
801
852
  return true;
802
853
  }
803
854
  if (mayUseThreads && this.emitsOnThreads(checked, oldSnapshot))
@@ -899,6 +950,9 @@ export class Orchestrator {
899
950
  true,
900
951
  incrementalProgram.emitKindsOnCheckers(files),
901
952
  incrementalProgram.checksOnCheckers,
953
+ undefined,
954
+ undefined,
955
+ task.run,
902
956
  );
903
957
  return true;
904
958
  }
@@ -945,10 +999,14 @@ export class Orchestrator {
945
999
  task.exitStatus = exitStatusOf(result);
946
1000
  if (parallelCheckTime !== undefined) times.checkTime = parallelCheckTime;
947
1001
  const options = program.options;
1002
+ // A project's sample is kept whether or not the build reports statistics, and the section reports
1003
+ // where it went (`run-records.ts`).
1004
+ const records = saveRunRecord(sys, incrementalProgram, task.run);
948
1005
  if (options.diagnostics === true || options.extendedDiagnostics === true) {
949
1006
  times.totalTime = sys.sinceStart();
950
1007
  const statistics = statisticsOf(incrementalProgram);
951
1008
  statistics.report(sys);
1009
+ if (options.extendedDiagnostics === true && task.run !== undefined) reportRunConfig(sys, task.run, records);
952
1010
  task.statistics = statistics;
953
1011
  }
954
1012
  const status = task.status;
@@ -33,8 +33,10 @@ import { Colors, defaultIsPretty, header, printBuildHelp, printHelp, printVersio
33
33
  import { generateTSConfig } from './init.js';
34
34
  import { isIncremental, type CompilerOptions } from './options.js';
35
35
  import { combinePaths, getDirectoryPath, normalizePath } from './path.js';
36
- import { checkerThreads, parseThreads } from './parallel-check.js';
36
+ import { checkerThreads } from './parallel-check.js';
37
37
  import { ParseSession, parallelParse, reportPreparedFiles } from './parallel-parse.js';
38
+ import { preParseConfig, type RunConfig } from './run-config.js';
39
+ import { openRecord } from './run-record-read.js';
38
40
  import { showConfig, type ShowConfigInput } from './show-config.js';
39
41
  import { assignSymbolIdsAtCreation } from './symbol.js';
40
42
  import { sharesPublishedGraphs } from '@scriptc/threads';
@@ -163,6 +165,8 @@ export type CommandLineResult =
163
165
  readonly errorSummary: ErrorSummaryReporter;
164
166
  /** The build info of an incremental program, read while threads parsed its files; `undefined` if not read yet. */
165
167
  readonly buildInfo?: EarlyBuildInfo;
168
+ /** How the run is configured: parse threads, checker count and sharing (run-config.ts). */
169
+ readonly config: RunConfig;
166
170
  }
167
171
  /** Threads are preparing the program's files; `run` calls back with the result once they are done. */
168
172
  | { readonly kind: 'deferred'; readonly run: (done: (result: CommandLineResult) => void) => void }
@@ -363,6 +367,31 @@ function executeCompile(sys: System, commandLine: ParsedCommandLine): CommandLin
363
367
  const configStart = performance.now();
364
368
  mark('config start');
365
369
  const errors: Diagnostic[] = [];
370
+ // The run's configuration, made once the config's options are known: its parse threads are needed
371
+ // before the first file is parsed.
372
+ let run: RunConfig | undefined = undefined;
373
+ const runConfigOf = (projectOptions: CompilerOptions): RunConfig => {
374
+ if (run === undefined) {
375
+ const opened = openRecord({
376
+ configFile: projectOptions.configFilePath ?? (configFileName === '' ? undefined : configFileName),
377
+ rootNames: fileNames,
378
+ options: projectOptions,
379
+ });
380
+ const made = preParseConfig(
381
+ projectOptions,
382
+ checkerThreads(projectOptions.checkers, projectOptions.singleThreaded),
383
+ process.env,
384
+ opened?.record,
385
+ );
386
+ if (opened !== undefined) {
387
+ made.recordDirectory = opened.directory;
388
+ made.recordKey = opened.key;
389
+ made.recordFile = opened.file;
390
+ }
391
+ run = made;
392
+ }
393
+ return run;
394
+ };
366
395
  // Where threads parse and bind the program's files ahead of it (parallel-parse.ts), they start with
367
396
  // the libraries and the root files found so far while the config's `include` patterns are matched.
368
397
  let session: ParseSession | undefined = undefined;
@@ -370,7 +399,7 @@ function executeCompile(sys: System, commandLine: ParsedCommandLine): CommandLin
370
399
  if (projectOptions.watch === true || options.showConfig === true || !parsesAhead(projectOptions)) return undefined;
371
400
  assignSymbolIdsAtCreation();
372
401
  const started = new ParseSession(
373
- parseThreads(checkerThreads(projectOptions.checkers, projectOptions.singleThreaded)),
402
+ runConfigOf(projectOptions).parseThreads,
374
403
  projectOptions,
375
404
  sys.currentDirectory,
376
405
  configFileName,
@@ -424,20 +453,20 @@ function executeCompile(sys: System, commandLine: ParsedCommandLine): CommandLin
424
453
  reporter,
425
454
  errorSummary,
426
455
  buildInfo,
456
+ config: runConfigOf(config.options),
427
457
  };
428
458
  };
429
459
  if (parsesAhead(config.options)) {
430
460
  // Threads parse and bind the files the program is expected to load first (parallel-parse.ts), and
431
461
  // share the program it becomes: its symbols get their ids when they are created.
432
462
  assignSymbolIdsAtCreation();
433
- const threads = checkerThreads(config.options.checkers, config.options.singleThreaded);
434
463
  return {
435
464
  kind: 'deferred',
436
465
  run: (done: (result: CommandLineResult) => void) => {
437
466
  const session =
438
467
  early ??
439
468
  new ParseSession(
440
- parseThreads(threads),
469
+ runConfigOf(config.options).parseThreads,
441
470
  config.options,
442
471
  sys.currentDirectory,
443
472
  configFileName,
@@ -26,7 +26,9 @@ import {
26
26
  } from './incremental.js';
27
27
  import {
28
28
  checkerThreads,
29
+ memoryLimit,
29
30
  parallelCheck,
31
+ residentMB,
30
32
  shareWithCheckers,
31
33
  startParallelCheck,
32
34
  traceSingleChecker,
@@ -52,6 +54,22 @@ import {
52
54
  type System,
53
55
  SystemFileSystem,
54
56
  } from './tsc-cli.js';
57
+ import { reportRunConfig, Source, type CheckSignals, type RunConfig, type RecordsReport } from './run-config.js';
58
+ import { availableCpus } from './cgroup.js';
59
+ import { doctorReportPath, setDoctorCompile, type DoctorCompile, type DoctorSignals } from './doctor-report.js';
60
+ import {
61
+ displayPath,
62
+ recordEnvironment,
63
+ recordKey,
64
+ recordsDirectory,
65
+ writeRecord,
66
+ type RecordChoice,
67
+ type RecordSample,
68
+ type RecordShape,
69
+ } from './run-records.js';
70
+ import { recordOptionsOf } from './run-record-read.js';
71
+ import { version } from './help.js';
72
+ import { loadavg } from 'node:os';
55
73
 
56
74
  type CompileWork = Extract<CommandLineResult, { kind: 'compile' }>;
57
75
 
@@ -93,6 +111,12 @@ export function compile(sys: System, work: CompileWork): ExitStatus {
93
111
  let compilerProgram: CompilerProgram;
94
112
  if (work.incremental) {
95
113
  const old = oldSnapshotOf(sys, work, times);
114
+ // An incremental recheck on old build info checks the files a change affects, not the program:
115
+ // a record's samples are full checks (`run-records.ts`).
116
+ if (old !== undefined && work.config !== undefined) {
117
+ work.config.records = false;
118
+ work.config.recordsReason = 'incremental recheck';
119
+ }
96
120
  const changesStart = performance.now();
97
121
  compilerProgram = new IncrementalProgram(checked, times, old, new SystemTimeStamps(sys));
98
122
  times.changesComputeTime = performance.now() - changesStart;
@@ -106,7 +130,7 @@ export function compile(sys: System, work: CompileWork): ExitStatus {
106
130
  outputLocale(program.options),
107
131
  );
108
132
  traceSingleChecker(checked);
109
- return emitStatusAndReportStatistics(sys, compilerProgram, result);
133
+ return emitStatusAndReportStatistics(sys, compilerProgram, result, work.config);
110
134
  }
111
135
 
112
136
  /** The old state of `work`'s incremental program: read while threads parsed its files, or now. */
@@ -136,7 +160,12 @@ function oldSnapshotOf(sys: System, work: CompileWork, times: CompileTimes): Sna
136
160
  export function compileThreads(_sys: System, work: CompileWork): number {
137
161
  const options = work.program.options;
138
162
  mark('compile threads');
139
- return checkerThreads(options.checkers, options.singleThreaded, work.program);
163
+ // The run's configuration counts the checkers the program allows: one where it checks little, or
164
+ // where its text doesn't fit the machine's memory as many. A checker thread run decides again once
165
+ // the plan is made (`startParallelCheck`, run-config.ts).
166
+ const count = checkerThreads(options.checkers, options.singleThreaded, work.program);
167
+ work.config.checkers = count;
168
+ return count;
140
169
  }
141
170
 
142
171
  /** The number of threads to emit JavaScript on while this thread checks (`compileWithEmitThreads`), or 0. */
@@ -186,7 +215,7 @@ export function compileWithEmitThreads(
186
215
  outputLocale(program.options),
187
216
  );
188
217
  traceSingleChecker(checked);
189
- done(emitStatusAndReportStatistics(sys, compilerProgram, result));
218
+ done(emitStatusAndReportStatistics(sys, compilerProgram, result, work.config));
190
219
  };
191
220
  const emitKinds = emitKindsOf(compilerProgram);
192
221
  if (emitKinds !== undefined) threads = emitThreads(program, program.options.singleThreaded, emitKinds);
@@ -333,7 +362,7 @@ export function compileWithDeclarationThread(
333
362
  outputLocale(options),
334
363
  );
335
364
  traceSingleChecker(checked);
336
- done(emitStatusAndReportStatistics(sys, compilerProgram, result));
365
+ done(emitStatusAndReportStatistics(sys, compilerProgram, result, work.config));
337
366
  };
338
367
  startDeclarationErrorThread(checked, (declarations) => {
339
368
  // `PlainProgram.getDeclarationDiagnostics` reads them.
@@ -383,6 +412,11 @@ export function compileInParallel(
383
412
  if (sharesPublishedGraphs) shareWithCheckers(checked);
384
413
  checking = program.getSyntacticDiagnostics().length === 0 && program.getProgramDiagnostics().length === 0;
385
414
  }
415
+ if (!checking && work.config !== undefined) {
416
+ // The threads only emit: nothing is checked, so there is no sample to keep.
417
+ work.config.records = false;
418
+ work.config.recordsReason = 'nothing checked';
419
+ }
386
420
  const finish = (checkTime: number): void => {
387
421
  // An incremental program here has no build info (`compileThreads`).
388
422
  let compilerProgram: CompilerProgram;
@@ -402,7 +436,7 @@ export function compileInParallel(
402
436
  );
403
437
  times.checkTime = checkTime;
404
438
  mark(`reported ${result.diagnostics.length} diagnostics`);
405
- done(emitStatusAndReportStatistics(sys, compilerProgram, result));
439
+ done(emitStatusAndReportStatistics(sys, compilerProgram, result, work.config));
406
440
  };
407
441
  const options = program.options;
408
442
  if (!checking && (options.noEmit === true || options.noEmitOnError === true)) return finish(0);
@@ -440,10 +474,13 @@ export function compileInParallel(
440
474
  true,
441
475
  kinds,
442
476
  checking,
477
+ undefined,
478
+ undefined,
479
+ work.config,
443
480
  );
444
481
  return;
445
482
  }
446
- startParallelCheck(checked, threads, finish, true, undefined, checking);
483
+ startParallelCheck(checked, threads, finish, true, undefined, checking, undefined, undefined, work.config);
447
484
  }
448
485
 
449
486
  /**
@@ -463,6 +500,12 @@ function recheckInParallel(
463
500
  done: (status: ExitStatus) => void,
464
501
  ): void {
465
502
  const program = work.program;
503
+ // An incremental recheck measures a part of the program, which a record's samples are not comparable
504
+ // with full checks (`run-records.ts`).
505
+ if (work.config !== undefined) {
506
+ work.config.records = false;
507
+ work.config.recordsReason = 'incremental recheck';
508
+ }
466
509
  const old = oldSnapshotOf(sys, work, times);
467
510
  // The program is shared before its checker does more than merge the globals; the incremental program
468
511
  // compares the references of its files with the old state on as many threads as check.
@@ -487,7 +530,7 @@ function recheckInParallel(
487
530
  );
488
531
  times.checkTime += threadsCheckTime;
489
532
  mark(`reported ${result.diagnostics.length} diagnostics`);
490
- done(emitStatusAndReportStatistics(sys, incrementalProgram, result));
533
+ done(emitStatusAndReportStatistics(sys, incrementalProgram, result, work.config));
491
534
  };
492
535
  // Reporting checks only programs without syntactic, program and global errors (getDiagnosticsOfProgram).
493
536
  // The threads emit the files they check, as the checker that checks a file emits it on this thread: into
@@ -513,6 +556,9 @@ function recheckInParallel(
513
556
  true,
514
557
  incrementalProgram.emitKindsOnCheckers(files),
515
558
  incrementalProgram.checksOnCheckers,
559
+ undefined,
560
+ undefined,
561
+ work.config,
516
562
  );
517
563
  }
518
564
 
@@ -580,20 +626,179 @@ export function reportEmitAndErrors(
580
626
  return new CompileResult(all, emitResult);
581
627
  }
582
628
 
583
- /** The exit status of a compilation, after printing `--diagnostics` statistics. */
629
+ /**
630
+ * The exit status of a compilation, after printing `--diagnostics` statistics and, under
631
+ * `--extendedDiagnostics` only, the run configuration's `Suggestions:` section (run-config.ts).
632
+ */
584
633
  export function emitStatusAndReportStatistics(
585
634
  sys: System,
586
635
  program: CompilerProgram,
587
636
  result: CompileResult,
637
+ config: RunConfig | undefined = undefined,
588
638
  ): ExitStatus {
589
639
  const options = program.options;
640
+ const records = saveRunRecord(sys, program, config);
641
+ if (doctorReportPath() !== undefined) setDoctorCompile(doctorCompileOf(program, config));
590
642
  if (options.diagnostics === true || options.extendedDiagnostics === true) {
591
643
  program.times.totalTime = sys.sinceStart();
592
644
  statisticsOf(program).report(sys);
645
+ if (options.extendedDiagnostics === true && config !== undefined) reportRunConfig(sys, config, records);
593
646
  }
594
647
  return exitStatusOf(result);
595
648
  }
596
649
 
650
+ /**
651
+ * Writes this run's sample to its record (`run-records.ts`) and says what the section's `Records:` line
652
+ * reports. A run keeps no record where it may not write one (watch mode, a build's concurrent projects,
653
+ * `--singleThreaded`, Node builds, `TSC_TS_AUTO=0` or `static`, `TSC_TS_CACHE=0`), where it checked
654
+ * nothing, and where it rechecked the files a change affects on old build info (`recheckInParallel`, a
655
+ * build's projects). The write is best effort: a cache directory that cannot be written reports why and
656
+ * the run goes on.
657
+ */
658
+ export function saveRunRecord(
659
+ sys: System,
660
+ program: CompilerProgram,
661
+ config: RunConfig | undefined,
662
+ ): RecordsReport | undefined {
663
+ if (config === undefined) return undefined;
664
+ if (!config.records) return { off: config.recordsReason ?? 'not measured' };
665
+ if (!sharesPublishedGraphs) return { off: 'Node.js builds' };
666
+ if (program.options.singleThreaded === true) return { off: '--singleThreaded' };
667
+ // A run that stopped before checking (`--noCheck`, syntax errors) has nothing to sample.
668
+ if (!program.checkedTheProgram) return { off: 'nothing checked' };
669
+ // A run that read its record before parsing already knows the directory and key (`openRecord`); a
670
+ // build project that did not computes them now, from the parsed program.
671
+ const directory = config.recordDirectory ?? recordsDirectory(recordEnvironment());
672
+ if (directory === undefined) return { off: 'TSC_TS_CACHE=0' };
673
+ const shape = config.shape ?? recordShapeOf(program, config);
674
+ const key = config.recordKey ?? recordKeyOf(program);
675
+ const written = writeRecord(
676
+ directory,
677
+ key,
678
+ recordSampleOf(sys, program, config),
679
+ shape,
680
+ chosenOf(config),
681
+ config.rejected,
682
+ );
683
+ if (written.path !== undefined) return { path: displayPath(written.path) };
684
+ return { unavailable: written.reason };
685
+ }
686
+
687
+ /** The key this run's record has: the build, the project, the options and the machine (`run-records.ts`). */
688
+ function recordKeyOf(program: CompilerProgram): string {
689
+ return recordKey({
690
+ version,
691
+ executable: process.execPath,
692
+ configFile: program.options.configFilePath,
693
+ rootNames: program.program.commandLine.fileNames,
694
+ options: recordOptionsOf(program.options),
695
+ cpus: Math.max(1, availableCpus()),
696
+ memoryBytes: memoryLimit(),
697
+ });
698
+ }
699
+
700
+ /** What this compile measured, for a doctor child's report (`doctor-report.ts`). */
701
+ function doctorCompileOf(program: CompilerProgram, config: RunConfig | undefined): DoctorCompile {
702
+ const times = program.times;
703
+ const signals = config?.signals;
704
+ return {
705
+ version,
706
+ configFile: program.options.configFilePath,
707
+ key: recordKeyOf(program),
708
+ checkers: config?.checkers ?? 1,
709
+ share: config?.share ?? false,
710
+ parseThreads: config?.parseThreads ?? 1,
711
+ times: {
712
+ parseMs: times.parseTime + times.bindTime,
713
+ checkMs: times.checkTime,
714
+ totalMs:
715
+ times.totalTime !== 0 ? times.totalTime : times.parseTime + times.bindTime + times.checkTime + times.emitTime,
716
+ },
717
+ shape: recordShapeOf(program, config),
718
+ signals: signals === undefined ? undefined : doctorSignalsOf(signals),
719
+ };
720
+ }
721
+
722
+ /** The bound program's signals as a doctor report keeps them. */
723
+ function doctorSignalsOf(signals: CheckSignals): DoctorSignals {
724
+ return {
725
+ count: signals.count,
726
+ ranges: signals.ranges,
727
+ library: signals.library,
728
+ own: signals.own,
729
+ redundancy: signals.redundancy,
730
+ declarationShare: signals.declarationShare,
731
+ nodeModulesShare: signals.nodeModulesShare,
732
+ fileShare: signals.fileShare,
733
+ };
734
+ }
735
+
736
+ /** The program's size, and the library and redundancy signals where the run computed them. */
737
+ function recordShapeOf(program: CompilerProgram, config: RunConfig | undefined): RecordShape {
738
+ let files = 0;
739
+ let chars = 0;
740
+ let declarationChars = 0;
741
+ for (const file of program.program.getSourceFiles()) {
742
+ files++;
743
+ chars += file.end;
744
+ if (file.isDeclarationFile) declarationChars += file.end;
745
+ }
746
+ const signals = config?.signals;
747
+ return {
748
+ files,
749
+ chars,
750
+ declarationChars,
751
+ checkChars: chars - declarationChars,
752
+ library: signals === undefined ? 0 : Math.round(signals.library),
753
+ own: signals === undefined ? 0 : Math.round(signals.own),
754
+ redundancy: signals === undefined ? 0 : signals.redundancy,
755
+ fileShare: signals === undefined ? 0 : Math.max(signals.declarationShare, signals.nodeModulesShare),
756
+ };
757
+ }
758
+
759
+ /** What this run measured, for its record (`RecordSample`, without the number the record gives it). */
760
+ function recordSampleOf(sys: System, program: CompilerProgram, config: RunConfig): Omit<RecordSample, 'run'> {
761
+ const times = program.times;
762
+ const usage = process.cpuUsage();
763
+ const cpus = Math.max(1, availableCpus());
764
+ return {
765
+ at: Math.floor(Date.now() / 1000),
766
+ role: config.role,
767
+ checkers: config.checkers,
768
+ share: config.share,
769
+ bounded: config.bounded,
770
+ parseThreads: config.parseThreads,
771
+ parseMs: Math.round(times.parseTime + times.bindTime),
772
+ checkMs: Math.round(times.checkTime),
773
+ totalMs: Math.round(sys.sinceStart()),
774
+ cpuMs: Math.round((usage.user + usage.system) / 1000),
775
+ peakMB: residentMB()[1] ?? 0,
776
+ load: Math.round((loadavg()[0]! / cpus) * 100) / 100,
777
+ };
778
+ }
779
+
780
+ /**
781
+ * The configuration this run chose for the next one, as its record keeps it (the record numbers the run
782
+ * that chose it). The learning rules name it (`config.chosen`); a run without them records the choice it
783
+ * used, and whether a pin made it.
784
+ */
785
+ function chosenOf(config: RunConfig): RecordChoice {
786
+ if (config.chosen !== undefined) return config.chosen;
787
+ return {
788
+ checkers: config.checkers,
789
+ share: config.share,
790
+ bounded: config.bounded,
791
+ parseThreads: config.parseThreads,
792
+ source:
793
+ config.checkersSource === Source.Pinned ||
794
+ config.shareSource === Source.Pinned ||
795
+ config.boundedSource === Source.Pinned
796
+ ? 'pinned'
797
+ : 'default',
798
+ since: 0,
799
+ };
800
+ }
801
+
597
802
  export function exitStatusOf(result: CompileResult): ExitStatus {
598
803
  if (result.diagnostics.length === 0) return ExitStatus.Success;
599
804
  return result.emitResult.emitSkipped
@@ -43,7 +43,8 @@ import {
43
43
  } from './tsc-cli.js';
44
44
  import { isIncremental } from './options.js';
45
45
  import { ParseSession, clearPreparedFiles, parallelParse, takePreparedFile } from './parallel-parse.js';
46
- import { checkerThreads, parseThreads, shareWithCheckers, startParallelCheck } from './parallel-check.js';
46
+ import { checkerThreads, shareWithCheckers, startParallelCheck } from './parallel-check.js';
47
+ import { preParseConfig, type RunConfig } from './run-config.js';
47
48
  import { assignSymbolIdsAtCreation, symbolIdsAtCreation } from './symbol.js';
48
49
  import { Orchestrator, StatusReporter, startWatchRebuild } from './tsc-build.js';
49
50
  import { mark } from './timeline.js';
@@ -452,6 +453,8 @@ class Watcher {
452
453
  /** Whether a rebuild is running (`poll`), and whether a check came during it. */
453
454
  private building = false;
454
455
  private checkPending = false;
456
+ /** The first program's run configuration, which its rebuilds use too (run-config.ts). */
457
+ private run: RunConfig | undefined = undefined;
455
458
 
456
459
  constructor(
457
460
  private readonly sys: System,
@@ -516,8 +519,15 @@ class Watcher {
516
519
  const config = copyCommandLine(this.config);
517
520
  const options = config.options;
518
521
  const incremental = isIncremental(options);
522
+ // A watch chooses its configuration once, for its first program: parse threads are fixed before
523
+ // the program is parsed, and its rebuilds check what changed as that program did.
524
+ const run = (this.run = preParseConfig(options, checkerThreads(options.checkers, options.singleThreaded)));
525
+ // A watcher's rebuilds measure a change, not the project: it reads records for its first program only
526
+ // and keeps none (run-records.ts).
527
+ run.records = false;
528
+ run.recordsReason = 'watch mode';
519
529
  const session = new ParseSession(
520
- parseThreads(checkerThreads(options.checkers, options.singleThreaded)),
530
+ run.parseThreads,
521
531
  options,
522
532
  sys.currentDirectory,
523
533
  config.configName,
@@ -585,7 +595,8 @@ class Watcher {
585
595
  this.finishBuild(checked);
586
596
  finish();
587
597
  };
588
- if (this.snapshot === undefined && threads > 1) startParallelCheck(checked, threads, report);
598
+ if (this.snapshot === undefined && threads > 1)
599
+ startParallelCheck(checked, threads, report, true, undefined, true, undefined, undefined, run);
589
600
  else report();
590
601
  });
591
602
  if (incremental && sys.fileExists(config.buildInfoFileName())) session.queueAhead();
@@ -802,7 +813,17 @@ class Watcher {
802
813
  this.report(incremental, changedFiles);
803
814
  done();
804
815
  };
805
- startParallelCheck(checked, threads, finish, true, incremental.emitKindsOnCheckers(files));
816
+ startParallelCheck(
817
+ checked,
818
+ threads,
819
+ finish,
820
+ true,
821
+ incremental.emitKindsOnCheckers(files),
822
+ true,
823
+ undefined,
824
+ undefined,
825
+ this.run,
826
+ );
806
827
  return;
807
828
  }
808
829
  }