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
@@ -226,6 +226,13 @@ const maxChunk = 16;
226
226
  /** Files from this size (bytes) on take a parse thread 25 ms or more, and go out ahead of others. */
227
227
  const largeFile = 256 * 1024;
228
228
 
229
+ /**
230
+ * The most text (bytes) a chunk of other files holds, about one large file's: a chunk ends with the file
231
+ * that reaches it. Sixteen of tsc-ts's own compiler sources, 50-200 KB each, made one chunk that kept its
232
+ * thread busy for 30-45 ms after the others were done.
233
+ */
234
+ const maxChunkBytes = largeFile;
235
+
229
236
  /**
230
237
  * The files to hand out to parse threads, each queued once. Library files go out first, one per
231
238
  * chunk, also those found later through `/// <reference lib>`: a large one (`lib.dom.d.ts`) takes a
@@ -233,7 +240,7 @@ const largeFile = 256 * 1024;
233
240
  * Then, for the same reason, large files, one per chunk: root files (TypeScript's 3 MB `checker.ts`,
234
241
  * which in a chunk of root files kept one thread busy after the others were done) and the files the
235
242
  * parse threads found (`csstype`'s 0.9 MB of declarations, found through React's). The other files go
236
- * out in program order, in chunks of up to `maxChunk`.
243
+ * out in program order, in chunks of up to `maxChunk` files and about `maxChunkBytes` of text.
237
244
  */
238
245
  export class ParseQueue {
239
246
  private readonly libraries = new LoadJobs();
@@ -281,7 +288,8 @@ export class ParseQueue {
281
288
 
282
289
  /**
283
290
  * The next chunk for one of `threads` parse threads, or `undefined` when nothing is left. With
284
- * `full`, other files only go out `maxChunk` at a time, and stay queued until there are as many.
291
+ * `full`, other files only go out in full chunks, `maxChunk` files or `maxChunkBytes` of text, and stay
292
+ * queued until there are as many.
285
293
  */
286
294
  next(threads: number, full = false): LoadMessage | undefined {
287
295
  const libraries = this.libraries;
@@ -306,10 +314,13 @@ export class ParseQueue {
306
314
  }
307
315
  const queue = this.others;
308
316
  const left = queue.names.length - this.head;
309
- if (left <= 0 || (full && left < maxChunk)) return undefined;
310
- const size = full ? maxChunk : Math.min(maxChunk, Math.max(1, Math.floor(left / threads)));
317
+ if (left <= 0) return undefined;
318
+ const most = full ? maxChunk : Math.min(maxChunk, Math.max(1, Math.floor(left / threads)));
311
319
  const start = this.head;
312
- const end = start + size;
320
+ let end = start;
321
+ let bytes = 0;
322
+ while (end < queue.names.length && end - start < most && bytes < maxChunkBytes) bytes += queue.sizes[end++]!;
323
+ if (full && end - start < maxChunk && bytes < maxChunkBytes) return undefined;
313
324
  this.head = end;
314
325
  return {
315
326
  stop: false,
@@ -49,6 +49,9 @@ class PreparingHost extends CompilerHost {
49
49
  );
50
50
  const t2 = timeline ? performance.now() : 0;
51
51
  bindSourceFile(file);
52
+ // Published now, while its nodes are in cache: the chunk's publish (`ParseThread.load`) then skips
53
+ // them, where walking the whole chunk's files at once reads each node from memory again.
54
+ publish(file);
52
55
  if (timeline) {
53
56
  const t3 = performance.now();
54
57
  this.readMs += t1 - t0;
@@ -287,6 +287,11 @@ export class EmitContext extends NodeFactory {
287
287
 
288
288
  // Emit data
289
289
 
290
+ /** Whether `node` has emit data: flags, ranges, helpers or synthesized comments. */
291
+ hasEmitNode(node: Node): boolean {
292
+ return this.lookup(node) !== undefined;
293
+ }
294
+
290
295
  getEmitFlags(node: Node): EmitFlags {
291
296
  const emitNode = this.lookup(node);
292
297
  return emitNode === undefined ? EmitFlags.None : emitNode.emitFlags;
@@ -174,7 +174,7 @@ import { SyntaxKind } from '../syntax-kind.js';
174
174
  import { type CommentRange, TokenFlags } from '../types.js';
175
175
  import { ScriptTarget } from '../options.js';
176
176
  import { fileExtensionIs } from '../path.js';
177
- import { isWhiteSpaceLike } from '../character-codes.js';
177
+ import { CharacterCodes, isWhiteSpaceLike } from '../character-codes.js';
178
178
  import type { SourceMapGenerator } from '../sourcemap.js';
179
179
  import { EmitContext, compareEmitHelpers, type SynthesizedComment } from './emit-context.js';
180
180
  import { EmitFlags, GeneratedIdentifierFlags } from './emit-flags.js';
@@ -399,6 +399,9 @@ const enum State {
399
399
  SourceMaps = 1 << 1,
400
400
  }
401
401
 
402
+ /** The saved emit flags of a node whose comments were not looked for (`Printer.inCommentFreeRange`). */
403
+ const commentFreeNode = -1;
404
+
402
405
  export class Printer extends NameGeneratorHost {
403
406
  readonly options: PrinterOptions;
404
407
  readonly context: EmitContext;
@@ -410,6 +413,13 @@ export class Printer extends NameGeneratorHost {
410
413
  currentSourceFile: SourceFile | undefined;
411
414
  private sourceText: string;
412
415
  private lineStarts: readonly number[];
416
+ /**
417
+ * The line of the last `lineOf` lookup, `lookupLine`, and the positions it spans, `[lookupLineStart, lookupLineEnd)`,
418
+ * with `lookupLineEnd` -1 after the last line: printing reads positions mostly in order, so most lookups fall on that line.
419
+ */
420
+ private lookupLine: number;
421
+ private lookupLineStart: number;
422
+ private lookupLineEnd: number;
413
423
  private writer: TextWriter;
414
424
  private ownWriter: TextWriter | undefined;
415
425
  private uniqueHelperNames: Map<string, Identifier> | undefined;
@@ -423,8 +433,25 @@ export class Printer extends NameGeneratorHost {
423
433
  private commentsDisabled: boolean;
424
434
  /** Emitting the `extends` clause of a conditional or infer type. */
425
435
  private inExtends: boolean;
426
- /** Saved comment state: emit flags, comment pos, comment end, container pos, container end, declaration list end. */
427
- private readonly commentStack: number[];
436
+ /**
437
+ * Saved comment state, 32-bit values: emit flags, comment pos, comment end, container pos, container end,
438
+ * declaration list end.
439
+ * Flags are `commentFreeNode` for a node that `emitCommentsBeforeNode` handled in a comment-free range.
440
+ */
441
+ private commentStack: Int32Array;
442
+ /** The used length of `commentStack`: entries are written in place and kept for reuse once popped. */
443
+ private commentDepth: number;
444
+ /** Start positions of `//` and `/*` in `sourceText`, ascending; made on first use for each file. */
445
+ private commentStarts: Int32Array | undefined;
446
+ /**
447
+ * A range of `sourceText`, `[freeStart, freeEnd)`, where no comment starts and the last character is not trivia,
448
+ * so no comment is found from any position in it; -1 when there is none. `freeOwner` is the node it was made for.
449
+ */
450
+ private freeStart: number;
451
+ private freeEnd: number;
452
+ /** Whether no trailing comment follows `freeEnd`. */
453
+ private freeEndTrailingNone: boolean;
454
+ private freeOwner: Node | undefined;
428
455
  /** Saved source map state: emit flags, pos, end, has token range. */
429
456
  private readonly sourceMapStack: number[];
430
457
  /** The token position after `enterToken` skipped trivia. */
@@ -457,6 +484,9 @@ export class Printer extends NameGeneratorHost {
457
484
  this.currentSourceFile = undefined;
458
485
  this.sourceText = '';
459
486
  this.lineStarts = [];
487
+ this.lookupLine = -1;
488
+ this.lookupLineStart = 0;
489
+ this.lookupLineEnd = 0;
460
490
  this.writer = new TextWriter(options.newLine, 4);
461
491
  this.ownWriter = undefined;
462
492
  this.uniqueHelperNames = undefined;
@@ -468,7 +498,13 @@ export class Printer extends NameGeneratorHost {
468
498
  this.detachedComments = [];
469
499
  this.commentsDisabled = options.removeComments;
470
500
  this.inExtends = false;
471
- this.commentStack = [];
501
+ this.commentStack = new Int32Array(96);
502
+ this.commentDepth = 0;
503
+ this.commentStarts = undefined;
504
+ this.freeStart = -1;
505
+ this.freeEnd = -1;
506
+ this.freeEndTrailingNone = false;
507
+ this.freeOwner = undefined;
472
508
  this.sourceMapStack = [];
473
509
  this.tokenPos = 0;
474
510
  this.sourceMapsDisabled = true;
@@ -518,6 +554,11 @@ export class Printer extends NameGeneratorHost {
518
554
  this.currentSourceFile = sourceFile;
519
555
  this.sourceText = sourceFile === undefined ? '' : sourceFile.text;
520
556
  this.lineStarts = sourceFile === undefined ? [] : sourceFile.lineStarts();
557
+ this.lookupLine = -1;
558
+ this.lookupLineStart = 0;
559
+ this.lookupLineEnd = 0;
560
+ this.commentStarts = undefined;
561
+ this.endCommentFreeRange();
521
562
  return true;
522
563
  }
523
564
 
@@ -615,6 +656,8 @@ export class Printer extends NameGeneratorHost {
615
656
  case SyntaxKind.Identifier:
616
657
  case SyntaxKind.PrivateIdentifier:
617
658
  case SyntaxKind.JsxNamespacedName:
659
+ // Its text either way: the source text, or the text of a node from elsewhere.
660
+ if (this.textIsSourceText(node)) return (node as Identifier | PrivateIdentifier).text;
618
661
  if (!canUseSourceFile || !this.isInCurrentSourceFile(node)) {
619
662
  if (node instanceof Identifier) return node.text;
620
663
  if (node instanceof PrivateIdentifier) return node.text;
@@ -635,6 +678,38 @@ export class Printer extends NameGeneratorHost {
635
678
  return getSourceTextOfNode(this.sourceText, node);
636
679
  }
637
680
 
681
+ /**
682
+ * Whether the source text of an identifier or private identifier of the current file, from the end of its leading
683
+ * trivia to its end, is its text: the text ends the node, and the character before it is not part of the token, as
684
+ * it would be in an identifier written with escapes. Nodes from JSDoc may span other text, so they are left to the
685
+ * general path.
686
+ */
687
+ private textIsSourceText(node: Node): boolean {
688
+ if (!(node instanceof Identifier) && !(node instanceof PrivateIdentifier)) return false;
689
+ if ((node.flags & (NodeFlags.Reparsed | NodeFlags.JSDoc | NodeFlags.ReparserTransformedLiteral)) !== 0)
690
+ return false;
691
+ const text = node.text;
692
+ const source = this.sourceText;
693
+ const end = node.end;
694
+ const start = end - text.length;
695
+ if (text.length === 0 || start < node.pos || end > source.length) return false;
696
+ if (!source.startsWith(text, start)) return false;
697
+ if (start === node.pos) return true;
698
+ const before = source.charCodeAt(start - 1);
699
+ return !(
700
+ (before >= CharacterCodes.a && before <= CharacterCodes.z) ||
701
+ (before >= CharacterCodes.A && before <= CharacterCodes.Z) ||
702
+ (before >= CharacterCodes._0 && before <= CharacterCodes._9) ||
703
+ before === CharacterCodes._ ||
704
+ before === CharacterCodes.$ ||
705
+ before === CharacterCodes.backslash ||
706
+ before === CharacterCodes.openBrace ||
707
+ before === CharacterCodes.closeBrace ||
708
+ before === CharacterCodes.hash ||
709
+ before > CharacterCodes.maxAsciiCharacter
710
+ );
711
+ }
712
+
638
713
  private isInCurrentSourceFile(node: Node): boolean {
639
714
  const file = this.currentSourceFile;
640
715
  if (file === undefined) return false;
@@ -717,7 +792,28 @@ export class Printer extends NameGeneratorHost {
717
792
  // Lines between nodes
718
793
 
719
794
  private positionsAreOnSameLine(pos1: number, pos2: number): boolean {
720
- return getLinesBetweenPositions(this.lineStarts, pos1, pos2) === 0;
795
+ if (pos1 === pos2) return true;
796
+ const lower = pos1 < pos2 ? pos1 : pos2;
797
+ const upper = pos1 < pos2 ? pos2 : pos1;
798
+ // What getLinesBetweenPositions finds: no line starts in (lower, upper].
799
+ if (lower < 0) {
800
+ const first = this.lineStarts.length > 0 ? this.lineStarts[0] | 0 : -1;
801
+ return first < 0 || upper < first;
802
+ }
803
+ this.lineOf(lower);
804
+ return this.lookupLineEnd < 0 || upper < this.lookupLineEnd;
805
+ }
806
+
807
+ /** The zero-based line of `pos` (-1 before the first line), which becomes the line of the last lookup. */
808
+ private lineOf(pos: number): number {
809
+ if (pos >= this.lookupLineStart && (pos < this.lookupLineEnd || this.lookupLineEnd < 0) && this.lookupLine >= 0)
810
+ return this.lookupLine;
811
+ const starts = this.lineStarts;
812
+ const line = computeLineOfPosition(starts, pos);
813
+ this.lookupLine = line;
814
+ this.lookupLineStart = line >= 0 ? starts[line] | 0 : 0;
815
+ this.lookupLineEnd = line + 1 < starts.length ? starts[line + 1] | 0 : -1;
816
+ return line;
721
817
  }
722
818
 
723
819
  private rangeStart(pos: number): number {
@@ -1049,8 +1145,13 @@ export class Printer extends NameGeneratorHost {
1049
1145
 
1050
1146
  // Names
1051
1147
 
1052
- // Source text of names reparsed from JSDoc can span lines.
1148
+ // Source text of names reparsed from JSDoc can span lines; an identifier's own source text has no line breaks.
1053
1149
  private emitIdentifierText(node: Identifier): boolean {
1150
+ if (!this.context.autoGenerate.has(node) && this.textIsSourceText(node)) {
1151
+ // writeText's answer for text without line breaks.
1152
+ this.writer.write(node.text);
1153
+ return false;
1154
+ }
1054
1155
  return this.writer.writeText(this.getTextOfNode(node));
1055
1156
  }
1056
1157
 
@@ -4350,10 +4451,18 @@ export class Printer extends NameGeneratorHost {
4350
4451
 
4351
4452
  private emitCommentsBeforeNode(node: Node): boolean {
4352
4453
  if (!this.shouldEmitComments(node)) return false;
4454
+ if (this.inCommentFreeRange(node)) {
4455
+ // What the general path does for a node without emit data and without comments.
4456
+ this.pushCommentState(commentFreeNode, node.pos, node.end);
4457
+ this.containerPos = node.pos;
4458
+ this.containerEnd = node.end;
4459
+ if (node.kind === SyntaxKind.VariableDeclarationList) this.declarationListContainerEnd = node.end;
4460
+ return true;
4461
+ }
4353
4462
  const flags = this.context.getEmitFlags(node);
4354
4463
  const pos = this.context.commentPos(node);
4355
4464
  const end = this.context.commentEnd(node);
4356
- this.commentStack.push(flags, pos, end, this.containerPos, this.containerEnd, this.declarationListContainerEnd);
4465
+ this.pushCommentState(flags, pos, end);
4357
4466
  this.emitLeadingCommentsOfNode(node, flags, pos, end);
4358
4467
  if ((flags & EmitFlags.NoLeadingComments) === 0) {
4359
4468
  const synthesized = this.context.getSyntheticLeadingComments(node);
@@ -4363,17 +4472,43 @@ export class Printer extends NameGeneratorHost {
4363
4472
  return true;
4364
4473
  }
4365
4474
 
4475
+ /** Saves the comment state of a node: its emit flags (or `commentFreeNode`), comment range and the containers. */
4476
+ private pushCommentState(flags: number, pos: number, end: number): boolean {
4477
+ let stack = this.commentStack;
4478
+ const at = this.commentDepth;
4479
+ if (at + 6 > stack.length) {
4480
+ const grown = new Int32Array(stack.length * 2);
4481
+ grown.set(stack);
4482
+ this.commentStack = grown;
4483
+ stack = grown;
4484
+ }
4485
+ stack[at] = flags;
4486
+ stack[at + 1] = pos;
4487
+ stack[at + 2] = end;
4488
+ stack[at + 3] = this.containerPos;
4489
+ stack[at + 4] = this.containerEnd;
4490
+ stack[at + 5] = this.declarationListContainerEnd;
4491
+ this.commentDepth = at + 6;
4492
+ return true;
4493
+ }
4494
+
4366
4495
  private emitCommentsAfterNode(node: Node): boolean {
4367
4496
  const stack = this.commentStack;
4368
- const base = stack.length - 6;
4369
- // `| 0` keeps scriptc from typing array reads as possibly undefined.
4370
- const flags = stack[base] | 0;
4371
- const pos = stack[base + 1] | 0;
4372
- const end = stack[base + 2] | 0;
4373
- const containerPos = stack[base + 3] | 0;
4374
- const containerEnd = stack[base + 4] | 0;
4375
- const declarationListContainerEnd = stack[base + 5] | 0;
4376
- stack.length = base;
4497
+ const base = this.commentDepth - 6;
4498
+ const flags = stack[base]!;
4499
+ const pos = stack[base + 1]!;
4500
+ const end = stack[base + 2]!;
4501
+ const containerPos = stack[base + 3]!;
4502
+ const containerEnd = stack[base + 4]!;
4503
+ const declarationListContainerEnd = stack[base + 5]!;
4504
+ this.commentDepth = base;
4505
+ if (node === this.freeOwner) this.endCommentFreeRange();
4506
+ if (flags === commentFreeNode) {
4507
+ this.containerPos = containerPos;
4508
+ this.containerEnd = containerEnd;
4509
+ this.declarationListContainerEnd = declarationListContainerEnd;
4510
+ return true;
4511
+ }
4377
4512
  if ((flags & EmitFlags.NoNestedComments) !== 0) this.commentsDisabled = false;
4378
4513
  if ((flags & EmitFlags.NoTrailingComments) === 0) {
4379
4514
  const synthesized = this.context.getSyntheticTrailingComments(node);
@@ -4395,6 +4530,83 @@ export class Printer extends NameGeneratorHost {
4395
4530
  return true;
4396
4531
  }
4397
4532
 
4533
+ /**
4534
+ * Whether `node` has no emit data and lies in a range of the source text where no comment is found, so that its
4535
+ * comments need not be looked for. Outside such a range, a node whose own range is one starts it (`freeOwner`).
4536
+ */
4537
+ private inCommentFreeRange(node: Node): boolean {
4538
+ const pos = node.pos;
4539
+ const end = node.end;
4540
+ if (pos < 0 || end <= pos || node.kind === SyntaxKind.JsxText || node.kind === SyntaxKind.NotEmittedStatement)
4541
+ return false;
4542
+ if (this.freeEnd < 0) {
4543
+ if (!this.startCommentFreeRange(pos, end)) return false;
4544
+ this.freeOwner = node;
4545
+ } else if (pos < this.freeStart) return false;
4546
+ if (this.context.hasEmitNode(node)) return false;
4547
+ return end < this.freeEnd || (end === this.freeEnd && this.freeEndTrailingNone);
4548
+ }
4549
+
4550
+ private endCommentFreeRange(): boolean {
4551
+ this.freeStart = -1;
4552
+ this.freeEnd = -1;
4553
+ this.freeEndTrailingNone = false;
4554
+ this.freeOwner = undefined;
4555
+ return true;
4556
+ }
4557
+
4558
+ /** Whether the comment-free range shows that no trailing comment follows `pos`. */
4559
+ private noCommentsAfter(pos: number): boolean {
4560
+ return pos >= this.freeStart && (pos < this.freeEnd || (pos === this.freeEnd && this.freeEndTrailingNone));
4561
+ }
4562
+
4563
+ /** Makes `[pos, end)` the comment-free range if no comment starts in it and its last character is not trivia. */
4564
+ private startCommentFreeRange(pos: number, end: number): boolean {
4565
+ const text = this.sourceText;
4566
+ if (end > text.length) return false;
4567
+ const last = text.charCodeAt(end - 1);
4568
+ if (
4569
+ last === CharacterCodes.space ||
4570
+ last === CharacterCodes.tab ||
4571
+ last === CharacterCodes.lineFeed ||
4572
+ last === CharacterCodes.carriageReturn ||
4573
+ last === CharacterCodes.verticalTab ||
4574
+ last === CharacterCodes.formFeed ||
4575
+ last > CharacterCodes.maxAsciiCharacter
4576
+ )
4577
+ return false;
4578
+ const starts = this.commentStartsOfFile();
4579
+ // The first comment start at or after `pos`.
4580
+ let low = 0;
4581
+ let high = starts.length;
4582
+ while (low < high) {
4583
+ const middle = (low + high) >>> 1;
4584
+ if (starts[middle]! < pos) low = middle + 1;
4585
+ else high = middle;
4586
+ }
4587
+ if (low < starts.length && starts[low]! < end) return false;
4588
+ this.freeStart = pos;
4589
+ this.freeEnd = end;
4590
+ this.freeEndTrailingNone = getTrailingCommentRanges(text, end) === undefined;
4591
+ return true;
4592
+ }
4593
+
4594
+ private commentStartsOfFile(): Int32Array {
4595
+ const cached = this.commentStarts;
4596
+ if (cached !== undefined) return cached;
4597
+ const text = this.sourceText;
4598
+ const length = text.length;
4599
+ const list: number[] = [];
4600
+ for (let i = text.indexOf('/'); i >= 0 && i + 1 < length; i = text.indexOf('/', i + 1)) {
4601
+ const next = text.charCodeAt(i + 1);
4602
+ if (next === CharacterCodes.slash || next === CharacterCodes.asterisk) list.push(i);
4603
+ }
4604
+ const starts = new Int32Array(list.length);
4605
+ for (let i = 0; i < list.length; i++) starts[i] = list[i]!;
4606
+ this.commentStarts = starts;
4607
+ return starts;
4608
+ }
4609
+
4398
4610
  /** Emits comments before a token; returns whether `emitCommentsAfterToken` applies, with the token's position in `tokenPos`. */
4399
4611
  private emitCommentsBeforeToken(pos: number, contextNode: Node, flags: TokenEmitFlags): boolean {
4400
4612
  if ((flags & TokenEmitFlags.NoComments) !== 0 || this.commentsDisabled) {
@@ -4505,6 +4717,7 @@ export class Printer extends NameGeneratorHost {
4505
4717
  private emitLeadingComments(pos: number, elided: boolean): boolean {
4506
4718
  if (this.commentsDisabled || this.currentSourceFile === undefined || pos < 0 || pos === this.containerPos)
4507
4719
  return false;
4720
+ if (pos >= this.freeStart && pos < this.freeEnd) return false;
4508
4721
  // 1: only triple-slash comments, 2: all but triple-slash comments, 0: all.
4509
4722
  let tripleSlash = 0;
4510
4723
  if (!elided) {
@@ -4552,6 +4765,7 @@ export class Printer extends NameGeneratorHost {
4552
4765
  (this.containerEnd !== -1 && (pos === this.containerEnd || pos === this.declarationListContainerEnd))
4553
4766
  )
4554
4767
  return false;
4768
+ if (this.noCommentsAfter(pos)) return false;
4555
4769
  const ranges = getTrailingCommentRanges(this.sourceText, pos);
4556
4770
  if (ranges === undefined) return false;
4557
4771
  let comments: CommentRange[] | undefined = undefined;
@@ -4567,6 +4781,7 @@ export class Printer extends NameGeneratorHost {
4567
4781
  if (this.commentsDisabled || this.currentSourceFile === undefined) return false;
4568
4782
  if (this.containerEnd !== -1 && (pos === this.containerEnd || pos === this.declarationListContainerEnd))
4569
4783
  return false;
4784
+ if (this.noCommentsAfter(pos)) return false;
4570
4785
  const comments = getTrailingCommentRanges(this.sourceText, pos);
4571
4786
  if (comments === undefined) return false;
4572
4787
  for (const comment of comments) {
@@ -276,6 +276,10 @@ export class CheckedProgram {
276
276
  * emit. Set once every file is checked, when checker threads emit (parallel-check.ts).
277
277
  */
278
278
  emittedInParallel: (EmitResult | undefined)[] | undefined = undefined;
279
+ /** Emit threads emitted only the JavaScript of `emittedInParallel`: declarations are emitted here. */
280
+ javaScriptEmittedOnThreads = false;
281
+ /** The declaration emit of each file on the declaration thread (parallel-emit.ts); undefined for one it didn't emit. */
282
+ declarationsEmittedAhead: (EmitResult | undefined)[] | undefined = undefined;
279
283
  /**
280
284
  * The global diagnostics of this thread's checker before it emitted files while checker threads
281
285
  * checked (`emitUncheckedFiles`), reported instead of its current ones.
@@ -27,6 +27,7 @@ import {
27
27
  equalDiagnosticsNoRelatedInfo,
28
28
  type DiagnosticMessage,
29
29
  type Locale,
30
+ sortDiagnostics,
30
31
  useThreadTextCaches,
31
32
  } from './diagnostics.js';
32
33
  import {
@@ -455,30 +456,9 @@ export class Program implements OutputPathsHost {
455
456
  return this.processed.resolvedModules.get(file.path)?.get(modeAwareKey(moduleName, mode));
456
457
  }
457
458
 
458
- /** The program files that the imports and type reference directives of `file` resolved to. */
459
- /**
460
- * The files `file` imports and references through type directives. `byFileName`, the program's
461
- * files by file name, finds a file without computing its path when it is resolved by that name.
462
- */
463
- getResolvedFilesOf(file: SourceFile, byFileName?: ReadonlyMap<string, SourceFile>): SourceFile[] {
464
- const result: SourceFile[] = [];
465
- const modules = this.processed.resolvedModules.get(file.path);
466
- if (modules !== undefined)
467
- for (const resolved of modules.values()) {
468
- const name = resolved.resolvedFileName;
469
- if (name === '') continue;
470
- const target = byFileName?.get(name) ?? this.getSourceFileForResolvedModule(name);
471
- if (target !== undefined) result.push(target);
472
- }
473
- const types = this.processed.typeResolutionsInFile.get(file.path);
474
- if (types !== undefined)
475
- for (const resolved of types.values()) {
476
- const name = resolved.resolvedFileName;
477
- if (name === '') continue;
478
- const target = byFileName?.get(name) ?? this.getSourceFile(name);
479
- if (target !== undefined) result.push(target);
480
- }
481
- return result;
459
+ /** The files each file added to the program (`ProcessedFiles.addedFiles`), as indices of `getSourceFiles`. */
460
+ addedFiles(): readonly (readonly number[])[] {
461
+ return this.processed.addedFiles;
482
462
  }
483
463
 
484
464
  /** The specifier of the implicit JSX runtime import of `file`, if any. */
@@ -1398,7 +1378,7 @@ class NodeCollector extends NodeVisitor {
1398
1378
 
1399
1379
  /** Sorts diagnostics and merges duplicates, combining their related information. */
1400
1380
  export function sortAndDeduplicateDiagnostics(diagnostics: readonly Diagnostic[]): Diagnostic[] {
1401
- const sorted = [...diagnostics].sort(compareDiagnostics);
1381
+ const sorted = sortDiagnostics(diagnostics);
1402
1382
  if (sorted.length < 2) return sorted;
1403
1383
  const result: Diagnostic[] = [];
1404
1384
  let i = 0;
@@ -517,6 +517,13 @@ export function isTypeRelatedTo(c: Checker, source: Type, target: Type, relation
517
517
  // Types that can't simplify must have identical flags.
518
518
  if (source.flags !== target.flags) return false;
519
519
  if ((source.flags & TypeFlags.Singleton) !== 0) return true;
520
+ } else {
521
+ // What the relater decides first for types that may simplify (Relater.isRelatedToEx): their normal forms are
522
+ // one type, or can be identical only with identical flags.
523
+ const s = getNormalizedType(c, source, false);
524
+ const t = getNormalizedType(c, target, true);
525
+ if (s === t) return true;
526
+ if (s.flags !== t.flags) return false;
520
527
  }
521
528
  if ((source.flags & TypeFlags.Object) !== 0 && (target.flags & TypeFlags.Object) !== 0) {
522
529
  const related = relation.get(
@@ -156,12 +156,17 @@ export class SymbolRareFields implements ProgramLifetime {
156
156
  valueDeclaration: Node | undefined = undefined;
157
157
  }
158
158
 
159
- /** In `Symbol.bits`, above the check flags: the value declaration is the first declaration. */
160
- const valueDeclarationIsFirst = 1 << 30;
159
+ /**
160
+ * In `Symbol.bits`, above the check flags: the value declaration is the first declaration. A const enum
161
+ * member, so that native builds read it as a constant rather than as a module variable.
162
+ */
163
+ const enum SymbolBits {
164
+ ValueDeclarationIsFirst = 1 << 30,
165
+ }
161
166
 
162
167
  export class Symbol implements ProgramLifetime {
163
168
  flags: SymbolFlags;
164
- /** The check flags, and `valueDeclarationIsFirst`. */
169
+ /** The check flags, and `SymbolBits.ValueDeclarationIsFirst`. */
165
170
  private bits: number;
166
171
  readonly name: string;
167
172
  /**
@@ -186,19 +191,19 @@ export class Symbol implements ProgramLifetime {
186
191
  }
187
192
 
188
193
  get checkFlags(): CheckFlags {
189
- return (this.bits & ~valueDeclarationIsFirst) as CheckFlags;
194
+ return (this.bits & ~SymbolBits.ValueDeclarationIsFirst) as CheckFlags;
190
195
  }
191
196
 
192
197
  set checkFlags(flags: CheckFlags) {
193
- this.bits = (this.bits & valueDeclarationIsFirst) | flags;
198
+ this.bits = (this.bits & SymbolBits.ValueDeclarationIsFirst) | flags;
194
199
  }
195
200
 
196
201
  /** Makes `node` the first declaration, keeping the value declaration. */
197
202
  setFirst(node: Node | undefined): void {
198
- if ((this.bits & valueDeclarationIsFirst) !== 0 && node !== this.declaration) {
203
+ if ((this.bits & SymbolBits.ValueDeclarationIsFirst) !== 0 && node !== this.declaration) {
199
204
  // The value declaration stays what it was.
200
205
  this.rareFieldsOrNew().valueDeclaration = this.declaration;
201
- this.bits &= ~valueDeclarationIsFirst;
206
+ this.bits &= ~SymbolBits.ValueDeclarationIsFirst;
202
207
  }
203
208
  this.declaration = node;
204
209
  }
@@ -208,7 +213,7 @@ export class Symbol implements ProgramLifetime {
208
213
  * says; any other is kept in the rare fields.
209
214
  */
210
215
  get valueDeclaration(): Node | undefined {
211
- if ((this.bits & valueDeclarationIsFirst) !== 0) return this.declaration;
216
+ if ((this.bits & SymbolBits.ValueDeclarationIsFirst) !== 0) return this.declaration;
212
217
  const group = this.rareFields;
213
218
  return group === undefined ? undefined : group.valueDeclaration;
214
219
  }
@@ -216,11 +221,11 @@ export class Symbol implements ProgramLifetime {
216
221
  set valueDeclaration(node: Node | undefined) {
217
222
  const group = this.rareFields;
218
223
  if (node !== undefined && node === this.declaration) {
219
- this.bits |= valueDeclarationIsFirst;
224
+ this.bits |= SymbolBits.ValueDeclarationIsFirst;
220
225
  if (group !== undefined) group.valueDeclaration = undefined;
221
226
  return;
222
227
  }
223
- this.bits &= ~valueDeclarationIsFirst;
228
+ this.bits &= ~SymbolBits.ValueDeclarationIsFirst;
224
229
  if (node !== undefined || group !== undefined) this.rareFieldsOrNew().valueDeclaration = node;
225
230
  }
226
231