tsc-ts 0.0.7 → 0.0.9

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 (39) hide show
  1. package/README.md +1 -1
  2. package/dist/api/compiler/checker-declaration-order.js +3 -2
  3. package/dist/api/compiler/checker-declared-types.js +6 -3
  4. package/dist/api/compiler/checker-deprecation.js +14 -13
  5. package/dist/api/compiler/checker-destructuring.js +3 -2
  6. package/dist/api/compiler/checker-members.js +6 -2
  7. package/dist/api/compiler/checker-source-file.d.ts +12 -0
  8. package/dist/api/compiler/checker-source-file.js +35 -0
  9. package/dist/api/compiler/checker-symbol-types.js +5 -5
  10. package/dist/api/compiler/checker-symbols.js +5 -2
  11. package/dist/api/compiler/checker.d.ts +6 -1
  12. package/dist/api/compiler/checker.js +9 -1
  13. package/dist/api/compiler/file-loader.d.ts +2 -2
  14. package/dist/api/compiler/file-loader.js +43 -23
  15. package/dist/api/compiler/link-tables.d.ts +7 -0
  16. package/dist/api/compiler/link-tables.js +22 -0
  17. package/dist/api/compiler/parser.d.ts +7 -0
  18. package/dist/api/compiler/parser.js +55 -32
  19. package/dist/api/compiler/program-checker.d.ts +18 -0
  20. package/dist/api/compiler/program-checker.js +35 -2
  21. package/dist/api/library/diagnostics.d.ts +10 -0
  22. package/dist/api/library/diagnostics.js +20 -0
  23. package/package.json +9 -9
  24. package/src/compiler/checker-declaration-order.ts +3 -2
  25. package/src/compiler/checker-declared-types.ts +6 -3
  26. package/src/compiler/checker-deprecation.ts +13 -11
  27. package/src/compiler/checker-destructuring.ts +11 -2
  28. package/src/compiler/checker-members.ts +6 -2
  29. package/src/compiler/checker-source-file.ts +34 -0
  30. package/src/compiler/checker-symbol-types.ts +14 -4
  31. package/src/compiler/checker-symbols.ts +6 -2
  32. package/src/compiler/checker.ts +8 -1
  33. package/src/compiler/file-loader.ts +49 -24
  34. package/src/compiler/link-tables.ts +32 -0
  35. package/src/compiler/parallel-check.ts +199 -20
  36. package/src/compiler/parser.ts +55 -32
  37. package/src/compiler/program-checker.ts +36 -2
  38. package/src/compiler/thread-entry.ts +16 -1
  39. package/src/library/diagnostics.ts +23 -0
@@ -65,7 +65,8 @@ export function residentMB(): number[] {
65
65
  * (`CheckerResult.reserved`). With `emits` false the thread doesn't emit the files it checks, where
66
66
  * the main thread emits the program (`checkersEmit`). `emitKinds`, by file index, is what the emit of
67
67
  * each file includes (an `EmitOnly`, or -1 for none) where not every output of every file is emitted
68
- * (an incremental program's rebuild); `undefined` for all of them.
68
+ * (an incremental program's rebuild); `undefined` for all of them. `units` are the statement ranges of
69
+ * declaration files the order has positions for (`CheckPlan.units`).
69
70
  */
70
71
  export type CheckerSpawn = (
71
72
  program: Program | undefined,
@@ -78,6 +79,7 @@ export type CheckerSpawn = (
78
79
  emits: boolean,
79
80
  emitKinds: number[] | undefined,
80
81
  checking: boolean,
82
+ units: number[],
81
83
  ) => Worker;
82
84
 
83
85
  export class ParallelCheck {
@@ -113,6 +115,10 @@ export class CheckerResult {
113
115
  /** The positions of a group the thread took and had not checked when it stopped at its file limit. */
114
116
  readonly reserved: number[] = [],
115
117
  readonly emits: EmitResult[] = [],
118
+ /** The statement ranges of declaration files it checked (`rangeUnits`). */
119
+ readonly units: number[] = [],
120
+ /** Those of `units` after which its checker had diagnostics in their file (`checkSourceFileRange`). */
121
+ readonly reportingUnits: number[] = [],
116
122
  ) {}
117
123
  }
118
124
 
@@ -152,6 +158,8 @@ export type PackedResult = {
152
158
  readonly stats: number[];
153
159
  readonly more: boolean;
154
160
  readonly reserved: number[];
161
+ readonly units: Int32Array;
162
+ readonly reportingUnits: Int32Array;
155
163
  };
156
164
 
157
165
  class ResultPacker {
@@ -234,6 +242,8 @@ export function packResult(result: CheckerResult, program: Program): PackedResul
234
242
  stats: result.stats,
235
243
  more: result.more,
236
244
  reserved: result.reserved,
245
+ units: Int32Array.from(result.units),
246
+ reportingUnits: Int32Array.from(result.reportingUnits),
237
247
  };
238
248
  }
239
249
 
@@ -348,6 +358,8 @@ export function unpackResult(
348
358
  packed.more,
349
359
  packed.reserved,
350
360
  emits,
361
+ Array.from(packed.units),
362
+ Array.from(packed.reportingUnits),
351
363
  );
352
364
  }
353
365
 
@@ -1161,6 +1173,12 @@ export class CheckPlan {
1161
1173
  readonly references: number,
1162
1174
  /** For each position, the number of files of the group starting there (0 elsewhere). */
1163
1175
  readonly groups: number[] = [],
1176
+ /**
1177
+ * The statement ranges of declaration files that several checkers check (`rangeUnits`): for each,
1178
+ * the file's index and its first and end statement. Position `k` of `order` is range `u` where
1179
+ * `order[k]` is `-1 - u`.
1180
+ */
1181
+ readonly units: number[] = [],
1164
1182
  ) {}
1165
1183
  }
1166
1184
 
@@ -1310,16 +1328,100 @@ export function checkGroups(
1310
1328
  return groups;
1311
1329
  }
1312
1330
 
1331
+ /**
1332
+ * How many ranges a declaration file checked in statement ranges (`rangeUnits`) has per checker's share
1333
+ * of the program's estimated cost, and at most per checker.
1334
+ */
1335
+ const rangesPerShare = 2;
1336
+ const rangesPerChecker = 2;
1337
+
1338
+ /**
1339
+ * The statement ranges that `count` checkers check declaration files in, as `CheckPlan.units`: those of
1340
+ * the files of `costs` whose estimated cost is more than a checker's share, which one checker would
1341
+ * finish long after the others. lib.dom.d.ts (2.4 MB) is such a file in a small program: one checker
1342
+ * took 150 ms on it while 15 waited. Each range is about half a share, of about equal text, and the
1343
+ * last one ends the file (`checkSourceFileRange`). `grouped` files (`checkGroups`) are checked whole.
1344
+ * TSC_TS_RANGES=0 checks every file whole; TSC_TS_RANGES=all checks every checked declaration file in
1345
+ * ranges, `count` of them.
1346
+ */
1347
+ export function rangeUnits(
1348
+ checked: CheckedProgram,
1349
+ count: number,
1350
+ costs: CheckCosts,
1351
+ grouped: ReadonlySet<number> = new Set<number>(),
1352
+ ): number[] {
1353
+ const units: number[] = [];
1354
+ const mode = process.env.TSC_TS_RANGES;
1355
+ if (count < 2 || mode === '0') return units;
1356
+ const files = checked.program.getSourceFiles();
1357
+ const cost = costs.cost;
1358
+ let all = 0;
1359
+ for (const i of costs.order) all += cost[i]!;
1360
+ const share = all / count;
1361
+ for (const i of costs.order) {
1362
+ const file = files[i]!;
1363
+ if (!file.isDeclarationFile || grouped.has(i) || (mode !== 'all' && cost[i]! <= share)) continue;
1364
+ const statements = file.statements.nodes;
1365
+ const n = Math.min(
1366
+ statements.length,
1367
+ mode === 'all' ? count : Math.min(rangesPerChecker * count, Math.ceil((rangesPerShare * cost[i]!) / share)),
1368
+ );
1369
+ if (n < 2) continue;
1370
+ const text = file.text.length;
1371
+ let start = 0;
1372
+ for (let r = 1; r <= n && start < statements.length; r++) {
1373
+ let end = statements.length;
1374
+ if (r < n) {
1375
+ const target = (text * r) / n;
1376
+ end = start + 1;
1377
+ while (end < statements.length && statements[end - 1]!.end < target) end++;
1378
+ }
1379
+ units.push(i, start, end);
1380
+ start = end;
1381
+ }
1382
+ }
1383
+ return units;
1384
+ }
1385
+
1386
+ /** The estimated cost of range `u` of `units`, its file's share by text. */
1387
+ function unitCost(checked: CheckedProgram, costs: CheckCosts, units: readonly number[], u: number): number {
1388
+ const file = checked.program.getSourceFiles()[units[3 * u]!]!;
1389
+ return (costs.cost[units[3 * u]!]! * unitLength(file, units, u)) / Math.max(1, file.text.length);
1390
+ }
1391
+
1392
+ /** The text length of the statements of range `u` of `units` of `file`. */
1393
+ function unitLength(file: SourceFile, units: readonly number[], u: number): number {
1394
+ const statements = file.statements.nodes;
1395
+ return statements[units[3 * u + 2]! - 1]!.end - statements[units[3 * u + 1]!]!.pos;
1396
+ }
1397
+
1313
1398
  /**
1314
1399
  * Plans checking with `count` checkers: the order to check files in, the costliest files and the
1315
1400
  * groups (`checkGroups`; at least `count` entries, and every file costing more than a 32nd of a
1316
1401
  * checker's share), costliest first, from the shared queue; then the others split, in program
1317
- * order, into one range of about equal cost per checker, each costliest first.
1402
+ * order, into one range of about equal cost per checker, each costliest first. With `inRanges`, the
1403
+ * statement ranges of large declaration files (`rangeUnits`) replace them in the shared queue.
1318
1404
  */
1319
- export function planSchedule(checked: CheckedProgram, count: number, costs = costOrder(checked)): CheckPlan {
1405
+ export function planSchedule(
1406
+ checked: CheckedProgram,
1407
+ count: number,
1408
+ costs = costOrder(checked),
1409
+ inRanges = false,
1410
+ ): CheckPlan {
1320
1411
  const order = costs.order;
1321
1412
  const cost = costs.cost;
1322
- if (order.length <= count) return new CheckPlan(order, -1, [], [], [], [], 0);
1413
+ if (order.length <= count) {
1414
+ const units = inRanges ? rangeUnits(checked, count, costs) : [];
1415
+ if (units.length === 0) return new CheckPlan(order, -1, [], [], [], [], 0);
1416
+ // The ranges take the place of their files, all in one shared queue, costliest first.
1417
+ const split = new Set<number>();
1418
+ for (let u = 0; u < units.length; u += 3) split.add(units[u]!);
1419
+ const entries: number[] = order.filter((i) => !split.has(i));
1420
+ for (let u = 0; u < units.length / 3; u++) entries.push(-1 - u);
1421
+ const costOf = (k: number): number => (k < 0 ? unitCost(checked, costs, units, -1 - k) : cost[k]!);
1422
+ entries.sort((a, b) => costOf(b) - costOf(a));
1423
+ return new CheckPlan(entries, -1, [], [], [], [], 0, [], units);
1424
+ }
1323
1425
  let all = 0;
1324
1426
  for (const i of order) all += cost[i]!;
1325
1427
  // Files that cost more than a 32nd of a checker's share go first, costliest first: at the end of
@@ -1330,11 +1432,15 @@ export function planSchedule(checked: CheckedProgram, count: number, costs = cos
1330
1432
  const groups = checkGroups(checked, count, groupLoad, costs);
1331
1433
  const grouped = new Set<number>();
1332
1434
  for (const group of groups) for (const i of group) grouped.add(i);
1435
+ const units = inRanges ? rangeUnits(checked, count, costs, grouped) : [];
1436
+ const split = new Set<number>();
1437
+ for (let u = 0; u < units.length; u += 3) split.add(units[u]!);
1333
1438
  const entries: number[][] = groups.slice();
1334
- for (const i of order.slice(0, heavyCount)) if (!grouped.has(i)) entries.push([i]);
1439
+ for (const i of order.slice(0, heavyCount)) if (!grouped.has(i) && !split.has(i)) entries.push([i]);
1440
+ for (let u = 0; u < units.length / 3; u++) entries.push([-1 - u]);
1335
1441
  const entryCost = (entry: number[]): number => {
1336
1442
  let sum = 0;
1337
- for (const i of entry) sum += cost[i]!;
1443
+ for (const i of entry) sum += i < 0 ? unitCost(checked, costs, units, -1 - i) : cost[i]!;
1338
1444
  return sum;
1339
1445
  };
1340
1446
  entries.sort((a, b) => entryCost(b) - entryCost(a) || a[0]! - b[0]!);
@@ -1347,7 +1453,7 @@ export function planSchedule(checked: CheckedProgram, count: number, costs = cos
1347
1453
  }
1348
1454
  const rest = order
1349
1455
  .slice(heavyCount)
1350
- .filter((i) => !grouped.has(i))
1456
+ .filter((i) => !grouped.has(i) && !split.has(i))
1351
1457
  .sort((a, b) => a - b);
1352
1458
  heavyCount = heavy.length;
1353
1459
  let total = 0;
@@ -1378,9 +1484,9 @@ export function planSchedule(checked: CheckedProgram, count: number, costs = cos
1378
1484
  const libraries: number[][] = [];
1379
1485
  let references = 0;
1380
1486
  for (const i of planned) {
1381
- lengths.push(textLength[i]!);
1487
+ lengths.push(i < 0 ? unitLength(files[units[-3 * i - 3]!]!, units, -1 - i) : textLength[i]!);
1382
1488
  const ids: number[] = [];
1383
- for (const k of costs.libraries[i]!) {
1489
+ for (const k of costs.libraries[i < 0 ? units[-3 * i - 3]! : i]!) {
1384
1490
  let id = libraryIds.get(k);
1385
1491
  if (id === undefined) {
1386
1492
  id = libraryCosts.length;
@@ -1398,7 +1504,7 @@ export function planSchedule(checked: CheckedProgram, count: number, costs = cos
1398
1504
  process.stderr.write(
1399
1505
  `checkers: group of ${group.length}: ${group.map((i) => files[i]!.fileName.split('/').slice(-2).join('/')).join(' ')}\n`,
1400
1506
  );
1401
- return new CheckPlan(planned, heavyCount, ranges, lengths, libraries, libraryCosts, references, groupAt);
1507
+ return new CheckPlan(planned, heavyCount, ranges, lengths, libraries, libraryCosts, references, groupAt, units);
1402
1508
  }
1403
1509
 
1404
1510
  /** With TSC_TS_CHECKERS_TRACE: the single-threaded checker's statistics, like a checker thread's. */
@@ -1422,7 +1528,8 @@ export function traceSingleChecker(checked: CheckedProgram): void {
1422
1528
  * A checker thread (`onMainThread` false) also sends back its global diagnostics. At `limit` files
1423
1529
  * it stops; `reserved` are the positions of a group its predecessor on `index` had not checked then.
1424
1530
  * With `emit` false it doesn't emit the files it checks (`checkersEmit`). With `checking` false it
1425
- * only emits them (`startParallelCheck`).
1531
+ * only emits them (`startParallelCheck`). A position of the order below 0 is a statement range of a
1532
+ * declaration file, `-1 - u` for the range at `3 * u` in `units` (`CheckPlan.units`).
1426
1533
  */
1427
1534
  export function checkFromQueue(
1428
1535
  checked: CheckedProgram,
@@ -1436,6 +1543,7 @@ export function checkFromQueue(
1436
1543
  emit: boolean = true,
1437
1544
  emitKinds: readonly number[] | undefined = undefined,
1438
1545
  checking: boolean = true,
1546
+ units: readonly number[] = [],
1439
1547
  ): CheckerResult {
1440
1548
  const start = performance.now();
1441
1549
  const readyAt = Date.now();
@@ -1446,6 +1554,8 @@ export function checkFromQueue(
1446
1554
  const diagnostics: Diagnostic[][] = [];
1447
1555
  const declarations: Diagnostic[][] = [];
1448
1556
  const emits: EmitResult[] = [];
1557
+ const unitsDone: number[] = [];
1558
+ const reportingUnits: number[] = [];
1449
1559
  // Emit (or declaration emit) of a file right after checking it, with this checker.
1450
1560
  const emitting = checkersEmit(checked, emit);
1451
1561
  // Outputs are held for the main thread under `noEmitOnError`, where emit waits for every file's diagnostics,
@@ -1487,6 +1597,28 @@ export function checkFromQueue(
1487
1597
  const k = takePosition(counter, order.length, index, plan);
1488
1598
  if (k < 0) break;
1489
1599
  const i = order[k]!;
1600
+ if (i < 0) {
1601
+ // A statement range of a declaration file, which has nothing to emit.
1602
+ const u = -1 - i;
1603
+ const file = files[units[3 * u]!]!;
1604
+ const rangeStart = verbose || timeline ? performance.now() : 0;
1605
+ const reports = checking && checked.checkRange(file, units[3 * u + 1]!, units[3 * u + 2]!);
1606
+ unitsDone.push(u);
1607
+ if (reports) reportingUnits.push(u);
1608
+ // A range doesn't block emit under `noEmitOnError`: what its checker found may be suppressed
1609
+ // (`// @ts-ignore`) or not the file's, which this thread doesn't know. The main thread decides
1610
+ // from the file's diagnostics, and the outputs are held for it.
1611
+ if (timeline)
1612
+ markAt(
1613
+ rangeStart,
1614
+ `range ${(performance.now() - rangeStart).toFixed(1)} pos ${k} statements ${units[3 * u + 1]}-${units[3 * u + 2]} ${file.fileName}`,
1615
+ );
1616
+ if (verbose)
1617
+ process.stderr.write(
1618
+ `checkers: range ${index} ${(performance.now() - rangeStart).toFixed(1)}ms statements ${units[3 * u + 1]}-${units[3 * u + 2]} ${file.fileName}\n`,
1619
+ );
1620
+ continue;
1621
+ }
1490
1622
  done.push(i);
1491
1623
  const fileStart = verbose || timeline ? performance.now() : 0;
1492
1624
  // Without `checking` the file is only emitted: declaration emit checks what it needs.
@@ -1525,7 +1657,7 @@ export function checkFromQueue(
1525
1657
  if (timeline) markAt(emitStart, `emit ${(performance.now() - emitStart).toFixed(1)} ${files[i]!.fileName}`);
1526
1658
  } else if (emitter !== undefined) declarations.push(declarationDiagnosticsOfFile(emitter, files[i]!));
1527
1659
  }
1528
- mark(`checked ${done.length} files`);
1660
+ mark(`checked ${done.length} files, ${unitsDone.length} ranges`);
1529
1661
  // The main thread reports its own global diagnostics (CheckedProgram.getGlobalDiagnostics).
1530
1662
  const globals = onMainThread ? [] : checked.getGlobalDiagnostics().slice();
1531
1663
  let stats: number[] = [];
@@ -1564,6 +1696,8 @@ export function checkFromQueue(
1564
1696
  more,
1565
1697
  left,
1566
1698
  emits,
1699
+ unitsDone,
1700
+ reportingUnits,
1567
1701
  );
1568
1702
  }
1569
1703
 
@@ -1721,23 +1855,29 @@ export function startParallelCheck(
1721
1855
  }
1722
1856
  const orderStart = performance.now();
1723
1857
  const costs = costOrder(checked);
1724
- let plan = planSchedule(checked, count, costs);
1858
+ let plan = planSchedule(checked, count, costs, checking);
1725
1859
  const fewer = libraryBoundCheckers(checked, plan, costs, count);
1726
1860
  if (fewer < count) {
1727
1861
  count = fewer;
1728
- plan = planSchedule(checked, count, costs);
1862
+ plan = planSchedule(checked, count, costs, checking);
1729
1863
  }
1730
1864
  const order = plan.order;
1865
+ const units = plan.units;
1731
1866
  const counter = scheduleOf(plan, count);
1732
1867
  if (trace) {
1733
1868
  let top = '';
1734
- for (let k = 0; k < Math.min(5, order.length); k++) top += ' ' + files[order[k]!]!.fileName;
1869
+ for (let k = 0; k < Math.min(5, order.length); k++)
1870
+ top +=
1871
+ ' ' +
1872
+ (order[k]! < 0 ? `range ${-1 - order[k]!} of ` : '') +
1873
+ files[order[k]! < 0 ? units[-3 * order[k]! - 3]! : order[k]!]!.fileName;
1735
1874
  process.stderr.write(
1736
- `checkers: ordered ${order.length} files in ${(performance.now() - orderStart).toFixed(1)}ms, first:${top}\n`,
1875
+ `checkers: ordered ${order.length} positions (${units.length / 3} statement ranges) in ${(performance.now() - orderStart).toFixed(1)}ms, first:${top}\n`,
1737
1876
  );
1738
1877
  }
1739
1878
  if (shared) {
1740
1879
  publish(order);
1880
+ publish(units);
1741
1881
  if (emitKinds !== undefined) publish(emitKinds);
1742
1882
  }
1743
1883
  mark(`planned ${order.length} files, ${plan.heavy} heavy`);
@@ -1749,6 +1889,10 @@ export function startParallelCheck(
1749
1889
  perFileDeclarations.push(undefined);
1750
1890
  perFileEmits.push(undefined);
1751
1891
  }
1892
+ // For each statement range (`CheckPlan.units`): 0 until checked, 1 once checked, 2 if its checker then had
1893
+ // diagnostics in the file.
1894
+ const perUnit: number[] = [];
1895
+ for (let u = 0; u < units.length / 3; u++) perUnit.push(0);
1752
1896
  const revival = shared ? undefined : new Revival(program);
1753
1897
  const mainChecks = mainThreadChecks(shared);
1754
1898
  const firstThread = mainChecks ? 1 : 0;
@@ -1756,8 +1900,10 @@ export function startParallelCheck(
1756
1900
  let ownDone = false;
1757
1901
  const finish = (): void => {
1758
1902
  if (checking) {
1903
+ joinRanges(checked, units, perUnit, perFile);
1759
1904
  // A thread that failed may have taken files it never checked: check them here.
1760
- for (const i of order) if (perFile[i] === undefined) perFile[i] = checked.unfilteredDiagnosticsOfFile(files[i]!);
1905
+ for (const i of order)
1906
+ if (i >= 0 && perFile[i] === undefined) perFile[i] = checked.unfilteredDiagnosticsOfFile(files[i]!);
1761
1907
  checked.checkedInParallel = perFile;
1762
1908
  }
1763
1909
  mark('all checked');
@@ -1787,12 +1933,14 @@ export function startParallelCheck(
1787
1933
  for (let k = 0; k < result.declarations.length; k++)
1788
1934
  perFileDeclarations[result.files[k]!] = result.declarations[k]!;
1789
1935
  for (let k = 0; k < result.emits.length; k++) perFileEmits[result.files[k]!] = result.emits[k]!;
1936
+ for (const u of result.units) perUnit[u] = 1;
1937
+ for (const u of result.reportingUnits) perUnit[u] = 2;
1790
1938
  for (const d of result.globals) checked.parallelGlobalDiagnostics.push(d);
1791
1939
  if (trace) {
1792
1940
  const st = result.stats;
1793
1941
  const rel = (t: number | undefined): string => (t === undefined ? '?' : String(t - epoch));
1794
1942
  process.stderr.write(
1795
- `checkers: thread ${result.index} checked ${result.files.length} files in ${result.checkTime.toFixed(1)}ms, done at ${(performance.now() - start).toFixed(1)}ms` +
1943
+ `checkers: thread ${result.index} checked ${result.files.length} files and ${result.units.length} ranges in ${result.checkTime.toFixed(1)}ms, done at ${(performance.now() - start).toFixed(1)}ms` +
1796
1944
  ` [start ${rel(st[0])} ready ${rel(st[1])} end ${rel(st[2])} types ${st[3]} inst ${st[4]} user ${Math.round(st[5] ?? 0)} sys ${Math.round(st[6] ?? 0)} rss ${st[7]}MB symlinks ${st[8]} nodelinks ${st[9]} sigs ${st[10]} rel ${st[11]}]\n`,
1797
1945
  );
1798
1946
  }
@@ -1814,6 +1962,7 @@ export function startParallelCheck(
1814
1962
  emits,
1815
1963
  emitKinds,
1816
1964
  checking,
1965
+ units,
1817
1966
  );
1818
1967
  worker.on('message', (message: unknown) => {
1819
1968
  mark(`result from c${i}`);
@@ -1843,11 +1992,39 @@ export function startParallelCheck(
1843
1992
  const emittedHere =
1844
1993
  !mainChecks && emitKinds === undefined ? emitUncheckedFiles(checked, order, emits) : new Map<number, EmitResult>();
1845
1994
  if (mainChecks)
1846
- record(checkFromQueue(checked, 0, new Int32Array(counter), order, epoch, true, 0, [], emits, emitKinds, checking));
1995
+ record(
1996
+ checkFromQueue(checked, 0, new Int32Array(counter), order, epoch, true, 0, [], emits, emitKinds, checking, units),
1997
+ );
1847
1998
  ownDone = true;
1848
1999
  if (remaining === 0) finish();
1849
2000
  }
1850
2001
 
2002
+ /**
2003
+ * Sets the diagnostics in `perFile` of each file checked in statement ranges (`units`): none from its
2004
+ * checkers where every range was checked and found no diagnostics in it (`perUnit`, by range:
2005
+ * `checkSourceFileRange`); otherwise a new checker on this thread checks it whole.
2006
+ */
2007
+ function joinRanges(
2008
+ checked: CheckedProgram,
2009
+ units: readonly number[],
2010
+ perUnit: readonly number[],
2011
+ perFile: (Diagnostic[] | undefined)[],
2012
+ ): void {
2013
+ const files = checked.program.getSourceFiles();
2014
+ const trace = process.env.TSC_TS_CHECKERS_TRACE !== undefined;
2015
+ // A file's ranges are consecutive.
2016
+ for (let u = 0; u < perUnit.length;) {
2017
+ const i = units[3 * u]!;
2018
+ let clean = true;
2019
+ for (; u < perUnit.length && units[3 * u] === i; u++) if (perUnit[u] !== 1) clean = false;
2020
+ if (clean) perFile[i] = checked.unfilteredDiagnosticsOfCleanRanges(files[i]!);
2021
+ else {
2022
+ if (trace) process.stderr.write(`checkers: checking ${files[i]!.fileName} whole: its ranges have diagnostics\n`);
2023
+ perFile[i] = checked.unfilteredDiagnosticsOfFileOnNewChecker(files[i]!);
2024
+ }
2025
+ }
2026
+ }
2027
+
1851
2028
  /**
1852
2029
  * Emits, on this thread and into memory, the files that checker threads emit nothing for because
1853
2030
  * they don't check them (`skipTypeChecking`, such as a file with `// @ts-nocheck`), while the threads
@@ -1867,7 +2044,7 @@ function emitUncheckedFiles(
1867
2044
  const files = program.getSourceFiles();
1868
2045
  const inOrder: boolean[] = [];
1869
2046
  for (let i = 0; i < files.length; i++) inOrder.push(false);
1870
- for (const i of order) inOrder[i] = true;
2047
+ for (const i of order) if (i >= 0) inOrder[i] = true;
1871
2048
  let emitter: ProgramEmitter | undefined = undefined;
1872
2049
  let holding: HoldingWriter | undefined = undefined;
1873
2050
  for (let i = 0; i < files.length; i++) {
@@ -1909,6 +2086,8 @@ class Revival {
1909
2086
  copy.more,
1910
2087
  copy.reserved,
1911
2088
  copy.emits.map((emit) => this.emit(emit)),
2089
+ copy.units,
2090
+ copy.reportingUnits,
1912
2091
  );
1913
2092
  }
1914
2093
 
@@ -503,6 +503,10 @@ export class Parser extends ScannerErrorReporter {
503
503
  // receives reparsed nodes, and a fresh array per parsed list would be one more allocation each.
504
504
  private readonly reparseLists: Node[][];
505
505
  private reparseDepth: number;
506
+ // One scratch array per nesting depth of the lists being parsed. A list collects its elements in
507
+ // its depth's array and is copied out at its exact length, so the tree keeps no spare capacity.
508
+ private readonly listScratch: Node[][];
509
+ private listDepth: number;
506
510
  reparsedClones: Node[];
507
511
  /** Nodes with eagerly parsed JSDoc, for rewinding. */
508
512
  jsDocInfos: Node[];
@@ -562,6 +566,8 @@ export class Parser extends ScannerErrorReporter {
562
566
  this.reparseList = [];
563
567
  this.reparseLists = [this.reparseList];
564
568
  this.reparseDepth = 0;
569
+ this.listScratch = [];
570
+ this.listDepth = -1;
565
571
  this.reparsedClones = [];
566
572
  this.jsDocInfos = [];
567
573
  this.jsDocTable = undefined;
@@ -1024,7 +1030,7 @@ export class Parser extends ScannerErrorReporter {
1024
1030
  if (depth === this.reparseLists.length) this.reparseLists.push([]);
1025
1031
  const reparseList = this.reparseLists[depth]!;
1026
1032
  this.reparseList = reparseList;
1027
- const list: Node[] = [];
1033
+ const list = this.openList();
1028
1034
  while (!this.isListTerminator(kind)) {
1029
1035
  if (this.isListElement(kind, false)) {
1030
1036
  const elt = this.parseListElement(element, list.length);
@@ -1049,7 +1055,7 @@ export class Parser extends ScannerErrorReporter {
1049
1055
  this.reparseDepth = depth - 1;
1050
1056
  this.reparseList = outerReparseList;
1051
1057
  this.parsingContexts = saveParsingContexts;
1052
- return list;
1058
+ return this.closeList(list);
1053
1059
  }
1054
1060
 
1055
1061
  parseList(kind: ParsingContext, element: ListElement): NodeList {
@@ -1063,12 +1069,13 @@ export class Parser extends ScannerErrorReporter {
1063
1069
  const pos = this.nodePos();
1064
1070
  const saveParsingContexts = this.parsingContexts;
1065
1071
  this.parsingContexts |= 1 << kind;
1066
- const list: Node[] = [];
1072
+ const list = this.openList();
1067
1073
  for (;;) {
1068
1074
  if (this.isListElement(kind, false)) {
1069
1075
  const startPos = this.nodePos();
1070
1076
  const result = this.parseListElement(element, list.length);
1071
1077
  if (result === this.noElement) {
1078
+ this.closeList(list);
1072
1079
  this.parsingContexts = saveParsingContexts;
1073
1080
  return undefined;
1074
1081
  }
@@ -1094,7 +1101,24 @@ export class Parser extends ScannerErrorReporter {
1094
1101
  if (this.abortParsingListOrMoveToNextToken(kind)) break;
1095
1102
  }
1096
1103
  this.parsingContexts = saveParsingContexts;
1097
- return this.newNodeList(pos, this.nodePos(), list);
1104
+ return this.newNodeList(pos, this.nodePos(), this.closeList(list));
1105
+ }
1106
+
1107
+ /** The scratch array for a list at the next nesting depth, empty. */
1108
+ private openList(): Node[] {
1109
+ const depth = ++this.listDepth;
1110
+ if (depth === this.listScratch.length) this.listScratch.push([]);
1111
+ return this.listScratch[depth]!;
1112
+ }
1113
+
1114
+ /** The elements collected in `list` (the scratch array from `openList`) as an array of their exact
1115
+ * length, or the shared empty array; empties the scratch array for the next list at its depth. */
1116
+ private closeList(list: Node[]): Node[] {
1117
+ this.listDepth--;
1118
+ if (list.length === 0) return emptyListNodes;
1119
+ const nodes = list.slice();
1120
+ list.length = 0;
1121
+ return nodes;
1098
1122
  }
1099
1123
 
1100
1124
  /** The list; a missing list if `opening` is absent; undefined if an element parser returned undefined. */
@@ -3123,9 +3147,10 @@ export class Parser extends ScannerErrorReporter {
3123
3147
  ? this.parseFunctionOrConstructorTypeToError(isUnionType)
3124
3148
  : this.parseConstituentType(isUnionType);
3125
3149
  if (this.token() === operator || hasLeadingOperator) {
3126
- const types: Node[] = [type];
3150
+ const types = this.openList();
3151
+ types.push(type);
3127
3152
  while (this.parseOptional(operator)) types.push(this.parseFunctionOrConstructorTypeToError(isUnionType));
3128
- const list = this.newNodeList(pos, this.nodePos(), types);
3153
+ const list = this.newNodeList(pos, this.nodePos(), this.closeList(types));
3129
3154
  type = isUnionType ? new UnionTypeNode(list) : new IntersectionTypeNode(list);
3130
3155
  this.finishNode(type, pos);
3131
3156
  }
@@ -3821,17 +3846,12 @@ export class Parser extends ScannerErrorReporter {
3821
3846
  }
3822
3847
 
3823
3848
  parseTypeOrTypePredicate(): TypeNode {
3824
- if (this.isIdentifier()) {
3825
- const state = this.beginSpeculation();
3849
+ // `x is T`: the identifier is parsed only once `is` is known to follow it.
3850
+ if (this.isIdentifier() && this.lookAhead(nextTokenIsIsKeywordOnSameLine)) {
3826
3851
  const pos = this.nodePos();
3827
3852
  const id = this.parseIdentifier(IdentifierUse.Name);
3828
- if (this.token() === SyntaxKind.IsKeyword && !this.hasPrecedingLineBreak()) {
3829
- this.endSpeculation(state);
3830
- this.nextToken();
3831
- return this.finishNode(new TypePredicateNode(undefined, id, this.parseType()), pos);
3832
- }
3833
- this.rewind(state);
3834
- this.endSpeculation(state);
3853
+ this.nextToken();
3854
+ return this.finishNode(new TypePredicateNode(undefined, id, this.parseType()), pos);
3835
3855
  }
3836
3856
  return this.parseType();
3837
3857
  }
@@ -4059,13 +4079,13 @@ export class Parser extends ScannerErrorReporter {
4059
4079
 
4060
4080
  parseTemplateTypeSpans(): NodeList {
4061
4081
  const pos = this.nodePos();
4062
- const list: Node[] = [];
4082
+ const list = this.openList();
4063
4083
  for (;;) {
4064
4084
  const span = this.parseTemplateTypeSpan();
4065
4085
  list.push(span);
4066
4086
  if (span.literal.kind !== SyntaxKind.TemplateMiddle) break;
4067
4087
  }
4068
- return this.newNodeList(pos, this.nodePos(), list);
4088
+ return this.newNodeList(pos, this.nodePos(), this.closeList(list));
4069
4089
  }
4070
4090
 
4071
4091
  parseTemplateTypeSpan(): TemplateLiteralTypeSpan {
@@ -4189,24 +4209,23 @@ export class Parser extends ScannerErrorReporter {
4189
4209
  let hasTrailingModifier = false;
4190
4210
  let hasStaticModifier = false;
4191
4211
  const pos = this.nodePos();
4192
- let list: Node[] | undefined;
4212
+ const list = this.openList();
4193
4213
  for (;;) {
4194
4214
  if (allowDecorators && this.token() === SyntaxKind.AtToken && !hasTrailingModifier) {
4195
4215
  const decorator = this.parseDecorator();
4196
- if (list === undefined) list = [];
4197
4216
  list.push(decorator);
4198
4217
  if (hasLeadingModifier) hasTrailingDecorator = true;
4199
4218
  } else {
4200
4219
  const modifier = this.tryParseModifier(hasStaticModifier, permitConstAsModifier, stopOnStartOfClassStaticBlock);
4201
4220
  if (modifier === undefined) break;
4202
4221
  if (modifier.kind === SyntaxKind.StaticKeyword) hasStaticModifier = true;
4203
- if (list === undefined) list = [];
4204
4222
  list.push(modifier);
4205
4223
  if (hasTrailingDecorator) hasTrailingModifier = true;
4206
4224
  else hasLeadingModifier = true;
4207
4225
  }
4208
4226
  }
4209
- return list === undefined ? undefined : this.newModifierList(pos, this.nodePos(), list);
4227
+ const nodes = this.closeList(list);
4228
+ return nodes.length === 0 ? undefined : this.newModifierList(pos, this.nodePos(), nodes);
4210
4229
  }
4211
4230
 
4212
4231
  parseDecorator(): Node {
@@ -4938,7 +4957,7 @@ export class Parser extends ScannerErrorReporter {
4938
4957
  const pos = this.nodePos();
4939
4958
  const saveParsingContexts = this.parsingContexts;
4940
4959
  this.parsingContexts |= 1 << ParsingContext.JsxChildren;
4941
- const list: Node[] = [];
4960
+ const list = this.openList();
4942
4961
  for (;;) {
4943
4962
  // Like tsgo, this leaves the parser's current token unchanged.
4944
4963
  const currentToken = this.scanner.reScanJsxToken(true);
@@ -4958,7 +4977,7 @@ export class Parser extends ScannerErrorReporter {
4958
4977
  break;
4959
4978
  }
4960
4979
  this.parsingContexts = saveParsingContexts;
4961
- return this.newNodeList(pos, this.nodePos(), list);
4980
+ return this.newNodeList(pos, this.nodePos(), this.closeList(list));
4962
4981
  }
4963
4982
 
4964
4983
  parseJsxChild(openingTag: Node, token: SyntaxKind): Expression | undefined {
@@ -5633,13 +5652,13 @@ export class Parser extends ScannerErrorReporter {
5633
5652
 
5634
5653
  parseTemplateSpans(isTaggedTemplate: boolean): NodeList {
5635
5654
  const pos = this.nodePos();
5636
- const list: Node[] = [];
5655
+ const list = this.openList();
5637
5656
  for (;;) {
5638
5657
  const span = this.parseTemplateSpan(isTaggedTemplate);
5639
5658
  list.push(span);
5640
5659
  if (span.literal.kind !== SyntaxKind.TemplateMiddle) break;
5641
5660
  }
5642
- return this.newNodeList(pos, this.nodePos(), list);
5661
+ return this.newNodeList(pos, this.nodePos(), this.closeList(list));
5643
5662
  }
5644
5663
 
5645
5664
  parseTemplateSpan(isTaggedTemplate: boolean): TemplateSpan {
@@ -7283,6 +7302,10 @@ function nextTokenIsClassKeywordOnSameLine(p: Parser): boolean {
7283
7302
  return p.nextToken() === SyntaxKind.ClassKeyword && !p.hasPrecedingLineBreak();
7284
7303
  }
7285
7304
 
7305
+ function nextTokenIsIsKeywordOnSameLine(p: Parser): boolean {
7306
+ return p.nextToken() === SyntaxKind.IsKeyword && !p.hasPrecedingLineBreak();
7307
+ }
7308
+
7286
7309
  function nextTokenIsFunctionKeywordOnSameLine(p: Parser): boolean {
7287
7310
  return p.nextToken() === SyntaxKind.FunctionKeyword && !p.hasPrecedingLineBreak();
7288
7311
  }
@@ -7347,13 +7370,13 @@ function nextIsUnParenthesizedAsyncArrowFunction(p: Parser): boolean {
7347
7370
  p.nextToken();
7348
7371
  // `async =>` is a simple arrow function with a parameter named `async`.
7349
7372
  if (p.hasPrecedingLineBreak() || p.token() === SyntaxKind.EqualsGreaterThanToken) return false;
7350
- const expr = p.parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest);
7351
- if (
7352
- !p.hasPrecedingLineBreak() &&
7353
- expr.kind === SyntaxKind.Identifier &&
7354
- p.token() === SyntaxKind.EqualsGreaterThanToken
7355
- )
7356
- return true;
7373
+ // The binary expression after `async` is a lone identifier followed by `=>` only when an identifier
7374
+ // token is followed by `=>`. Any other first token starts another kind of expression or none, and any
7375
+ // other token after the identifier continues the expression or ends it before `=>`. Checking the
7376
+ // tokens gives the same answer as parsing the expression, whose nodes would be discarded.
7377
+ if (!p.isIdentifier()) return false;
7378
+ p.nextToken();
7379
+ return !p.hasPrecedingLineBreak() && p.token() === SyntaxKind.EqualsGreaterThanToken;
7357
7380
  }
7358
7381
  return false;
7359
7382
  }