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.
- package/README.md +1 -1
- package/dist/api/compiler/checker-declaration-order.js +3 -2
- package/dist/api/compiler/checker-declared-types.js +6 -3
- package/dist/api/compiler/checker-deprecation.js +14 -13
- package/dist/api/compiler/checker-destructuring.js +3 -2
- 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-symbol-types.js +5 -5
- package/dist/api/compiler/checker-symbols.js +5 -2
- 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.d.ts +7 -0
- package/dist/api/compiler/parser.js +55 -32
- 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 +9 -9
- package/src/compiler/checker-declaration-order.ts +3 -2
- package/src/compiler/checker-declared-types.ts +6 -3
- package/src/compiler/checker-deprecation.ts +13 -11
- package/src/compiler/checker-destructuring.ts +11 -2
- package/src/compiler/checker-members.ts +6 -2
- package/src/compiler/checker-source-file.ts +34 -0
- package/src/compiler/checker-symbol-types.ts +14 -4
- package/src/compiler/checker-symbols.ts +6 -2
- 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 +55 -32
- 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
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
3825
|
-
|
|
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
|
-
|
|
3829
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
)
|
|
7356
|
-
|
|
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
|
}
|