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.
- package/dist/api/compiler/checker-members.js +6 -2
- package/dist/api/compiler/checker-source-file.d.ts +12 -0
- package/dist/api/compiler/checker-source-file.js +35 -0
- package/dist/api/compiler/checker-symbols.js +2 -1
- package/dist/api/compiler/checker.d.ts +6 -1
- package/dist/api/compiler/checker.js +9 -1
- package/dist/api/compiler/file-loader.d.ts +2 -2
- package/dist/api/compiler/file-loader.js +43 -23
- package/dist/api/compiler/link-tables.d.ts +7 -0
- package/dist/api/compiler/link-tables.js +22 -0
- package/dist/api/compiler/parser.js +15 -14
- package/dist/api/compiler/program-checker.d.ts +18 -0
- package/dist/api/compiler/program-checker.js +35 -2
- package/dist/api/library/diagnostics.d.ts +10 -0
- package/dist/api/library/diagnostics.js +20 -0
- package/package.json +8 -8
- package/src/compiler/checker-members.ts +6 -2
- package/src/compiler/checker-source-file.ts +34 -0
- package/src/compiler/checker-symbols.ts +2 -1
- package/src/compiler/checker.ts +8 -1
- package/src/compiler/file-loader.ts +49 -24
- package/src/compiler/link-tables.ts +32 -0
- package/src/compiler/parallel-check.ts +199 -20
- package/src/compiler/parser.ts +15 -16
- package/src/compiler/program-checker.ts +36 -2
- package/src/compiler/thread-entry.ts +16 -1
- 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(
|
|
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)
|
|
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++)
|
|
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}
|
|
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)
|
|
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(
|
|
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
|
|
package/src/compiler/parser.ts
CHANGED
|
@@ -3846,17 +3846,12 @@ export class Parser extends ScannerErrorReporter {
|
|
|
3846
3846
|
}
|
|
3847
3847
|
|
|
3848
3848
|
parseTypeOrTypePredicate(): TypeNode {
|
|
3849
|
-
|
|
3850
|
-
|
|
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
|
-
|
|
3854
|
-
|
|
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
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
)
|
|
7380
|
-
|
|
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
|
-
|
|
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 = (
|
|
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
|
+
}
|