tsc-ts 0.0.8 → 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.
@@ -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
 
@@ -3846,17 +3846,12 @@ export class Parser extends ScannerErrorReporter {
3846
3846
  }
3847
3847
 
3848
3848
  parseTypeOrTypePredicate(): TypeNode {
3849
- if (this.isIdentifier()) {
3850
- 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)) {
3851
3851
  const pos = this.nodePos();
3852
3852
  const id = this.parseIdentifier(IdentifierUse.Name);
3853
- if (this.token() === SyntaxKind.IsKeyword && !this.hasPrecedingLineBreak()) {
3854
- this.endSpeculation(state);
3855
- this.nextToken();
3856
- return this.finishNode(new TypePredicateNode(undefined, id, this.parseType()), pos);
3857
- }
3858
- this.rewind(state);
3859
- this.endSpeculation(state);
3853
+ this.nextToken();
3854
+ return this.finishNode(new TypePredicateNode(undefined, id, this.parseType()), pos);
3860
3855
  }
3861
3856
  return this.parseType();
3862
3857
  }
@@ -7307,6 +7302,10 @@ function nextTokenIsClassKeywordOnSameLine(p: Parser): boolean {
7307
7302
  return p.nextToken() === SyntaxKind.ClassKeyword && !p.hasPrecedingLineBreak();
7308
7303
  }
7309
7304
 
7305
+ function nextTokenIsIsKeywordOnSameLine(p: Parser): boolean {
7306
+ return p.nextToken() === SyntaxKind.IsKeyword && !p.hasPrecedingLineBreak();
7307
+ }
7308
+
7310
7309
  function nextTokenIsFunctionKeywordOnSameLine(p: Parser): boolean {
7311
7310
  return p.nextToken() === SyntaxKind.FunctionKeyword && !p.hasPrecedingLineBreak();
7312
7311
  }
@@ -7371,13 +7370,13 @@ function nextIsUnParenthesizedAsyncArrowFunction(p: Parser): boolean {
7371
7370
  p.nextToken();
7372
7371
  // `async =>` is a simple arrow function with a parameter named `async`.
7373
7372
  if (p.hasPrecedingLineBreak() || p.token() === SyntaxKind.EqualsGreaterThanToken) return false;
7374
- const expr = p.parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest);
7375
- if (
7376
- !p.hasPrecedingLineBreak() &&
7377
- expr.kind === SyntaxKind.Identifier &&
7378
- p.token() === SyntaxKind.EqualsGreaterThanToken
7379
- )
7380
- 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;
7381
7380
  }
7382
7381
  return false;
7383
7382
  }
@@ -26,6 +26,7 @@ import {
26
26
  type SourceFileMetadata,
27
27
  } from './program-utilities.js';
28
28
  import { bindSourceFile } from './binder.js';
29
+ import { checkSourceFileRange } from './checker-source-file.js';
29
30
  import { computeLineOfPosition } from './scanner.js';
30
31
  import { CommentDirectiveKind } from './types.js';
31
32
 
@@ -313,6 +314,38 @@ export class CheckedProgram {
313
314
  return result;
314
315
  }
315
316
 
317
+ /**
318
+ * Checks the statements `start` to `end` of the declaration file `file` with this checker, one of the
319
+ * ranges that several checkers check it in (`checkSourceFileRange`, parallel-check.ts), and returns
320
+ * whether the checker then has diagnostics in the file.
321
+ */
322
+ checkRange(file: SourceFile, start: number, end: number): boolean {
323
+ return checkSourceFileRange(this.checker, file, start, end);
324
+ }
325
+
326
+ /**
327
+ * The diagnostics of `file`, as `unfilteredDiagnosticsOfFile` gives them, where checkers checked it in
328
+ * ranges and found no diagnostics in it (`checkRange`).
329
+ */
330
+ unfilteredDiagnosticsOfCleanRanges(file: SourceFile): Diagnostic[] {
331
+ const result = this.getBindAndCheckDiagnostics(file, []);
332
+ for (const d of this.getIncludeProcessorDiagnostics(file)) result.push(d);
333
+ return result;
334
+ }
335
+
336
+ /**
337
+ * The diagnostics of `file`, as `unfilteredDiagnosticsOfFile` gives them, from a new checker that checks it
338
+ * whole: for a file checked in ranges that found diagnostics in it (parallel-check.ts), part of which this
339
+ * thread's checker may have checked.
340
+ */
341
+ unfilteredDiagnosticsOfFileOnNewChecker(file: SourceFile): Diagnostic[] {
342
+ const checker = this.createChecker();
343
+ const result = this.getBindAndCheckDiagnostics(file, checker.getDiagnostics(file));
344
+ disposeChecker(checker);
345
+ for (const d of this.getIncludeProcessorDiagnostics(file)) result.push(d);
346
+ return result;
347
+ }
348
+
316
349
  /**
317
350
  * The bind and check diagnostics of `file` before `noEmit` filtering and without include errors.
318
351
  * Once checker threads checked the program, they come from the threads' diagnostics of the file,
@@ -395,10 +428,11 @@ export class CheckedProgram {
395
428
  return sortAndDeduplicateDiagnostics(result);
396
429
  }
397
430
 
398
- private getBindAndCheckDiagnostics(file: SourceFile): Diagnostic[] {
431
+ /** `checks` are the checker's diagnostics of `file`. */
432
+ private getBindAndCheckDiagnostics(file: SourceFile, checks?: readonly Diagnostic[]): Diagnostic[] {
399
433
  const options = this.program.options;
400
434
  const diagnostics = file.bindDiagnostics.slice();
401
- for (const d of this.checker.getDiagnostics(file)) diagnostics.push(d);
435
+ for (const d of checks ?? this.checker.getDiagnostics(file)) diagnostics.push(d);
402
436
  if (isPlainJSFile(file, options)) {
403
437
  const result: Diagnostic[] = [];
404
438
  for (const d of diagnostics) if (plainJSErrors.has(d.code)) result.push(d);
@@ -54,6 +54,7 @@ export interface CheckerThreadData {
54
54
  readonly emits: boolean;
55
55
  readonly emitKinds: number[] | undefined;
56
56
  readonly checking: boolean;
57
+ readonly units: number[];
57
58
  }
58
59
 
59
60
  export interface ParseThreadData {
@@ -75,7 +76,19 @@ export interface ReferenceThreadData {
75
76
  */
76
77
  export function startThreads(spawn: ThreadSpawn, drivers: ThreadDrivers, args: readonly string[]): void {
77
78
  parallelCheck.writer = drivers.outputWriter();
78
- parallelCheck.spawn = (program, index, counter, order, nodeIds, symbolIds, reserved, emits, emitKinds, checking) =>
79
+ parallelCheck.spawn = (
80
+ program,
81
+ index,
82
+ counter,
83
+ order,
84
+ nodeIds,
85
+ symbolIds,
86
+ reserved,
87
+ emits,
88
+ emitKinds,
89
+ checking,
90
+ units,
91
+ ) =>
79
92
  spawn({
80
93
  kind: 'check',
81
94
  program,
@@ -89,6 +102,7 @@ export function startThreads(spawn: ThreadSpawn, drivers: ThreadDrivers, args: r
89
102
  emits,
90
103
  emitKinds,
91
104
  checking,
105
+ units,
92
106
  });
93
107
  if (sharesPublishedGraphs) {
94
108
  parallelParse.spawn = (configFileName, counter, index) =>
@@ -161,6 +175,7 @@ function runCheckerThread(job: CheckerThreadData, drivers: ThreadDrivers): void
161
175
  job.emits,
162
176
  job.emitKinds,
163
177
  job.checking,
178
+ job.units,
164
179
  );
165
180
  // Packed results are copied: nothing this thread allocated stays reachable after it ends.
166
181
  if (packsResults) parentPort!.postMessage(packResult(result, program!));
@@ -70,3 +70,26 @@ export function programDiagnostics(
70
70
  return [];
71
71
  }
72
72
  }
73
+
74
+ /**
75
+ * What `semantic` answers for each of `files` and what `global` answers, after checking `files` and
76
+ * no other file. Checking one file can add diagnostics located in another, so every file is checked
77
+ * before any file's diagnostics are collected. With every file of the program listed, the answers are
78
+ * those of the full check.
79
+ */
80
+ export function checkedFileDiagnostics(
81
+ checked: CheckedProgram,
82
+ files: readonly SourceFile[],
83
+ ): { files: (readonly CompilerDiagnostic[])[]; global: readonly CompilerDiagnostic[] } {
84
+ for (const file of files) checked.getSemanticDiagnostics(file);
85
+ const program = checked.program;
86
+ const global = [
87
+ ...program.getConfigFileParsingDiagnostics(),
88
+ ...program.getProgramDiagnostics(),
89
+ ...checked.getGlobalDiagnostics(),
90
+ ];
91
+ return {
92
+ files: files.map((file) => checked.getSemanticDiagnostics(file)),
93
+ global: sortAndDeduplicateDiagnostics(global).filter((d) => d.file === undefined),
94
+ };
95
+ }