tsc-ts 0.0.9 → 0.0.10

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 (55) hide show
  1. package/README.md +1 -1
  2. package/dist/api/compiler/ast.d.ts +2 -0
  3. package/dist/api/compiler/ast.js +3 -0
  4. package/dist/api/compiler/checker-calls.js +5 -1
  5. package/dist/api/compiler/checker-grammar.js +3 -2
  6. package/dist/api/compiler/checker-inference.js +8 -2
  7. package/dist/api/compiler/checker-members.js +2 -1
  8. package/dist/api/compiler/checker-type-factory.js +8 -2
  9. package/dist/api/compiler/checker.d.ts +11 -0
  10. package/dist/api/compiler/checker.js +40 -0
  11. package/dist/api/compiler/compile-program.d.ts +26 -2
  12. package/dist/api/compiler/compile-program.js +100 -7
  13. package/dist/api/compiler/diagnostics.d.ts +22 -0
  14. package/dist/api/compiler/diagnostics.js +86 -4
  15. package/dist/api/compiler/file-loader.d.ts +11 -1
  16. package/dist/api/compiler/file-loader.js +63 -7
  17. package/dist/api/compiler/parallel-parse.d.ts +3 -2
  18. package/dist/api/compiler/parallel-parse.js +17 -5
  19. package/dist/api/compiler/printer/emit-context.d.ts +2 -0
  20. package/dist/api/compiler/printer/emit-context.js +4 -0
  21. package/dist/api/compiler/printer/printer.d.ts +48 -2
  22. package/dist/api/compiler/printer/printer.js +238 -14
  23. package/dist/api/compiler/program-checker.d.ts +4 -0
  24. package/dist/api/compiler/program-checker.js +4 -0
  25. package/dist/api/compiler/program.d.ts +2 -6
  26. package/dist/api/compiler/program.js +5 -30
  27. package/dist/api/compiler/relations.js +10 -0
  28. package/dist/api/compiler/symbol.d.ts +1 -1
  29. package/dist/api/compiler/symbol.js +8 -10
  30. package/package.json +9 -9
  31. package/src/compiler/ast.ts +3 -0
  32. package/src/compiler/checker-calls.ts +3 -1
  33. package/src/compiler/checker-grammar.ts +3 -2
  34. package/src/compiler/checker-inference.ts +7 -2
  35. package/src/compiler/checker-members.ts +3 -1
  36. package/src/compiler/checker-type-factory.ts +4 -2
  37. package/src/compiler/checker.ts +37 -0
  38. package/src/compiler/compile-program.ts +121 -7
  39. package/src/compiler/diagnostics.ts +84 -3
  40. package/src/compiler/file-loader.ts +58 -6
  41. package/src/compiler/incremental.ts +80 -13
  42. package/src/compiler/parallel-check.ts +129 -44
  43. package/src/compiler/parallel-emit.ts +320 -0
  44. package/src/compiler/parallel-parse.ts +16 -5
  45. package/src/compiler/parse-thread.ts +3 -0
  46. package/src/compiler/printer/emit-context.ts +5 -0
  47. package/src/compiler/printer/printer.ts +231 -16
  48. package/src/compiler/program-checker.ts +4 -0
  49. package/src/compiler/program.ts +5 -25
  50. package/src/compiler/relations.ts +7 -0
  51. package/src/compiler/symbol.ts +15 -10
  52. package/src/compiler/thread-entry.ts +45 -2
  53. package/src/compiler/tsc-build.ts +63 -9
  54. package/src/compiler/tsc-cli.ts +5 -0
  55. package/src/compiler/tsc-compile.ts +278 -3
@@ -1574,7 +1574,9 @@ export function getPropertyOfTypeEx(
1574
1574
  ): Symbol | undefined {
1575
1575
  if (!isOwnReducedApparentType(t)) t = getReducedApparentTypeWorker(c, t);
1576
1576
  if ((t.flags & TypeFlags.Object) !== 0) {
1577
- const resolved = resolveStructuredTypeMembers(c, t);
1577
+ // The first test of resolveStructuredTypeMembers, here where most lookups find the members resolved.
1578
+ const resolved =
1579
+ (t.objectFlags & ObjectFlags.MembersResolved) !== 0 ? (t as StructuredType) : resolveStructuredTypeMembers(c, t);
1578
1580
  const symbol = getResolvedMember(resolved, name);
1579
1581
  if (symbol !== undefined && includeTypeOnlyMembers) return symbol;
1580
1582
  if (symbol !== undefined) {
@@ -998,7 +998,8 @@ export function unionContainsSortedTypes(c: Checker, union: UnionType, types: re
998
998
  export function insertType(c: Checker, types: Type[], t: Type): boolean {
999
999
  const index = binarySearchTypes(c, types, t);
1000
1000
  if (index >= 0) return false;
1001
- types.splice(~index, 0, t);
1001
+ if (~index === types.length) types.push(t);
1002
+ else types.splice(~index, 0, t);
1002
1003
  return true;
1003
1004
  }
1004
1005
 
@@ -1214,7 +1215,8 @@ function insertTypeNearLast(c: Checker, set: TypeSet, t: Type): void {
1214
1215
  if (r < 0) low = middle + 1;
1215
1216
  else high = middle - 1;
1216
1217
  }
1217
- types.splice(low, 0, t);
1218
+ if (low === types.length) types.push(t);
1219
+ else types.splice(low, 0, t);
1218
1220
  set.last = low;
1219
1221
  }
1220
1222
 
@@ -245,6 +245,30 @@ class DiagnosticCollection {
245
245
  return this.global.slice();
246
246
  }
247
247
 
248
+ /** The global diagnostics `withoutNewGlobals` removed. */
249
+ removedGlobals = 0;
250
+
251
+ /** Runs `run`, then removes the global diagnostics it added. */
252
+ withoutNewGlobals<T>(run: () => T): T {
253
+ const outer = this.log;
254
+ const added: Diagnostic[] = [];
255
+ this.log = added;
256
+ try {
257
+ return run();
258
+ } finally {
259
+ this.log = outer;
260
+ if (outer !== undefined) for (const d of added) outer.push(d);
261
+ const globals = new Set<Diagnostic>();
262
+ for (const d of added) if (d.file === undefined) globals.add(d);
263
+ if (globals.size > 0) {
264
+ this.removedGlobals += globals.size;
265
+ let kept = 0;
266
+ for (const d of this.global) if (!globals.has(d)) this.global[kept++] = d;
267
+ this.global.length = kept;
268
+ }
269
+ }
270
+ }
271
+
248
272
  /** Records the diagnostics added from now on. */
249
273
  startLog(): void {
250
274
  this.log = [];
@@ -940,6 +964,19 @@ export class Checker implements TypeChecking {
940
964
  return this.diagnostics.globals();
941
965
  }
942
966
 
967
+ /**
968
+ * Runs `run` (such as emitting declarations), then forgets the global diagnostics it reported:
969
+ * the program collects global diagnostics before it emits.
970
+ */
971
+ withoutGlobalDiagnostics<T>(run: () => T): T {
972
+ return this.diagnostics.withoutNewGlobals(run);
973
+ }
974
+
975
+ /** The global diagnostics `withoutGlobalDiagnostics` forgot so far. */
976
+ forgottenGlobalDiagnostics(): number {
977
+ return this.diagnostics.removedGlobals;
978
+ }
979
+
943
980
  // ---- Links
944
981
 
945
982
  /** The rarely used links of `symbol`, created on first use. */
@@ -13,7 +13,11 @@ import { CheckerDeclarationEmitResolver, CheckerEmitResolver } from './checker-e
13
13
  import { getEmitModuleFormatOfFile } from './program-utilities.js';
14
14
  import { DeclarationGlobals, SyntacticDeclarationEmitResolver } from './transformers/declarations-syntactic.js';
15
15
  import { mark } from './timeline.js';
16
- import type { EmitResolver } from './transformers/emit-resolver.js';
16
+ import { DecoratorMetadata } from './transformers/decorator-metadata.js';
17
+ import { SyntaxFacts } from './transformers/syntax-facts.js';
18
+ import type { TypeReferenceSerializationKind } from './transformers/type-serialization.js';
19
+ import { EmitContext } from './printer/emit-context.js';
20
+ import { type EmitResolver, SyntacticEmitResolver } from './transformers/emit-resolver.js';
17
21
  import { ModuleFormatHost } from './transformers/module.js';
18
22
  import { type MergedGlobals, ModuleLookup, ReferenceResolver } from './transformers/reference-resolver.js';
19
23
 
@@ -193,6 +197,100 @@ export class EmitIndexes {
193
197
  }
194
198
  }
195
199
 
200
+ /**
201
+ * The JavaScript emit of a program's files without a checker, for emit threads (parallel-emit.ts).
202
+ * JavaScript emit asks the checker only how decorator metadata serializes a type
203
+ * (`CheckerEmitResolver`): with `kinds`, the answers the main thread's checker gave
204
+ * (`recordMetadataKinds`), else files with decorator metadata are emitted by a `ProgramEmitter`.
205
+ */
206
+ export class JavaScriptEmitter {
207
+ private readonly host: ProgramEmitHost;
208
+ private readonly recorded: RecordedKindsEmitResolver | undefined;
209
+
210
+ constructor(program: Program, kinds: readonly number[] = []) {
211
+ const options = program.options;
212
+ const indexes = program.emitIndexes;
213
+ const references = new ReferenceResolver(
214
+ options,
215
+ program.getSourceFiles(),
216
+ new ProgramModules(program),
217
+ indexes !== undefined ? indexes.globals : undefined,
218
+ );
219
+ this.recorded = kinds.length > 0 ? new RecordedKindsEmitResolver(options, references, kinds) : undefined;
220
+ this.host = new ProgramEmitHost(program, this.recorded ?? new SyntacticEmitResolver(options, references));
221
+ }
222
+
223
+ /**
224
+ * Emits `file`, or returns undefined if its decorator metadata asked for a kind that wasn't recorded: what
225
+ * was written is then not the file's emit, and it must be emitted with the checker.
226
+ */
227
+ emit(file: SourceFile, writer: OutputWriter): EmitResult | undefined {
228
+ const host = this.host;
229
+ const recorded = this.recorded;
230
+ if (recorded !== undefined) recorded.missed = false;
231
+ host.writer = writer;
232
+ const result = emitFiles(host, [file], EmitOnly.Js);
233
+ host.writer = undefined;
234
+ return recorded !== undefined && recorded.missed ? undefined : result;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * The serialization kinds of decorator metadata that the checker of `checked` gives the type references of
240
+ * `files`, in the order JavaScript emit asks for them: the type name's node id, the location's node id (-1 for
241
+ * none) and the kind, for each. Only the decorator metadata transform runs, as the first transform of emit.
242
+ */
243
+ export function recordMetadataKinds(checked: CheckedProgram, files: readonly SourceFile[]): number[] {
244
+ const program = checked.program;
245
+ const options = program.options;
246
+ const indexes = program.emitIndexes;
247
+ const references = new ReferenceResolver(
248
+ options,
249
+ program.getSourceFiles(),
250
+ new ProgramModules(program),
251
+ indexes !== undefined ? indexes.globals : undefined,
252
+ );
253
+ const resolver = new RecordingEmitResolver(options, references, checked.checker);
254
+ for (const file of files)
255
+ new DecoratorMetadata(new EmitContext(), options, resolver, new SyntaxFacts()).transformSourceFile(file);
256
+ return resolver.kinds;
257
+ }
258
+
259
+ /** A `CheckerEmitResolver` that records the serialization kinds it gives (`recordMetadataKinds`). */
260
+ class RecordingEmitResolver extends CheckerEmitResolver {
261
+ readonly kinds: number[] = [];
262
+
263
+ getTypeReferenceSerializationKind(typeName: Node, location: Node | undefined): TypeReferenceSerializationKind {
264
+ const kind = super.getTypeReferenceSerializationKind(typeName, location);
265
+ this.kinds.push(typeName.id, location === undefined ? -1 : location.id, kind);
266
+ return kind;
267
+ }
268
+ }
269
+
270
+ /** Answers serialization kinds from those recorded on the main thread; `missed` once one wasn't. */
271
+ class RecordedKindsEmitResolver extends SyntacticEmitResolver {
272
+ /** The kind and location of each type name, by the type name's node id. */
273
+ private readonly byName = new Map<number, number>();
274
+ missed = false;
275
+
276
+ constructor(
277
+ options: CompilerOptions,
278
+ referenceResolver: ReferenceResolver,
279
+ private readonly kinds: readonly number[],
280
+ ) {
281
+ super(options, referenceResolver);
282
+ for (let i = 0; i < kinds.length; i += 3) if (!this.byName.has(kinds[i]!)) this.byName.set(kinds[i]!, i);
283
+ }
284
+
285
+ getTypeReferenceSerializationKind(typeName: Node, location: Node | undefined): TypeReferenceSerializationKind {
286
+ const at = this.byName.get(typeName.id);
287
+ if (at !== undefined && this.kinds[at + 1] === (location === undefined ? -1 : location.id))
288
+ return this.kinds[at + 2]! as TypeReferenceSerializationKind;
289
+ this.missed = true;
290
+ return super.getTypeReferenceSerializationKind(typeName, location);
291
+ }
292
+ }
293
+
196
294
  /** A checked program and the emit of its files. */
197
295
  export class ProgramEmitter {
198
296
  private host: ProgramEmitHost | undefined = undefined;
@@ -354,16 +452,25 @@ export class PlainProgram extends CompilerProgram {
354
452
  const program = this.program;
355
453
  const files = program.getSourceFiles();
356
454
  const parallel = this.checked.emittedInParallel;
357
- // Checker threads emitted the files they checked (parallel-check.ts); the others are emitted here, in order.
455
+ // Checker threads emitted the files they checked (parallel-check.ts), or emit threads their JavaScript
456
+ // (parallel-emit.ts); the rest is emitted here, in order.
358
457
  const result =
359
458
  parallel === undefined
360
459
  ? this.emitter.emit(files, EmitOnly.All, writer)
361
460
  : combineEmitResults(
362
461
  files.map((file, i) => {
363
462
  const emitted = parallel[i];
364
- return emitted !== undefined
365
- ? writeHeldOutputs(emitted, writer)
366
- : this.emitter.emit([file], EmitOnly.All, writer);
463
+ // The declaration thread emitted the declarations of the files it could (parallel-emit.ts).
464
+ const ahead = this.checked.declarationsEmittedAhead;
465
+ const declarations = (): EmitResult => ahead?.[i] ?? this.emitter.emit([file], EmitOnly.Dts, writer);
466
+ if (emitted === undefined)
467
+ return ahead !== undefined
468
+ ? combineEmitResults([this.emitter.emit([file], EmitOnly.Js, writer), declarations()])
469
+ : this.emitter.emit([file], EmitOnly.All, writer);
470
+ const written = writeHeldOutputs(emitted, writer);
471
+ // Emit threads emit JavaScript only.
472
+ if (!this.checked.javaScriptEmittedOnThreads || !getEmitDeclarations(options)) return written;
473
+ return combineEmitResults([written, declarations()]);
367
474
  }),
368
475
  );
369
476
  if (options.noEmit === true) {
@@ -390,9 +497,15 @@ export function handleNoEmitOnError(program: CompilerProgram): EmitResult | unde
390
497
 
391
498
  /**
392
499
  * Config and syntax errors; without syntax errors, program, global and semantic errors too, and
393
- * declaration errors under `noEmit` (or `withDeclarationDiagnostics`).
500
+ * declaration errors under `noEmit` (or `withDeclarationDiagnostics`). With `declarationsLater`, the
501
+ * declaration errors are left to `program.emit`, which reports them under `noEmit` (without
502
+ * `noEmitOnError`) whether or not other errors were found.
394
503
  */
395
- export function getDiagnosticsOfProgram(program: CompilerProgram, withDeclarationDiagnostics: boolean): Diagnostic[] {
504
+ export function getDiagnosticsOfProgram(
505
+ program: CompilerProgram,
506
+ withDeclarationDiagnostics: boolean,
507
+ declarationsLater: boolean = false,
508
+ ): Diagnostic[] {
396
509
  const result: Diagnostic[] = [...program.getConfigFileParsingDiagnostics()];
397
510
  const configCount = result.length;
398
511
  for (const d of program.getSyntacticDiagnostics()) result.push(d);
@@ -414,6 +527,7 @@ export function getDiagnosticsOfProgram(program: CompilerProgram, withDeclaratio
414
527
  mark('semantic diagnostics');
415
528
  }
416
529
  if (
530
+ !declarationsLater &&
417
531
  (withDeclarationDiagnostics || options.noEmit === true) &&
418
532
  getEmitDeclarations(options) &&
419
533
  result.length === configCount
@@ -347,6 +347,11 @@ export class Diagnostic {
347
347
  readonly reportsDeprecated: boolean;
348
348
  skippedOnNoEmit = false;
349
349
  repopulateInfo: RepopulateInfo | undefined = undefined;
350
+ /**
351
+ * What `formatDiagnostic` gives for this diagnostic in `formattedReports.format`, where the checker
352
+ * thread that found it formatted it (`packResult`); `''` otherwise.
353
+ */
354
+ formatted = '';
350
355
 
351
356
  constructor(
352
357
  file: DiagnosticFile | undefined,
@@ -388,6 +393,7 @@ export class Diagnostic {
388
393
  result.relatedInformation = this.relatedInformation;
389
394
  result.skippedOnNoEmit = this.skippedOnNoEmit;
390
395
  result.repopulateInfo = this.repopulateInfo;
396
+ result.formatted = this.formatted;
391
397
  return result;
392
398
  }
393
399
  }
@@ -510,9 +516,13 @@ function compareRelatedInfo(a: readonly Diagnostic[], b: readonly Diagnostic[]):
510
516
  /** The order tsc reports diagnostics in: by file, position, code, arguments, then elaboration. */
511
517
  export function compareDiagnostics(a: Diagnostic, b: Diagnostic): number {
512
518
  if (a === b) return 0;
513
- let c = compareCodePoints(fileNameOf(a), fileNameOf(b));
514
- if (c !== 0) return c;
515
- c = a.pos - b.pos;
519
+ const c = compareCodePoints(fileNameOf(a), fileNameOf(b));
520
+ return c !== 0 ? c : compareDiagnosticsInFile(a, b);
521
+ }
522
+
523
+ /** `compareDiagnostics` of two diagnostics whose files have the same name. */
524
+ function compareDiagnosticsInFile(a: Diagnostic, b: Diagnostic): number {
525
+ let c = a.pos - b.pos;
516
526
  if (c !== 0) return c;
517
527
  c = a.end - b.end;
518
528
  if (c !== 0) return c;
@@ -527,6 +537,61 @@ export function compareDiagnostics(a: Diagnostic, b: Diagnostic): number {
527
537
  return compareRelatedInfo(a.relatedInformation, b.relatedInformation);
528
538
  }
529
539
 
540
+ /**
541
+ * `diagnostics` in the order of `compareDiagnostics`, equal ones in their order in `diagnostics`. File
542
+ * names are compared once per pair of files rather than once per pair of diagnostics: the distinct
543
+ * files are sorted by name, and each diagnostic sorts by its file's place in that order, then by the
544
+ * rest of `compareDiagnostics`. A program's diagnostics come a file at a time, so the files are found
545
+ * from the runs of one file.
546
+ */
547
+ export function sortDiagnostics(diagnostics: readonly Diagnostic[]): Diagnostic[] {
548
+ const n = diagnostics.length;
549
+ if (n < 2) return diagnostics.slice();
550
+ const fileIndices = new Map<DiagnosticFile | undefined, number>();
551
+ const files: (DiagnosticFile | undefined)[] = [];
552
+ const fileOf: number[] = [];
553
+ let last: DiagnosticFile | undefined = undefined;
554
+ let lastIndex = -1;
555
+ for (let i = 0; i < n; i++) {
556
+ const file = diagnostics[i]!.file;
557
+ if (lastIndex < 0 || file !== last) {
558
+ let k = fileIndices.get(file);
559
+ if (k === undefined) {
560
+ k = files.length;
561
+ fileIndices.set(file, k);
562
+ files.push(file);
563
+ }
564
+ last = file;
565
+ lastIndex = k;
566
+ }
567
+ fileOf.push(lastIndex);
568
+ }
569
+ // Files with the same name share a rank, as they compare equal.
570
+ const nameOf = (k: number): string => {
571
+ const file = files[k];
572
+ return file === undefined ? '' : file.fileName;
573
+ };
574
+ const byName: number[] = [];
575
+ for (let k = 0; k < files.length; k++) byName.push(k);
576
+ byName.sort((x, y) => compareCodePoints(nameOf(x), nameOf(y)));
577
+ const rank: number[] = [];
578
+ for (let k = 0; k < files.length; k++) rank.push(0);
579
+ for (let k = 1; k < byName.length; k++)
580
+ rank[byName[k]!] = rank[byName[k - 1]!]! + (nameOf(byName[k]!) === nameOf(byName[k - 1]!) ? 0 : 1);
581
+ const order: number[] = [];
582
+ for (let i = 0; i < n; i++) order.push(i);
583
+ order.sort((x, y) => {
584
+ const c = rank[fileOf[x]!]! - rank[fileOf[y]!]!;
585
+ if (c !== 0) return c;
586
+ const a = diagnostics[x]!;
587
+ const b = diagnostics[y]!;
588
+ return a === b ? 0 : compareDiagnosticsInFile(a, b);
589
+ });
590
+ const sorted: Diagnostic[] = [];
591
+ for (const i of order) sorted.push(diagnostics[i]!);
592
+ return sorted;
593
+ }
594
+
530
595
  function equalChain(a: readonly Diagnostic[], b: readonly Diagnostic[]): boolean {
531
596
  if (a.length !== b.length) return false;
532
597
  for (let i = 0; i < a.length; i++) {
@@ -625,13 +690,29 @@ function formatDiagnosticIn(diagnostic: Diagnostic, fileName: string, options: D
625
690
  return result + flattenDiagnosticMessage(diagnostic, options.newLine, options.locale) + options.newLine;
626
691
  }
627
692
 
693
+ /**
694
+ * The plain format the main thread reports a compilation's diagnostics in (`DiagnosticReporter.plainFormat`),
695
+ * which its checker threads receive a copy of and format the diagnostics they send back in
696
+ * (`Diagnostic.formatted`). Unset where diagnostics are reported with color and context, or not at all.
697
+ */
698
+ export class FormattedReports {
699
+ format: DiagnosticFormatOptions | undefined = undefined;
700
+ }
701
+
702
+ export const formattedReports = new FormattedReports();
703
+
628
704
  /** `formatDiagnostic` of each diagnostic. */
629
705
  export function formatDiagnostics(diagnostics: readonly Diagnostic[], options: DiagnosticFormatOptions): string {
630
706
  let result = '';
707
+ const formatted = options === formattedReports.format;
631
708
  // Sorted diagnostics come a file at a time: the relative name of the last file is kept.
632
709
  let lastFile: DiagnosticFile | undefined = undefined;
633
710
  let lastName = '';
634
711
  for (const diagnostic of diagnostics) {
712
+ if (formatted && diagnostic.formatted !== '') {
713
+ result += diagnostic.formatted;
714
+ continue;
715
+ }
635
716
  const file = diagnostic.file;
636
717
  if (file !== undefined && file !== lastFile) {
637
718
  lastFile = file;
@@ -256,6 +256,8 @@ class ParseTask {
256
256
  jsxRuntimeImport: JsxRuntimeImport | undefined = undefined;
257
257
  /** The task of the same file name that was loaded instead of this one. */
258
258
  loadedTask: ParseTask | undefined = undefined;
259
+ /** Whether an import or a type reference directive adds the file (`ProcessedFiles.addedFiles`). */
260
+ readonly byImport: boolean;
259
261
 
260
262
  constructor(
261
263
  readonly normalizedFilePath: string,
@@ -266,7 +268,10 @@ class ParseTask {
266
268
  /** A JavaScript file found in `node_modules`, loaded only within `maxNodeModuleJsDepth`. */
267
269
  readonly elideOnDepth: boolean,
268
270
  readonly packageId: PackageId | undefined,
269
- ) {}
271
+ ) {
272
+ const kind = includeReason !== undefined ? includeReason.kind : -1;
273
+ this.byImport = kind === FileIncludeKind.Import || kind === FileIncludeKind.TypeReferenceDirective;
274
+ }
270
275
 
271
276
  addSubTask(
272
277
  fileName: string,
@@ -296,12 +301,20 @@ class ParseTask {
296
301
  }
297
302
  }
298
303
 
304
+ const noData: readonly ParseTaskData[] = [];
305
+
299
306
  /** The tasks of one path, by file name casing. */
300
307
  class ParseTaskData {
301
308
  readonly tasks = new Map<string, ParseTask>();
302
309
  lowestDepth = Number.MAX_SAFE_INTEGER;
303
310
  startedSubTasks = false;
304
311
  packageId: PackageId | undefined = undefined;
312
+ /** The index of this path's file in the program's files, once collected; -1 for none. */
313
+ index = -1;
314
+ /** For a deduplicated package's path, the tasks of the copy kept (`collect`). */
315
+ keptCopy: ParseTaskData | undefined = undefined;
316
+ /** The paths the file's imports and type reference directives add, once collected. */
317
+ added: readonly ParseTaskData[] = noData;
305
318
 
306
319
  constructor(task: ParseTask) {
307
320
  this.tasks.set(task.normalizedFilePath, task);
@@ -419,6 +432,11 @@ export class ProcessedFiles {
419
432
  * threads (`preparedPackageJsonLookups`); `undefined` where they may miss some.
420
433
  */
421
434
  readonly preparedPackageJsons: readonly PackageJsonLookup[] | undefined,
435
+ /**
436
+ * The files each file's imports and type reference directives added to the program, by index into
437
+ * `files`, without the file itself.
438
+ */
439
+ readonly addedFiles: readonly (readonly number[])[],
422
440
  ) {}
423
441
  }
424
442
 
@@ -446,6 +464,8 @@ export function withReplacedFile(processed: ProcessedFiles, file: SourceFile): P
446
464
  processed.redirectFilesByPath,
447
465
  processed.projectReferenceMapper,
448
466
  processed.preparedPackageJsons,
467
+ // The file has the same references (`canReplaceFile`), and keeps its index.
468
+ processed.addedFiles,
449
469
  );
450
470
  }
451
471
 
@@ -1393,16 +1413,24 @@ export class FileLoader {
1393
1413
  const packageIdToSourceFile = new Map<string, SourceFile>();
1394
1414
  const checkCasing = this.options.forceConsistentCasingInFileNames !== false;
1395
1415
  const seen = new Map<ParseTaskData, string>();
1396
-
1397
- const collectFiles = (tasks: readonly ParseTask[]): void => {
1416
+ // The paths each file's imports and type reference directives add (the tasks of each path), for
1417
+ // `ProcessedFiles.addedFiles`: recorded while collecting visits them, as their files are only known
1418
+ // once every file is collected.
1419
+ const fileData: ParseTaskData[] = [];
1420
+ const libData = new Map<SourceFile, ParseTaskData>();
1421
+ const packageIdToData = new Map<string, ParseTaskData>();
1422
+
1423
+ const collectFiles = (tasks: readonly ParseTask[], added: ParseTaskData[] | undefined): void => {
1398
1424
  for (let task of tasks) {
1399
1425
  const includeReason = task.includeReason;
1426
+ const byImport = task.byImport;
1400
1427
  // Automatic type directive tasks are an implementation detail, not a reason.
1401
1428
  if (task.redirectedParseTask === undefined && !task.isForAutomaticTypeDirective) {
1402
1429
  if (task.loadedTask !== undefined) task = task.loadedTask;
1403
1430
  if (includeReason !== undefined) addIncludeReason(includeProcessor, task, includeReason);
1404
1431
  }
1405
1432
  const data = this.taskDataByPath.get(task.path);
1433
+ if (added !== undefined && data !== undefined && byImport) added.push(data);
1406
1434
  if (!task.loaded || data === undefined) continue;
1407
1435
 
1408
1436
  const checkedName = seen.get(data);
@@ -1457,13 +1485,18 @@ export class FileLoader {
1457
1485
  ),
1458
1486
  );
1459
1487
  filesByPath.set(task.path, packageIdFile);
1488
+ data.keptCopy = packageIdToData.get(key);
1460
1489
  if (data.lowestDepth > 0) sourceFilesFoundSearchingNodeModules.add(task.path);
1461
1490
  continue;
1462
1491
  }
1463
- if (file !== undefined) packageIdToSourceFile.set(key, file);
1492
+ if (file !== undefined) {
1493
+ packageIdToSourceFile.set(key, file);
1494
+ packageIdToData.set(key, data);
1495
+ }
1464
1496
  }
1465
1497
 
1466
- if (task.subTasks !== undefined && task.subTasks.length > 0) collectFiles(task.subTasks);
1498
+ let own: ParseTaskData[] | undefined = undefined;
1499
+ if (task.subTasks !== undefined && task.subTasks.length > 0) collectFiles(task.subTasks, (own = []));
1467
1500
 
1468
1501
  const redirected = task.redirectedParseTask;
1469
1502
  if (redirected !== undefined) {
@@ -1485,10 +1518,13 @@ export class FileLoader {
1485
1518
  }
1486
1519
  if (task.libFile !== undefined) {
1487
1520
  libFiles.push(file);
1521
+ libData.set(file, data);
1488
1522
  libFilesByPath.set(path, task.libFile);
1489
1523
  } else {
1490
1524
  files.push(file);
1525
+ fileData.push(data);
1491
1526
  }
1527
+ if (own !== undefined) data.added = own;
1492
1528
  filesByPath.set(path, file);
1493
1529
  resolvedModules.set(path, task.resolutionsInFile);
1494
1530
  typeResolutionsInFile.set(path, task.typeResolutionsInFile);
@@ -1500,13 +1536,28 @@ export class FileLoader {
1500
1536
  if (data.lowestDepth > 0) sourceFilesFoundSearchingNodeModules.add(path);
1501
1537
  }
1502
1538
  };
1503
- collectFiles(this.rootTasks);
1539
+ collectFiles(this.rootTasks, undefined);
1504
1540
 
1505
1541
  const priorities = new Map<SourceFile, number>();
1506
1542
  for (const file of libFiles) priorities.set(file, this.libFilePriority(file));
1507
1543
  libFiles.sort((a, b) => (priorities.get(a) ?? 0) - (priorities.get(b) ?? 0));
1508
1544
  const allFiles = [...libFiles, ...files];
1509
1545
  for (const redirect of redirectFilesByPath.values()) redirect.index += libFiles.length;
1546
+ // The files a file adds, without reading its resolutions again or looking up their paths.
1547
+ const dataOfFiles: ParseTaskData[] = [];
1548
+ for (const file of libFiles) dataOfFiles.push(libData.get(file)!);
1549
+ for (const data of fileData) dataOfFiles.push(data);
1550
+ for (let i = 0; i < dataOfFiles.length; i++) dataOfFiles[i]!.index = i;
1551
+ const addedFiles: number[][] = [];
1552
+ for (let i = 0; i < dataOfFiles.length; i++) {
1553
+ const added: number[] = [];
1554
+ for (const data of dataOfFiles[i]!.added) {
1555
+ const kept = data.keptCopy;
1556
+ const k = kept !== undefined ? kept.index : data.index;
1557
+ if (k >= 0 && k !== i) added.push(k);
1558
+ }
1559
+ addedFiles.push(added);
1560
+ }
1510
1561
 
1511
1562
  const keys = [...this.libResolutions.keys()].sort();
1512
1563
  for (const key of keys) {
@@ -1536,6 +1587,7 @@ export class FileLoader {
1536
1587
  redirectFilesByPath,
1537
1588
  this.mapper,
1538
1589
  this.tookPreparedResolutions ? preparedPackageJsonLookups() : noPackageJsonLookups,
1590
+ addedFiles,
1539
1591
  );
1540
1592
  }
1541
1593
  }
@@ -1864,8 +1864,21 @@ class EmitFilesHandler {
1864
1864
  const files = program.program.getSourceFiles();
1865
1865
  const emittedOnCheckers = this.emittedOnCheckers();
1866
1866
  const wrapped = this.wrap(writer);
1867
- const result =
1868
- emittedOnCheckers.size === 0
1867
+ const ahead = this.declarationsEmittedAhead();
1868
+ const result = program.checked.javaScriptEmittedOnThreads
1869
+ ? combineEmitResults(
1870
+ files.map((file) =>
1871
+ this.emitAfterEmitThreads(
1872
+ file,
1873
+ EmitOnly.All,
1874
+ emittedOnCheckers.get(file),
1875
+ ahead.get(file),
1876
+ writer,
1877
+ wrapped,
1878
+ ),
1879
+ ),
1880
+ )
1881
+ : emittedOnCheckers.size === 0
1869
1882
  ? program.emitter.emit(files, EmitOnly.All, wrapped)
1870
1883
  : combineEmitResults(
1871
1884
  files.map((file) => {
@@ -1901,20 +1914,76 @@ class EmitFilesHandler {
1901
1914
  /**
1902
1915
  * The emit results of the files checker threads emitted right after checking them (parallel-check.ts),
1903
1916
  * which are not emitted again here. Checker threads emit every file of a program without old build
1904
- * info, with the outputs its options select, as its pending emit asks.
1917
+ * info, with the outputs its options select, as its pending emit asks. Emit threads emit only the
1918
+ * JavaScript of the files (`javaScriptEmittedOnThreads`, `emitAfterEmitThreads`).
1905
1919
  */
1906
1920
  private emittedOnCheckers(): Map<SourceFile, EmitResult> {
1921
+ return this.byFile(this.program.checked.emittedInParallel);
1922
+ }
1923
+
1924
+ /** The declaration emit of the files the declaration thread emitted (parallel-emit.ts). */
1925
+ private declarationsEmittedAhead(): Map<SourceFile, EmitResult> {
1926
+ return this.byFile(this.program.checked.declarationsEmittedAhead);
1927
+ }
1928
+
1929
+ /** The results of `results`, by file index, by file. */
1930
+ private byFile(results: readonly (EmitResult | undefined)[] | undefined): Map<SourceFile, EmitResult> {
1907
1931
  const result = new Map<SourceFile, EmitResult>();
1908
- const emitted = this.program.checked.emittedInParallel;
1909
- if (emitted === undefined) return result;
1932
+ if (results === undefined) return result;
1910
1933
  const files = this.program.program.getSourceFiles();
1911
1934
  for (let i = 0; i < files.length; i++) {
1912
- const fileResult = emitted[i];
1935
+ const fileResult = results[i];
1913
1936
  if (fileResult !== undefined) result.set(files[i]!, fileResult);
1914
1937
  }
1915
1938
  return result;
1916
1939
  }
1917
1940
 
1941
+ /**
1942
+ * The emit of `file` with `emitOnly` where emit threads emitted the JavaScript of the program's files
1943
+ * while this thread checked it (`javaScriptEmittedOnThreads`, parallel-emit.ts): the outputs an emit thread
1944
+ * emitted of its JavaScript (`javaScript`) and the declaration thread of its declarations (`declarations`),
1945
+ * and the rest emitted here. A file no thread emitted is emitted here as without the threads.
1946
+ */
1947
+ private emitAfterEmitThreads(
1948
+ file: SourceFile,
1949
+ emitOnly: EmitOnly,
1950
+ javaScript: EmitResult | undefined,
1951
+ declarations: EmitResult | undefined,
1952
+ writer: OutputWriter,
1953
+ wrapped: OutputWriter,
1954
+ ): EmitResult {
1955
+ const program = this.program;
1956
+ if (wrapped instanceof SignatureRecordingWriter) wrapped.file = file;
1957
+ if (javaScript === undefined && declarations === undefined) return program.emitter.emit([file], emitOnly, wrapped);
1958
+ const results: EmitResult[] = [];
1959
+ if (emitOnly !== EmitOnly.Dts)
1960
+ results.push(
1961
+ javaScript !== undefined
1962
+ ? this.writeThreadOutputs(file, javaScript, writer)
1963
+ : program.emitter.emit([file], EmitOnly.Js, writer),
1964
+ );
1965
+ if (emitOnly !== EmitOnly.Js && getEmitDeclarations(program.snapshot.options))
1966
+ results.push(
1967
+ declarations !== undefined
1968
+ ? this.writeThreadOutputs(file, declarations, writer)
1969
+ : program.emitter.emit([file], EmitOnly.Dts, wrapped),
1970
+ );
1971
+ return combineEmitResults(results);
1972
+ }
1973
+
1974
+ /**
1975
+ * Writes the outputs a thread emitted for `file` with `writer`, as this thread would have written them:
1976
+ * its declaration signatures are recorded (`recordDeclarationSignatures`), and the unchanged declaration
1977
+ * files of a composite program are left unwritten.
1978
+ */
1979
+ private writeThreadOutputs(file: SourceFile, emitted: EmitResult, writer: OutputWriter): EmitResult {
1980
+ const skips = this.recordDeclarationSignatures(file, emitted.declarationSignatures);
1981
+ return writeHeldOutputs(
1982
+ emitted,
1983
+ skips === undefined ? writer : new HeldDeclarationWriter(writer, skips, this.program.host),
1984
+ );
1985
+ }
1986
+
1918
1987
  /** Records declaration signatures when declarations are emitted. */
1919
1988
  private wrap(writer: OutputWriter): OutputWriter {
1920
1989
  return getEmitDeclarations(this.program.snapshot.options) ? new SignatureRecordingWriter(this, writer) : writer;
@@ -1926,6 +1995,7 @@ class EmitFilesHandler {
1926
1995
  collectAllAffectedFiles(program);
1927
1996
  const wrapped = this.wrap(writer);
1928
1997
  const emittedOnCheckers = this.emittedOnCheckers();
1998
+ const ahead = this.declarationsEmittedAhead();
1929
1999
  for (const [path, emitKind] of snapshot.affectedFilesPendingEmit) {
1930
2000
  const affectedFile = program.program.getSourceFileByPath(path);
1931
2001
  if (affectedFile === undefined || !program.program.sourceFileMayBeEmitted(affectedFile, false)) {
@@ -1939,13 +2009,10 @@ class EmitFilesHandler {
1939
2009
  if (!this.isForDtsErrors) {
1940
2010
  const emitOnly = emitOnlyOfPendingKind(pendingKind);
1941
2011
  const emitted = emittedOnCheckers.get(affectedFile);
1942
- if (emitted !== undefined) {
1943
- const skips = this.recordDeclarationSignatures(affectedFile, emitted.declarationSignatures);
1944
- result = writeHeldOutputs(
1945
- emitted,
1946
- skips === undefined ? writer : new HeldDeclarationWriter(writer, skips, program.host),
1947
- );
1948
- } else {
2012
+ if (program.checked.javaScriptEmittedOnThreads)
2013
+ result = this.emitAfterEmitThreads(affectedFile, emitOnly, emitted, ahead.get(affectedFile), writer, wrapped);
2014
+ else if (emitted !== undefined) result = this.writeThreadOutputs(affectedFile, emitted, writer);
2015
+ else {
1949
2016
  if (wrapped instanceof SignatureRecordingWriter) wrapped.file = affectedFile;
1950
2017
  result = program.emitter.emit([affectedFile], emitOnly, wrapped);
1951
2018
  }