carrick 0.3.116 → 0.3.118
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/contract.d.ts +2 -0
- package/dist/contract.js.map +1 -1
- package/dist/render.js +2 -1
- package/dist/render.js.map +1 -1
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/anchors.d.ts +1 -0
- package/sidecar/dist/src/capture/anchors.js +1 -1
- package/sidecar/dist/src/capture/api.d.ts +6 -0
- package/sidecar/dist/src/capture/check-classify.js +25 -0
- package/sidecar/dist/src/capture/check-probe.d.ts +1 -1
- package/sidecar/dist/src/capture/check-probe.js +20 -0
- package/sidecar/dist/src/capture/checkout.d.ts +3 -0
- package/sidecar/dist/src/capture/checkout.js +1 -1
- package/sidecar/dist/src/capture/deep-walk.d.ts +10 -0
- package/sidecar/dist/src/capture/deep-walk.js +15 -0
- package/sidecar/dist/src/capture/guarded-fs.d.ts +6 -0
- package/sidecar/dist/src/capture/guarded-fs.js +10 -0
- package/sidecar/dist/src/capture/index.d.ts +5 -0
- package/sidecar/dist/src/capture/index.js +62 -7
- package/sidecar/dist/src/capture/project-references.d.ts +24 -13
- package/sidecar/dist/src/capture/project-references.js +53 -16
- package/sidecar/dist/src/capture/reference-redirect.d.ts +84 -0
- package/sidecar/dist/src/capture/reference-redirect.js +209 -0
- package/sidecar/dist/src/capture/unresolved.d.ts +8 -0
- package/sidecar/dist/src/capture/unresolved.js +18 -2
- package/sidecar/dist/src/declaration-attribution.d.ts +40 -0
- package/sidecar/dist/src/declaration-attribution.js +66 -0
- package/sidecar/dist/src/index.js +37 -0
- package/sidecar/dist/src/library-claims.js +234 -23
- package/sidecar/dist/src/module-format.d.ts +19 -1
- package/sidecar/dist/src/module-format.js +9 -2
- package/sidecar/dist/src/position-rows.d.ts +95 -0
- package/sidecar/dist/src/position-rows.js +844 -0
- package/sidecar/dist/src/project-loader.d.ts +36 -7
- package/sidecar/dist/src/project-loader.js +70 -28
- package/sidecar/dist/src/retype.js +13 -0
- package/sidecar/dist/src/type-inferrer.d.ts +25 -29
- package/sidecar/dist/src/type-inferrer.js +119 -70
- package/sidecar/dist/src/types.d.ts +78 -2
- package/sidecar/dist/src/validators.d.ts +431 -30
- package/sidecar/dist/src/validators.js +45 -1
|
@@ -49,6 +49,8 @@ const HTTP_METHODS = new Set([
|
|
|
49
49
|
'HEAD',
|
|
50
50
|
'OPTIONS',
|
|
51
51
|
]);
|
|
52
|
+
/** The methods a route may register: every request method, and `ALL` for a member that registers each. */
|
|
53
|
+
const ROUTE_METHODS = new Set([...HTTP_METHODS, 'ALL']);
|
|
52
54
|
/**
|
|
53
55
|
* Interfaces whose members every value of that kind inherits. A key found only
|
|
54
56
|
* on one of these (`constructor`, `toString`, a string's `length`) is not a
|
|
@@ -100,6 +102,18 @@ const MESSAGE_RULES = {
|
|
|
100
102
|
message: true,
|
|
101
103
|
ops: new Set(['send', 'receive', 'request']),
|
|
102
104
|
};
|
|
105
|
+
/**
|
|
106
|
+
* An `http_client` export whose claims in the request use a label v2 field
|
|
107
|
+
* (`hasV2Field`) is read as the store's other roles are: its makers, `of`
|
|
108
|
+
* and `on`, paths and the name-slot rules, plus the method fields. An export
|
|
109
|
+
* whose claims use none keeps the #1564 checks exactly (`HTTP_RULES`).
|
|
110
|
+
*/
|
|
111
|
+
const HTTP_V2_RULES = { http: false, message: true, ops: new Set(['request']), methods: HTTP_METHODS };
|
|
112
|
+
/**
|
|
113
|
+
* A server framework's route members: a `receive` op whose name is the path,
|
|
114
|
+
* the method from the member's own name (`get` registers GET) or from a slot.
|
|
115
|
+
*/
|
|
116
|
+
const SERVER_RULES = { http: false, message: true, ops: new Set(['receive']), methods: ROUTE_METHODS };
|
|
103
117
|
/**
|
|
104
118
|
* The role table: the role alone picks the checks. A role this build does
|
|
105
119
|
* not read yet (GraphQL executors, server mounts) answers `role_unsupported`,
|
|
@@ -111,9 +125,21 @@ const ROLE_TABLE = {
|
|
|
111
125
|
in_process_bus: MESSAGE_RULES,
|
|
112
126
|
socket: MESSAGE_RULES,
|
|
113
127
|
graphql_client: undefined,
|
|
114
|
-
server_framework:
|
|
128
|
+
server_framework: SERVER_RULES,
|
|
115
129
|
none: undefined,
|
|
116
130
|
};
|
|
131
|
+
/**
|
|
132
|
+
* Whether a claim uses a field label structure v2 added (carrick#2434). An
|
|
133
|
+
* `http_client` export with one such claim in the request is read under
|
|
134
|
+
* `HTTP_V2_RULES`.
|
|
135
|
+
*/
|
|
136
|
+
function hasV2Field(claim) {
|
|
137
|
+
if (claim.kind === 'make')
|
|
138
|
+
return claim.runs !== undefined;
|
|
139
|
+
if (claim.kind !== 'op')
|
|
140
|
+
return false;
|
|
141
|
+
return claim.method_default !== undefined || claim.base !== undefined || claim.runs !== undefined || claim.yields !== undefined;
|
|
142
|
+
}
|
|
117
143
|
const VERIFIED = { verdict: 'verified' };
|
|
118
144
|
const failed = (reason) => ({ verdict: 'failed', reason });
|
|
119
145
|
const unchecked = (reason) => ({ verdict: 'unchecked', reason });
|
|
@@ -230,6 +256,13 @@ export class LibraryClaimsVerifier {
|
|
|
230
256
|
const factoryKeys = new Map();
|
|
231
257
|
// The roles each (package, export) is given across the request.
|
|
232
258
|
const roles = new Map();
|
|
259
|
+
// The `http_client` exports read under the label v2 rules.
|
|
260
|
+
const v2Exports = new Set();
|
|
261
|
+
for (const check of checks) {
|
|
262
|
+
if (check.role === 'http_client' && hasV2Field(check.claim))
|
|
263
|
+
v2Exports.add(exportKey(check));
|
|
264
|
+
}
|
|
265
|
+
const rulesFor = (check) => check.role === 'http_client' && v2Exports.has(exportKey(check)) ? HTTP_V2_RULES : ROLE_TABLE[check.role];
|
|
233
266
|
for (const check of checks) {
|
|
234
267
|
const key = exportKey(check);
|
|
235
268
|
if (!importIndex.has(key)) {
|
|
@@ -260,7 +293,7 @@ export class LibraryClaimsVerifier {
|
|
|
260
293
|
const builtLines = [];
|
|
261
294
|
const builtIndex = new Map();
|
|
262
295
|
for (const check of checks) {
|
|
263
|
-
if (!
|
|
296
|
+
if (!rulesFor(check)?.message)
|
|
264
297
|
continue;
|
|
265
298
|
const built = builtReceiver(check);
|
|
266
299
|
if (built === undefined)
|
|
@@ -326,6 +359,7 @@ export class LibraryClaimsVerifier {
|
|
|
326
359
|
const receiverPath = parseReceiver(check.receiver);
|
|
327
360
|
let types = [exported.type];
|
|
328
361
|
let makerBindsName = false;
|
|
362
|
+
let makerHolding;
|
|
329
363
|
if (receiverPath.maker) {
|
|
330
364
|
const maker = receiverPath.maker;
|
|
331
365
|
const makers = checks
|
|
@@ -336,10 +370,12 @@ export class LibraryClaimsVerifier {
|
|
|
336
370
|
other.claim.kind === 'make' &&
|
|
337
371
|
other.claim.form === maker.form &&
|
|
338
372
|
other.claim.member === maker.member);
|
|
339
|
-
const
|
|
373
|
+
const makerJudgements = makers.map(({ i }) => judge(i));
|
|
374
|
+
const made = holdingReturns(makerJudgements, atDefaults(check, receiverPath.base));
|
|
340
375
|
if ('reason' in made)
|
|
341
376
|
return { reason: made.reason === 'unresolved' ? 'maker_unresolved' : 'maker_unverified' };
|
|
342
377
|
types = made.value;
|
|
378
|
+
makerHolding = makerJudgements.reduce((common, judgement) => common.filter(sig => judgement.holding.includes(sig)), makerJudgements[0].holding);
|
|
343
379
|
makerBindsName = makers.some(({ other }) => other.claim.kind === 'make' && other.claim.name !== undefined);
|
|
344
380
|
}
|
|
345
381
|
if (receiverPath.scope !== undefined) {
|
|
@@ -366,6 +402,7 @@ export class LibraryClaimsVerifier {
|
|
|
366
402
|
home: reader.homeOf(check.package, [exported.target, ...typeSymbols(type)]),
|
|
367
403
|
makerBindsName,
|
|
368
404
|
scoped: receiverPath.scope !== undefined,
|
|
405
|
+
makerHolding,
|
|
369
406
|
})),
|
|
370
407
|
};
|
|
371
408
|
};
|
|
@@ -401,7 +438,7 @@ export class LibraryClaimsVerifier {
|
|
|
401
438
|
};
|
|
402
439
|
const judgeCheck = (index) => {
|
|
403
440
|
const check = checks[index];
|
|
404
|
-
const rules =
|
|
441
|
+
const rules = rulesFor(check);
|
|
405
442
|
if (!rules)
|
|
406
443
|
return { outcome: unchecked('role_unsupported') };
|
|
407
444
|
// An export given two roles is classified two ways; neither is a fact.
|
|
@@ -446,7 +483,7 @@ export class LibraryClaimsVerifier {
|
|
|
446
483
|
const builtAtDefaults = built === undefined ? undefined : atDefaults(check, built);
|
|
447
484
|
const holding = [];
|
|
448
485
|
for (const receiver of receivers.value) {
|
|
449
|
-
const result = reader.judgeMessage(receiver, check.claim, builtAtDefaults);
|
|
486
|
+
const result = reader.judgeMessage(receiver, check.claim, rules, builtAtDefaults);
|
|
450
487
|
if (result.outcome.verdict !== 'verified')
|
|
451
488
|
return result;
|
|
452
489
|
holding.push(...(result.holding ?? []));
|
|
@@ -1285,14 +1322,14 @@ class DeclarationReader {
|
|
|
1285
1322
|
* overloads it holds on. `built` is the probe's no-argument build of the
|
|
1286
1323
|
* receiver a maker or scope claim makes (see `returnOf`).
|
|
1287
1324
|
*/
|
|
1288
|
-
judgeMessage(receiver, claim, built) {
|
|
1325
|
+
judgeMessage(receiver, claim, rules, built) {
|
|
1289
1326
|
switch (claim.kind) {
|
|
1290
1327
|
case 'make':
|
|
1291
1328
|
return this.judgeMake(receiver, claim, built);
|
|
1292
1329
|
case 'scope':
|
|
1293
1330
|
return this.judgeScope(receiver, claim, built);
|
|
1294
1331
|
case 'op':
|
|
1295
|
-
return { outcome: this.judgeOp(receiver, claim) };
|
|
1332
|
+
return { outcome: this.judgeOp(receiver, claim, rules) };
|
|
1296
1333
|
case 'reserved':
|
|
1297
1334
|
return { outcome: this.judgeReserved(receiver, claim) };
|
|
1298
1335
|
}
|
|
@@ -1308,7 +1345,13 @@ class DeclarationReader {
|
|
|
1308
1345
|
* `handler` slot; a queue (`new Queue("emails")`) one with a positional name.
|
|
1309
1346
|
*/
|
|
1310
1347
|
judgeMake(receiver, claim, built) {
|
|
1311
|
-
const layout = this.layout({
|
|
1348
|
+
const layout = this.layout({
|
|
1349
|
+
base: claim.base,
|
|
1350
|
+
prefix: claim.prefix,
|
|
1351
|
+
name: claim.name,
|
|
1352
|
+
handler: claim.handler,
|
|
1353
|
+
runs: claim.runs,
|
|
1354
|
+
});
|
|
1312
1355
|
if ('failure' in layout)
|
|
1313
1356
|
return { outcome: layout.failure };
|
|
1314
1357
|
const callee = this.ownCallee(receiver, [], claim.member, claim.form);
|
|
@@ -1368,23 +1411,48 @@ class DeclarationReader {
|
|
|
1368
1411
|
* instance's maker claim has a `name` slot, or the receiver came through a
|
|
1369
1412
|
* scope.
|
|
1370
1413
|
*/
|
|
1371
|
-
judgeOp(receiver, claim) {
|
|
1372
|
-
|
|
1414
|
+
judgeOp(receiver, claim, rules) {
|
|
1415
|
+
const method = this.methodOutcome(claim, rules);
|
|
1416
|
+
if (method)
|
|
1417
|
+
return method;
|
|
1418
|
+
// What a handler is handed is read on a receive op's handler only.
|
|
1419
|
+
if (claim.yields !== undefined && (claim.op !== 'receive' || claim.handler === undefined)) {
|
|
1373
1420
|
return unchecked('claim_invalid');
|
|
1374
1421
|
}
|
|
1375
1422
|
const name = claim.name;
|
|
1376
|
-
// A send carries a payload: a name alone on `send(data)` is the data.
|
|
1377
|
-
|
|
1423
|
+
// A send carries a payload: a name alone on `send(data)` is the data. An
|
|
1424
|
+
// op that runs a function or client it is handed takes its name from
|
|
1425
|
+
// what it runs, and one that hands its handler a receiver (a server's
|
|
1426
|
+
// connection event) names nothing on the wire itself, so neither needs a
|
|
1427
|
+
// name of its own.
|
|
1428
|
+
const nameless = claim.runs !== undefined || claim.yields !== undefined;
|
|
1429
|
+
if ((name === undefined && !nameless) || (claim.op === 'send' && claim.payload === undefined)) {
|
|
1378
1430
|
return unchecked('claim_invalid');
|
|
1431
|
+
}
|
|
1379
1432
|
let nameSlot;
|
|
1380
|
-
if (
|
|
1433
|
+
if (name === undefined) {
|
|
1434
|
+
nameSlot = undefined;
|
|
1435
|
+
}
|
|
1436
|
+
else if ('bound' in name) {
|
|
1381
1437
|
if (name.bound === 'maker' ? !receiver.makerBindsName : !receiver.scoped)
|
|
1382
1438
|
return failed('name_unbound');
|
|
1383
1439
|
}
|
|
1384
1440
|
else {
|
|
1385
1441
|
nameSlot = name;
|
|
1386
1442
|
}
|
|
1387
|
-
const
|
|
1443
|
+
const runs = claim.runs;
|
|
1444
|
+
const boundRuns = runs?.bound === 'maker' ? runs : undefined;
|
|
1445
|
+
if (boundRuns && !receiver.makerHolding)
|
|
1446
|
+
return failed('runs_unbound');
|
|
1447
|
+
const layout = this.layout({
|
|
1448
|
+
name: nameSlot,
|
|
1449
|
+
payload: claim.payload,
|
|
1450
|
+
handler: claim.handler,
|
|
1451
|
+
ack: claim.ack,
|
|
1452
|
+
base: claim.base,
|
|
1453
|
+
method: claim.method_key,
|
|
1454
|
+
runs: runs && !boundRuns ? { arg: runs.arg, ...(runs.key === undefined ? {} : { key: runs.key }) } : undefined,
|
|
1455
|
+
});
|
|
1388
1456
|
if ('failure' in layout)
|
|
1389
1457
|
return layout.failure;
|
|
1390
1458
|
const callee = this.ownCallee(receiver, claim.path ?? [], claim.member, 'call');
|
|
@@ -1392,13 +1460,131 @@ class DeclarationReader {
|
|
|
1392
1460
|
return callee.failure;
|
|
1393
1461
|
const labels = claim.key_labels ?? {};
|
|
1394
1462
|
let best;
|
|
1463
|
+
let held = false;
|
|
1395
1464
|
for (const signature of callee.signatures) {
|
|
1396
|
-
|
|
1397
|
-
if (result.rank === Infinity)
|
|
1398
|
-
|
|
1465
|
+
let result = this.messageSignatureOutcome(signature, layout.value, labels);
|
|
1466
|
+
if (result.rank === Infinity && claim.yields !== undefined) {
|
|
1467
|
+
const given = this.yieldsOutcome(signature, claim.handler, claim.yields, receiver);
|
|
1468
|
+
if (given)
|
|
1469
|
+
result = { rank: 5, outcome: given };
|
|
1470
|
+
}
|
|
1471
|
+
if (result.rank === Infinity) {
|
|
1472
|
+
held = true;
|
|
1473
|
+
break;
|
|
1474
|
+
}
|
|
1399
1475
|
best = furthest(best, result.rank, result.outcome);
|
|
1400
1476
|
}
|
|
1401
|
-
|
|
1477
|
+
if (!held)
|
|
1478
|
+
return best.outcome;
|
|
1479
|
+
if (boundRuns)
|
|
1480
|
+
return this.boundRunsOutcome(receiver.makerHolding, boundRuns);
|
|
1481
|
+
return VERIFIED;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* The method fields of an op (label structure v2, carrick#2434), under the
|
|
1485
|
+
* role's method words. Without method words (the message roles), an op
|
|
1486
|
+
* carrying `method`, `method_key`, `options`, `method_default` or `base` is
|
|
1487
|
+
* `claim_invalid`, as before. With them:
|
|
1488
|
+
* - `method` is the member's own name, upper-cased (ASCII only), and a
|
|
1489
|
+
* method word: `get` registers or sends GET (`method_not_member_verb`);
|
|
1490
|
+
* - `method_key` is where the method sits, a slot that accepts a string or
|
|
1491
|
+
* a method literal (read with the call's other parts);
|
|
1492
|
+
* - `method_default` is a method word (`method_not_word`): beside
|
|
1493
|
+
* `method_key`, the method when that option is absent; alone, the one
|
|
1494
|
+
* method the call sends. Beside `method`, it says nothing new and is
|
|
1495
|
+
* `claim_invalid`, as is `method` beside `method_key`;
|
|
1496
|
+
* - `options` belongs to the #1564 checks and is `claim_invalid`.
|
|
1497
|
+
* Undefined when the fields hold so far; their slots are read with the
|
|
1498
|
+
* call's other parts.
|
|
1499
|
+
*/
|
|
1500
|
+
methodOutcome(claim, rules) {
|
|
1501
|
+
const words = rules.methods;
|
|
1502
|
+
if (!words) {
|
|
1503
|
+
const any = claim.method !== undefined ||
|
|
1504
|
+
claim.method_key !== undefined ||
|
|
1505
|
+
claim.options !== undefined ||
|
|
1506
|
+
claim.method_default !== undefined ||
|
|
1507
|
+
claim.base !== undefined;
|
|
1508
|
+
return any ? unchecked('claim_invalid') : undefined;
|
|
1509
|
+
}
|
|
1510
|
+
if (claim.options !== undefined)
|
|
1511
|
+
return unchecked('claim_invalid');
|
|
1512
|
+
if (claim.method !== undefined && (claim.method_key !== undefined || claim.method_default !== undefined)) {
|
|
1513
|
+
return unchecked('claim_invalid');
|
|
1514
|
+
}
|
|
1515
|
+
if (claim.method !== undefined) {
|
|
1516
|
+
if (claim.member === null || claim.method !== asciiUpperCase(claim.member) || !words.has(claim.method)) {
|
|
1517
|
+
return failed('method_not_member_verb');
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
if (claim.method_default !== undefined && !words.has(claim.method_default))
|
|
1521
|
+
return failed('method_not_word');
|
|
1522
|
+
return undefined;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* `runs: { bound: 'maker', arg, key }`: some overload the instance's maker
|
|
1526
|
+
* holds on takes a function with a declared signature there (the function
|
|
1527
|
+
* a mutation object's `mutate` runs is the one its maker was given).
|
|
1528
|
+
*/
|
|
1529
|
+
boundRunsOutcome(holding, runs) {
|
|
1530
|
+
let best;
|
|
1531
|
+
for (const signature of holding) {
|
|
1532
|
+
let declared = this.keyParameterAt(signature, runs.arg);
|
|
1533
|
+
if (declared !== undefined && runs.key !== undefined) {
|
|
1534
|
+
const property = this.declaredProperty(declared, runs.key);
|
|
1535
|
+
if (!property) {
|
|
1536
|
+
best = furthest(best, 2, failed('key_missing'));
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
declared = this.checker.getTypeOfSymbol(property);
|
|
1540
|
+
}
|
|
1541
|
+
const failure = this.runsFailure(declared);
|
|
1542
|
+
if (!failure)
|
|
1543
|
+
return VERIFIED;
|
|
1544
|
+
best = furthest(best, 3, failure);
|
|
1545
|
+
}
|
|
1546
|
+
return best?.outcome ?? failed('param_missing');
|
|
1547
|
+
}
|
|
1548
|
+
/** A `runs` slot: a function with a declared signature, as a handler is, under its own reasons. */
|
|
1549
|
+
runsFailure(declared) {
|
|
1550
|
+
const failure = this.handlerFailure(declared);
|
|
1551
|
+
if (!failure || failure.verdict === 'verified')
|
|
1552
|
+
return failure;
|
|
1553
|
+
const reason = failure.reason.replace(/^handler_/, 'runs_');
|
|
1554
|
+
return failure.verdict === 'failed' ? failed(reason) : unchecked(reason);
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* `yields: { param, export, side }` on a receive op: some signature of the
|
|
1558
|
+
* handler this overload takes declares a parameter at `param` whose type
|
|
1559
|
+
* is the package's own export named `export` (a class, or the interface
|
|
1560
|
+
* an instance has), declared in the receiver's home packages. `side` is
|
|
1561
|
+
* `client` or `server` (`yields_side_invalid`). Undefined when it holds.
|
|
1562
|
+
*/
|
|
1563
|
+
yieldsOutcome(signature, handler, yields, receiver) {
|
|
1564
|
+
if (yields.side !== 'client' && yields.side !== 'server')
|
|
1565
|
+
return failed('yields_side_invalid');
|
|
1566
|
+
let declared = this.keyParameterAt(signature, handler.arg);
|
|
1567
|
+
if (declared !== undefined && handler.key !== undefined) {
|
|
1568
|
+
const property = this.declaredProperty(declared, handler.key);
|
|
1569
|
+
declared = property ? this.checker.getTypeOfSymbol(property) : undefined;
|
|
1570
|
+
}
|
|
1571
|
+
if (declared === undefined)
|
|
1572
|
+
return failed('param_missing');
|
|
1573
|
+
const slot = this.throughConditional(this.throughConstraint(declared));
|
|
1574
|
+
if (slot === VARIADIC)
|
|
1575
|
+
return unchecked('handler_untyped');
|
|
1576
|
+
for (const part of this.parts(slot)) {
|
|
1577
|
+
for (const handed of part.getCallSignatures()) {
|
|
1578
|
+
const given = this.parameterAt(handed, yields.param);
|
|
1579
|
+
if (given === undefined || given === VARIADIC)
|
|
1580
|
+
continue;
|
|
1581
|
+
const named = typeSymbols(given).some(symbol => symbol?.getName() === yields.export &&
|
|
1582
|
+
(symbol.declarations ?? []).some(declaration => this.isOwnFile(declaration.getSourceFile(), receiver.home)));
|
|
1583
|
+
if (named)
|
|
1584
|
+
return undefined;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
return failed('yields_not_export');
|
|
1402
1588
|
}
|
|
1403
1589
|
/**
|
|
1404
1590
|
* The declarations spell `name` in one of `member`'s parameters: an
|
|
@@ -1462,6 +1648,14 @@ class DeclarationReader {
|
|
|
1462
1648
|
*/
|
|
1463
1649
|
messageSignatureOutcome(signature, layout, labels) {
|
|
1464
1650
|
for (const [arg, part] of layout.positional) {
|
|
1651
|
+
if (part === 'method') {
|
|
1652
|
+
const declared = this.keyParameterAt(signature, arg);
|
|
1653
|
+
if (declared === undefined)
|
|
1654
|
+
return { rank: 1, outcome: failed('param_missing') };
|
|
1655
|
+
if (!this.acceptsMethod(declared))
|
|
1656
|
+
return { rank: 1, outcome: this.slotFailure(declared, 'method_not_string') };
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1465
1659
|
if (part !== 'name' && part !== 'base' && part !== 'prefix')
|
|
1466
1660
|
continue;
|
|
1467
1661
|
const declared = this.keyParameterAt(signature, arg);
|
|
@@ -1486,9 +1680,10 @@ class DeclarationReader {
|
|
|
1486
1680
|
}
|
|
1487
1681
|
}
|
|
1488
1682
|
for (const [arg, part] of layout.positional) {
|
|
1489
|
-
if (part !== 'handler' && part !== 'ack')
|
|
1683
|
+
if (part !== 'handler' && part !== 'ack' && part !== 'runs')
|
|
1490
1684
|
continue;
|
|
1491
|
-
const
|
|
1685
|
+
const declared = this.keyParameterAt(signature, arg);
|
|
1686
|
+
const failure = part === 'runs' ? this.runsFailure(declared) : this.handlerFailure(declared);
|
|
1492
1687
|
if (failure)
|
|
1493
1688
|
return { rank: 3, outcome: failure };
|
|
1494
1689
|
}
|
|
@@ -1537,6 +1732,17 @@ class DeclarationReader {
|
|
|
1537
1732
|
failure = { rank: 3, outcome: failed('payload_is_function') };
|
|
1538
1733
|
break;
|
|
1539
1734
|
}
|
|
1735
|
+
if (part === 'method' && !this.acceptsMethod(type)) {
|
|
1736
|
+
failure = { rank: 3, outcome: this.slotFailure(type, 'method_not_string') };
|
|
1737
|
+
break;
|
|
1738
|
+
}
|
|
1739
|
+
if (part === 'runs') {
|
|
1740
|
+
const bad = this.runsFailure(type);
|
|
1741
|
+
if (bad) {
|
|
1742
|
+
failure = { rank: 3, outcome: bad };
|
|
1743
|
+
break;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1540
1746
|
if (part === 'handler' || part === 'ack') {
|
|
1541
1747
|
const bad = this.handlerFailure(type);
|
|
1542
1748
|
if (bad) {
|
|
@@ -2690,16 +2896,21 @@ class DeclarationReader {
|
|
|
2690
2896
|
}
|
|
2691
2897
|
/**
|
|
2692
2898
|
* What a factory builds says nothing: the type itself, or any branch of a
|
|
2693
|
-
* conditional return type, does.
|
|
2899
|
+
* conditional return type, does. A conditional whose branches lead back to
|
|
2900
|
+
* itself (a recursive alias) is read once: `seen` holds the conditionals
|
|
2901
|
+
* on the way down, and one met again says nothing new.
|
|
2694
2902
|
*/
|
|
2695
|
-
returnSaysNothing(type) {
|
|
2903
|
+
returnSaysNothing(type, seen = new Set()) {
|
|
2696
2904
|
if (this.saysNothing(type))
|
|
2697
2905
|
return true;
|
|
2698
2906
|
return this.parts(type).some(part => {
|
|
2699
2907
|
if (!(part.flags & ts.TypeFlags.Conditional))
|
|
2700
2908
|
return false;
|
|
2701
2909
|
const node = part.root.node;
|
|
2702
|
-
|
|
2910
|
+
if (seen.has(node))
|
|
2911
|
+
return false;
|
|
2912
|
+
seen.add(node);
|
|
2913
|
+
return [node.trueType, node.falseType].some(branch => this.returnSaysNothing(this.checker.getTypeFromTypeNode(branch), seen));
|
|
2703
2914
|
});
|
|
2704
2915
|
}
|
|
2705
2916
|
/** `any` (an unresolved type included) or `unknown`. */
|
|
@@ -29,4 +29,22 @@
|
|
|
29
29
|
* and is left unresolved rather than given another import's mode.
|
|
30
30
|
*/
|
|
31
31
|
import { type ResolutionHostFactory } from 'ts-morph';
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The editor's project-reference rule for the program's owner config
|
|
34
|
+
* (`ReferenceRedirect` in `capture/reference-redirect.ts`, carrick#2418),
|
|
35
|
+
* restated here because this module is outside the capture seam.
|
|
36
|
+
*/
|
|
37
|
+
export interface ResolutionRedirect {
|
|
38
|
+
referenceFor(containingFile: string): unknown;
|
|
39
|
+
resolutionHost<H extends {
|
|
40
|
+
fileExists(fileName: string): boolean;
|
|
41
|
+
}>(host: H): H;
|
|
42
|
+
sourceAnswer<R extends {
|
|
43
|
+
resolvedFileName: string;
|
|
44
|
+
} | undefined>(answer: R): R;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The resolution host factory, resolving through `redirect` when the owner
|
|
48
|
+
* config references other projects.
|
|
49
|
+
*/
|
|
50
|
+
export declare const moduleFormatResolutionHost: (redirect?: ResolutionRedirect) => ResolutionHostFactory;
|
|
@@ -55,7 +55,12 @@ function usagesOf(file) {
|
|
|
55
55
|
}
|
|
56
56
|
/** Why a module name cannot be given one mode: see the header. */
|
|
57
57
|
const UNPLACED = Symbol('unplaced');
|
|
58
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The resolution host factory, resolving through `redirect` when the owner
|
|
60
|
+
* config references other projects.
|
|
61
|
+
*/
|
|
62
|
+
export const moduleFormatResolutionHost = (redirect) => (host, getOptions) => {
|
|
63
|
+
const resolutionHost = redirect ? redirect.resolutionHost(host) : host;
|
|
59
64
|
let cache;
|
|
60
65
|
let cacheOptions;
|
|
61
66
|
const cacheFor = (options) => {
|
|
@@ -95,7 +100,9 @@ export const moduleFormatResolutionHost = (host, getOptions) => {
|
|
|
95
100
|
const mode = modeOf(name);
|
|
96
101
|
if (mode === UNPLACED)
|
|
97
102
|
return undefined;
|
|
98
|
-
|
|
103
|
+
const answer = ts.resolveModuleName(name, containingFile, compilerOptions, resolutionHost, resolutionCache, redirectedReference ??
|
|
104
|
+
redirect?.referenceFor(containingFile), mode).resolvedModule;
|
|
105
|
+
return redirect ? redirect.sourceAnswer(answer) : answer;
|
|
99
106
|
});
|
|
100
107
|
},
|
|
101
108
|
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-position compiler rows for a service (carrick#2405, move 2 of the fact
|
|
3
|
+
* layer). Record-only: nothing in a scan reads the file yet.
|
|
4
|
+
*
|
|
5
|
+
* For every source file the scanner sends, one row for each position of six
|
|
6
|
+
* kinds, each holding what the compiler knows there: the symbol, the
|
|
7
|
+
* declaration it is attributed to (file and line), the package that declares
|
|
8
|
+
* it and the type text.
|
|
9
|
+
*
|
|
10
|
+
* | kind | node | symbol of | type of |
|
|
11
|
+
* |-----------|---------------------------------------------------|---------------------------|----------------|
|
|
12
|
+
* | `call` | call, `new`, tagged template | the callee's name | the call |
|
|
13
|
+
* | `member` | property or element access | the name or the argument | the access |
|
|
14
|
+
* | `compare` | `=== !== == != < > <= >= instanceof in` | the left operand | the left operand |
|
|
15
|
+
* | `ident` | an identifier in expression position | itself | itself |
|
|
16
|
+
* | `impl` | a `call` or `member` whose declaration has no body | an implementation of it | nothing |
|
|
17
|
+
* | `jsx` | a JSX opening or self-closing element's tag | the tag's name | the tag |
|
|
18
|
+
*
|
|
19
|
+
* An `impl` row (version 2, carrick#2430) follows the `call` or `member` row
|
|
20
|
+
* it belongs to and has its span: one per declaration that go-to-implementation
|
|
21
|
+
* names for that row's declaration, when that declaration is an interface's
|
|
22
|
+
* or type literal's method, a function-typed property signature, an abstract
|
|
23
|
+
* member, or a function-typed class property or parameter with no
|
|
24
|
+
* initializer. Its `decl_*` name the implementing declaration. Past
|
|
25
|
+
* {@link IMPL_CAP} the row is one, with {@link FLAG_TOO_MANY} and nothing else.
|
|
26
|
+
*
|
|
27
|
+
* A row's key is `(start, end, kind)`, except that several `impl` rows share
|
|
28
|
+
* one: TypeScript `getStart()`/`getEnd()` of the node, in UTF-16 code units
|
|
29
|
+
* from the start of the file, which is what the scanner's `sidecar_position`
|
|
30
|
+
* gives for an SWC span. Every path written is relative to the scanned repo's
|
|
31
|
+
* root, with `/` separators.
|
|
32
|
+
*
|
|
33
|
+
* Each file is read in the program of the project that owns it
|
|
34
|
+
* (`ProjectLoader.projectKeyFor`, carrick#1604), one owner's program at a
|
|
35
|
+
* time: a group's program is released before the next one is built. A file
|
|
36
|
+
* the owner's program does not take (JavaScript under a config without
|
|
37
|
+
* `allowJs`) is read after every owner, in that owner's `scripts` program
|
|
38
|
+
* (`ProjectLoader.scriptProjectFor`, carrick#2413), with the owner's root
|
|
39
|
+
* declaration files beside it so that the globals they declare are declared.
|
|
40
|
+
*
|
|
41
|
+
* The file format is pinned on the scanner side by `src/position_rows.rs`.
|
|
42
|
+
*/
|
|
43
|
+
import { ts } from 'ts-morph';
|
|
44
|
+
import type { ProjectLoader } from './project-loader.js';
|
|
45
|
+
import type { PositionRowsCounts } from './types.js';
|
|
46
|
+
/** The format's version, written in the header. */
|
|
47
|
+
export declare const POSITION_ROWS_VERSION = 2;
|
|
48
|
+
/** The type is `any`, declared or inferred. Never set with the error type. */
|
|
49
|
+
export declare const FLAG_ANY = 1;
|
|
50
|
+
/** The type is the compiler's error type: something did not resolve. */
|
|
51
|
+
export declare const FLAG_ERROR_TYPE = 2;
|
|
52
|
+
/** The checker threw at this node; the row holds nothing else. */
|
|
53
|
+
export declare const FLAG_CHECKER_THREW = 4;
|
|
54
|
+
/** The declaration is outside the checkout, not the default library and in no installed package. */
|
|
55
|
+
export declare const FLAG_DECLARATION_UNNAMED = 8;
|
|
56
|
+
/** An `impl` row standing for more implementations than {@link IMPL_CAP}; it names none. */
|
|
57
|
+
export declare const FLAG_TOO_MANY = 16;
|
|
58
|
+
/** The most implementations one declaration's `impl` rows list. */
|
|
59
|
+
export declare const IMPL_CAP = 16;
|
|
60
|
+
/** The kinds {@link kindOf} gives a node; `impl` and `jsx` rows are written beside them. */
|
|
61
|
+
export type NodeKind = 'call' | 'member' | 'compare' | 'ident';
|
|
62
|
+
export type PositionKind = NodeKind | 'impl' | 'jsx';
|
|
63
|
+
/** `[kind, start, end, symbol, decl_file, decl_line, decl_package, type_text, flags]` */
|
|
64
|
+
export type PositionRow = [
|
|
65
|
+
PositionKind,
|
|
66
|
+
number,
|
|
67
|
+
number,
|
|
68
|
+
string | null,
|
|
69
|
+
string | null,
|
|
70
|
+
number | null,
|
|
71
|
+
string | null,
|
|
72
|
+
string | null,
|
|
73
|
+
number
|
|
74
|
+
];
|
|
75
|
+
export interface PositionRowsOptions {
|
|
76
|
+
loader: ProjectLoader;
|
|
77
|
+
/** Absolute paths, written in this order. */
|
|
78
|
+
files: string[];
|
|
79
|
+
scanRoot: string;
|
|
80
|
+
outPath: string;
|
|
81
|
+
service: string;
|
|
82
|
+
/** Called after each file is walked or skipped. */
|
|
83
|
+
onFile?: () => void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Walk `files`, write their rows to `outPath` and say what was written.
|
|
87
|
+
*
|
|
88
|
+
* The file is written beside `outPath` as `<outPath>.partial` and renamed
|
|
89
|
+
* over it, so a walk that dies leaves no half-written rows file. Every write
|
|
90
|
+
* goes through a {@link WriteGuard} over the file's directory, which refuses
|
|
91
|
+
* a directory inside the scanned trees anywhere but beneath `.carrick`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function writePositionRows(options: PositionRowsOptions): PositionRowsCounts;
|
|
94
|
+
/** The kind of row a node gives, if any (exported for the editor-parity test, carrick#2418). */
|
|
95
|
+
export declare function kindOf(node: ts.Node): NodeKind | undefined;
|