carrick 0.3.104 → 0.3.106
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/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/anchors.d.ts +13 -0
- package/sidecar/dist/src/capture/anchors.js +229 -37
- package/sidecar/dist/src/capture/api.d.ts +64 -4
- package/sidecar/dist/src/capture/check-classify.d.ts +10 -1
- package/sidecar/dist/src/capture/check-classify.js +23 -12
- package/sidecar/dist/src/capture/check-fields.js +4 -6
- package/sidecar/dist/src/capture/check-probe.js +16 -6
- package/sidecar/dist/src/capture/check-scrub.d.ts +3 -0
- package/sidecar/dist/src/capture/check-scrub.js +8 -4
- package/sidecar/dist/src/capture/check.js +75 -49
- package/sidecar/dist/src/capture/deep-walk.d.ts +20 -0
- package/sidecar/dist/src/capture/deep-walk.js +51 -11
- package/sidecar/dist/src/capture/guarded-fs.d.ts +5 -1
- package/sidecar/dist/src/capture/guarded-fs.js +23 -1
- package/sidecar/dist/src/capture/index.js +14 -2
- package/sidecar/dist/src/capture/member-name.d.ts +20 -0
- package/sidecar/dist/src/capture/member-name.js +24 -0
- package/sidecar/dist/src/capture/self-check.js +50 -9
- package/sidecar/dist/src/capture/service-config.d.ts +2 -0
- package/sidecar/dist/src/capture/service-config.js +1 -1
- package/sidecar/dist/src/failure-path.d.ts +67 -0
- package/sidecar/dist/src/failure-path.js +236 -0
- package/sidecar/dist/src/printed-names.d.ts +43 -0
- package/sidecar/dist/src/printed-names.js +186 -0
- package/sidecar/dist/src/retype.js +60 -99
- package/sidecar/dist/src/type-inferrer.d.ts +35 -3
- package/sidecar/dist/src/type-inferrer.js +208 -13
- package/sidecar/dist/src/type-structural-expander.js +10 -1
- package/sidecar/dist/src/types.d.ts +24 -0
- package/sidecar/dist/src/validators.d.ts +76 -0
- package/sidecar/dist/src/validators.js +8 -0
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
import * as path from 'node:path';
|
|
21
21
|
import { Node, SyntaxKind, ts, } from 'ts-morph';
|
|
22
22
|
import { validateInferRequestItem } from './validators.js';
|
|
23
|
+
import { notePrintedType, PrintedTypes } from './printed-names.js';
|
|
23
24
|
import { externalImportsOf, isExternalOrigin } from './origin.js';
|
|
25
|
+
import { reachedOnlyOnFailure } from './failure-path.js';
|
|
24
26
|
import { addedDiagnostics, applyInsertions, fileDiagnostics, literalInsertions, mapBack, mapForward, normalise, pathOf, } from './unwidened.js';
|
|
25
27
|
import { expandTypeStructural, } from './type-structural-expander.js';
|
|
26
28
|
/**
|
|
@@ -219,6 +221,14 @@ const REQUEST_BODY_PARTS = new Set(['json', 'form', 'body']);
|
|
|
219
221
|
* these names; a type parameter alone also types response modes and fallbacks.
|
|
220
222
|
*/
|
|
221
223
|
const BODY_MEMBER_NAMES = new Set(['data', 'body', 'json']);
|
|
224
|
+
/**
|
|
225
|
+
* The zero-argument reads that take the WHOLE body out of a transport response
|
|
226
|
+
* (the platform `Response` and the clients that copy its shape): parsed as
|
|
227
|
+
* JSON, or as raw text (carrick#1842).
|
|
228
|
+
*/
|
|
229
|
+
const WHOLE_BODY_READS = new Set(['json', 'text']);
|
|
230
|
+
/** The body format a caller names to read a response as raw text (carrick#1842). */
|
|
231
|
+
const RAW_TEXT_FORMAT = 'text';
|
|
222
232
|
/**
|
|
223
233
|
* Print a `Type` to its string form WITHOUT the compiler's default truncation.
|
|
224
234
|
*
|
|
@@ -229,7 +239,18 @@ const BODY_MEMBER_NAMES = new Set(['data', 'body', 'json']);
|
|
|
229
239
|
*/
|
|
230
240
|
const TYPE_TEXT_FLAGS = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.InTypeAlias;
|
|
231
241
|
function typeText(type, enclosingNode) {
|
|
232
|
-
|
|
242
|
+
const text = type.getText(enclosingNode, TYPE_TEXT_FLAGS);
|
|
243
|
+
notePrintedType(type, enclosingNode, text);
|
|
244
|
+
return text;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* `text` is `string`, alone or beside `null` and `undefined` in either order:
|
|
248
|
+
* the only payload a raw-text read publishes (carrick#1842).
|
|
249
|
+
*/
|
|
250
|
+
function isBareStringText(text) {
|
|
251
|
+
const members = text.split('|').map((member) => member.trim());
|
|
252
|
+
return (members.includes('string') &&
|
|
253
|
+
members.every((member) => member === 'string' || member === 'null' || member === 'undefined'));
|
|
233
254
|
}
|
|
234
255
|
/**
|
|
235
256
|
* How long the unwidened reading of one batch may take by default. The
|
|
@@ -309,8 +330,14 @@ export class TypeInferrer {
|
|
|
309
330
|
errors.push(`Invalid infer item at ${request.file_path}:${loc} (${request.infer_kind}): ${itemError}`);
|
|
310
331
|
continue;
|
|
311
332
|
}
|
|
312
|
-
|
|
333
|
+
// carrick#1836: what the names the published text prints meant, read
|
|
334
|
+
// while the program the prints were made in is still the project's.
|
|
335
|
+
// The unwidened re-read below records nothing: it prints the same
|
|
336
|
+
// node, and its reading has a budget.
|
|
337
|
+
const prints = new PrintedTypes();
|
|
338
|
+
const result = prints.during(() => this.inferSingle(request, extractionConfig));
|
|
313
339
|
if (result) {
|
|
340
|
+
this.recordPrintedNames(result, request, prints);
|
|
314
341
|
inferredTypes.push(result);
|
|
315
342
|
if (request.infer_kind === 'response_body' || request.infer_kind === 'function_return') {
|
|
316
343
|
responses.push({ request, result });
|
|
@@ -340,6 +367,18 @@ export class TypeInferrer {
|
|
|
340
367
|
errors: errors.length > 0 ? errors : undefined,
|
|
341
368
|
};
|
|
342
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* carrick#1836: list on `result` the declarations behind the names its text
|
|
372
|
+
* prints that the request's file cannot resolve (`PrintedTypes.namesIn`).
|
|
373
|
+
*/
|
|
374
|
+
recordPrintedNames(result, request, prints) {
|
|
375
|
+
const sourceFile = this.getSourceFile(request.file_path);
|
|
376
|
+
if (!sourceFile)
|
|
377
|
+
return;
|
|
378
|
+
const printedNames = prints.namesIn([result.type_string], sourceFile.compilerNode, this.project.getTypeChecker().compilerObject);
|
|
379
|
+
if (printedNames.length > 0)
|
|
380
|
+
result.printed_names = printedNames;
|
|
381
|
+
}
|
|
343
382
|
/**
|
|
344
383
|
* carrick#1516: read each response inference again with the literals on its
|
|
345
384
|
* handler's path marked `as const`, and record the narrower type the handler
|
|
@@ -1208,16 +1247,55 @@ export class TypeInferrer {
|
|
|
1208
1247
|
const carrierCandidate = unwrapResult.wasUnwrapped || explicitType
|
|
1209
1248
|
? undefined
|
|
1210
1249
|
: this.resultCarrierPayload(returnType, terminalNode, use.projections, `${request.file_path}:${request.line_number}`);
|
|
1250
|
+
// carrick#1841: what the carrier holds goes through the service's wrapper
|
|
1251
|
+
// rules before it is published, as the call's own result did. The carrier
|
|
1252
|
+
// is found by its shape, so what it holds can still be a library's
|
|
1253
|
+
// envelope: a request library answers `Task<Outcome<Reply<T>, E>>`, and
|
|
1254
|
+
// `Reply<T>` is that library's response object, a status, a url and
|
|
1255
|
+
// headers around the body. Where a rule reads a payload out of it, that
|
|
1256
|
+
// payload is the body. Where a rule verifies it as the library's transport
|
|
1257
|
+
// and reads nothing out of it, this site states no contract, and the
|
|
1258
|
+
// transport object must not stand in for one: judged against a
|
|
1259
|
+
// producer's body, every member it adds reads as a field the producer
|
|
1260
|
+
// does not send.
|
|
1261
|
+
//
|
|
1262
|
+
// The abstain is decided, so it rides the row (`machinery_envelope` at the
|
|
1263
|
+
// root, no anchor). A plain `null` is re-read by the capture's own
|
|
1264
|
+
// locator, which would resolve the raw call and publish the carrier.
|
|
1265
|
+
const carrierUnwrap = carrierCandidate
|
|
1266
|
+
? this.unwrapTypeWithConfig(carrierCandidate, terminalNode, extractionConfig)
|
|
1267
|
+
: undefined;
|
|
1268
|
+
if (carrierCandidate &&
|
|
1269
|
+
carrierUnwrap?.wasUnwrapped &&
|
|
1270
|
+
carrierUnwrap.typeString.trim() === 'unknown') {
|
|
1271
|
+
const carried = typeText(carrierCandidate, terminalNode);
|
|
1272
|
+
this.log(`Call result at ${request.file_path}:${request.line_number} carries ${carried}, which ` +
|
|
1273
|
+
"the service's wrapper rules verify as transport and read no payload out of; this " +
|
|
1274
|
+
'site states no response contract');
|
|
1275
|
+
const abstain = this.createInferredType(request, 'unknown', false, this.getNodeLocation(callExpr));
|
|
1276
|
+
abstain.any_provenance = [
|
|
1277
|
+
{
|
|
1278
|
+
path: '',
|
|
1279
|
+
kind: 'unknown',
|
|
1280
|
+
reason: 'machinery_envelope',
|
|
1281
|
+
detail: "what this call's result carries is transport that the service's wrapper rules " +
|
|
1282
|
+
'verify and read no payload out of (a library response object around the body), ' +
|
|
1283
|
+
'so this site states no response contract',
|
|
1284
|
+
},
|
|
1285
|
+
];
|
|
1286
|
+
return abstain;
|
|
1287
|
+
}
|
|
1288
|
+
const carried = carrierUnwrap?.wasUnwrapped && carrierUnwrap.payloadType
|
|
1289
|
+
? carrierUnwrap.payloadType
|
|
1290
|
+
: carrierCandidate;
|
|
1211
1291
|
// The payload rides the row as its own MEMBERS, never as its bare name
|
|
1212
1292
|
// (#257): `derive_capture_anchors` turns a usable inference into a literal
|
|
1213
1293
|
// capture anchor, and a bare name is out of scope where the surface
|
|
1214
1294
|
// declares the alias, so it would decay to a top type and publish nothing
|
|
1215
1295
|
// — trading a wrong answer for no answer. Where the payload carries no
|
|
1216
1296
|
// member shape to print, the carrier keeps its own answer.
|
|
1217
|
-
const carrierText =
|
|
1218
|
-
|
|
1219
|
-
: null;
|
|
1220
|
-
const carrierPayload = carrierText ? carrierCandidate : undefined;
|
|
1297
|
+
const carrierText = carried ? this.structuralTextFromType(carried, terminalNode) : null;
|
|
1298
|
+
const carrierPayload = carrierText ? carried : undefined;
|
|
1221
1299
|
if (carrierPayload && carrierText) {
|
|
1222
1300
|
typeString = carrierText;
|
|
1223
1301
|
}
|
|
@@ -1345,8 +1423,101 @@ export class TypeInferrer {
|
|
|
1345
1423
|
if (statedBody) {
|
|
1346
1424
|
inferred.stated_body = statedBody;
|
|
1347
1425
|
}
|
|
1426
|
+
// carrick#1842: `string` cannot tell a body read as raw text from a JSON
|
|
1427
|
+
// body that is a string, and raw text states no structural contract. The
|
|
1428
|
+
// row says which it is, so the check phase can read the pair unverifiable
|
|
1429
|
+
// instead of comparing `string` with the other side's body.
|
|
1430
|
+
if (isBareStringText(typeString) &&
|
|
1431
|
+
(this.textReadAtTerminal(callExpr, terminalNode) || this.callChoosesTextBody(callExpr))) {
|
|
1432
|
+
inferred.raw_text_read = true;
|
|
1433
|
+
}
|
|
1348
1434
|
return inferred;
|
|
1349
1435
|
}
|
|
1436
|
+
/**
|
|
1437
|
+
* The terminal of a call's def-use walk is a zero-argument `.text()` read of
|
|
1438
|
+
* the call's own result (carrick#1842): on its binding (`res.text()`,
|
|
1439
|
+
* through a declaration, `await` or a cast), or in the callback a `then` on
|
|
1440
|
+
* the call hands the response to (`fetch(u).then((res) => res.text())`).
|
|
1441
|
+
*/
|
|
1442
|
+
textReadAtTerminal(callExpr, terminal) {
|
|
1443
|
+
const value = Node.isVariableDeclaration(terminal) ? terminal.getInitializer() : terminal;
|
|
1444
|
+
const read = value ? this.unwrapExpressionNode(value) : undefined;
|
|
1445
|
+
if (!read || !Node.isCallExpression(read))
|
|
1446
|
+
return false;
|
|
1447
|
+
const callResult = this.unwrapPromiseType(callExpr.getType()).compilerType;
|
|
1448
|
+
if (this.isTextReadOf(read, (receiver) => receiver === callExpr ||
|
|
1449
|
+
this.unwrapPromiseType(receiver.getType()).compilerType === callResult)) {
|
|
1450
|
+
return true;
|
|
1451
|
+
}
|
|
1452
|
+
const then = read.getExpression();
|
|
1453
|
+
if (!Node.isPropertyAccessExpression(then) ||
|
|
1454
|
+
then.getName() !== 'then' ||
|
|
1455
|
+
this.unwrapExpressionNode(then.getExpression()) !== callExpr) {
|
|
1456
|
+
return false;
|
|
1457
|
+
}
|
|
1458
|
+
const callback = read.getArguments()[0];
|
|
1459
|
+
if (!callback || (!Node.isArrowFunction(callback) && !Node.isFunctionExpression(callback))) {
|
|
1460
|
+
return false;
|
|
1461
|
+
}
|
|
1462
|
+
const param = callback.getParameters()[0]?.getNameNode();
|
|
1463
|
+
if (!param || !Node.isIdentifier(param))
|
|
1464
|
+
return false;
|
|
1465
|
+
const returned = this.returnedExpressions(callback);
|
|
1466
|
+
const inner = returned.length === 1 ? this.unwrapExpressionNode(returned[0]) : undefined;
|
|
1467
|
+
return (!!inner &&
|
|
1468
|
+
Node.isCallExpression(inner) &&
|
|
1469
|
+
this.isTextReadOf(inner, (receiver) => Node.isIdentifier(receiver) && receiver.getSymbol() === param.getSymbol()));
|
|
1470
|
+
}
|
|
1471
|
+
/** `call` is `<receiver>.text()` with no arguments, and `accept` takes its receiver. */
|
|
1472
|
+
isTextReadOf(call, accept) {
|
|
1473
|
+
const access = call.getExpression();
|
|
1474
|
+
return (Node.isPropertyAccessExpression(access) &&
|
|
1475
|
+
access.getName() === RAW_TEXT_FORMAT &&
|
|
1476
|
+
call.getArguments().length === 0 &&
|
|
1477
|
+
accept(this.unwrapExpressionNode(access.getExpression())));
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* The call's resolved signature, at this site, types a member of an object
|
|
1481
|
+
* argument as exactly the literal `'text'`, and the source passes that
|
|
1482
|
+
* literal there (carrick#1842). That is how a request library lets a caller
|
|
1483
|
+
* choose a text body from the formats it offers (`{ type: 'text' }`): a
|
|
1484
|
+
* generic config instantiated by the literal, or an overload taken by it.
|
|
1485
|
+
* A member typed as a wider union (`kind: 'text' | 'image'`) chooses no
|
|
1486
|
+
* format, and no member name is read.
|
|
1487
|
+
*/
|
|
1488
|
+
callChoosesTextBody(callExpr) {
|
|
1489
|
+
const checker = this.project.getTypeChecker().compilerObject;
|
|
1490
|
+
let signature;
|
|
1491
|
+
try {
|
|
1492
|
+
signature = checker.getResolvedSignature(callExpr.compilerNode);
|
|
1493
|
+
}
|
|
1494
|
+
catch {
|
|
1495
|
+
return false;
|
|
1496
|
+
}
|
|
1497
|
+
if (!signature || signature.parameters.length === 0)
|
|
1498
|
+
return false;
|
|
1499
|
+
return callExpr.getArguments().some((argument, index) => {
|
|
1500
|
+
if (!Node.isObjectLiteralExpression(argument))
|
|
1501
|
+
return false;
|
|
1502
|
+
const parameter = signature.parameters[Math.min(index, signature.parameters.length - 1)];
|
|
1503
|
+
const parameterType = checker.getTypeOfSymbolAtLocation(parameter, callExpr.compilerNode);
|
|
1504
|
+
return argument.getProperties().some((property) => {
|
|
1505
|
+
if (!Node.isPropertyAssignment(property))
|
|
1506
|
+
return false;
|
|
1507
|
+
const value = property.getInitializer();
|
|
1508
|
+
if (!value ||
|
|
1509
|
+
!(Node.isStringLiteral(value) || Node.isNoSubstitutionTemplateLiteral(value)) ||
|
|
1510
|
+
value.getLiteralValue() !== RAW_TEXT_FORMAT) {
|
|
1511
|
+
return false;
|
|
1512
|
+
}
|
|
1513
|
+
const member = checker.getPropertyOfType(parameterType, property.getName());
|
|
1514
|
+
if (!member)
|
|
1515
|
+
return false;
|
|
1516
|
+
const declared = checker.getNonNullableType(checker.getTypeOfSymbolAtLocation(member, callExpr.compilerNode));
|
|
1517
|
+
return declared.isStringLiteral() && declared.value === RAW_TEXT_FORMAT;
|
|
1518
|
+
});
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1350
1521
|
/**
|
|
1351
1522
|
* What the source states the body read at `terminal` to be, when the type
|
|
1352
1523
|
* `extractExplicitTypeFromAncestor` printed for it is stated AT the read
|
|
@@ -1458,10 +1629,12 @@ export class TypeInferrer {
|
|
|
1458
1629
|
symbol = symbol.getAliasedSymbol() ?? symbol;
|
|
1459
1630
|
}
|
|
1460
1631
|
const source = symbol?.getDeclarations()[0]?.getSourceFile().getFilePath();
|
|
1632
|
+
const typeArguments = node.getTypeArguments().length;
|
|
1461
1633
|
return {
|
|
1462
1634
|
root: nameNode.getText(),
|
|
1463
1635
|
...(source ? { root_source: source } : {}),
|
|
1464
1636
|
...depthField,
|
|
1637
|
+
...(typeArguments > 0 ? { root_type_arguments: typeArguments } : {}),
|
|
1465
1638
|
};
|
|
1466
1639
|
}
|
|
1467
1640
|
inferVariable(sourceFile, request, extractionConfig) {
|
|
@@ -1813,6 +1986,16 @@ export class TypeInferrer {
|
|
|
1813
1986
|
for (const expr of candidates) {
|
|
1814
1987
|
if (expr.getStart() <= startPos)
|
|
1815
1988
|
continue;
|
|
1989
|
+
// carrick#1796: what the source does with the response once it has
|
|
1990
|
+
// FAILED describes the failure, not the payload. `if (res.ok) {
|
|
1991
|
+
// return ... } const errorText = await res.text()` published the error
|
|
1992
|
+
// text as the response contract because the walk keeps the LAST whole
|
|
1993
|
+
// read, and the failure path's read comes after the success path's
|
|
1994
|
+
// return. A read there is neither the payload nor a part of it.
|
|
1995
|
+
if (this.usesTrackedNames(expr, currentNames) &&
|
|
1996
|
+
reachedOnlyOnFailure(expr, func, (node) => this.isIdentifierUsage(node, currentNames))) {
|
|
1997
|
+
continue;
|
|
1998
|
+
}
|
|
1816
1999
|
// carrick#1017: a comparison is `boolean` by construction, so it is
|
|
1817
2000
|
// never the payload a call yields. `const res = await fetch(url); if
|
|
1818
2001
|
// (res.status === 404) return null; return await res.json() as Entry`
|
|
@@ -1823,9 +2006,9 @@ export class TypeInferrer {
|
|
|
1823
2006
|
continue;
|
|
1824
2007
|
}
|
|
1825
2008
|
// The body a caller reads OUT of a transport response IS the payload
|
|
1826
|
-
// of the call that produced it: `res.json()` on the
|
|
1827
|
-
// with whatever `as T` the source states around it
|
|
1828
|
-
// `extractExplicitTypeFromAncestor` below. A bare identifier never
|
|
2009
|
+
// of the call that produced it: `res.json()` or `res.text()` on the
|
|
2010
|
+
// tracked binding, with whatever `as T` the source states around it
|
|
2011
|
+
// read by `extractExplicitTypeFromAncestor` below. A bare identifier never
|
|
1829
2012
|
// matches `expressionUsesNames` (it tests DESCENDANTS), so without
|
|
1830
2013
|
// this the body read is invisible to the def-use walk.
|
|
1831
2014
|
if (Node.isIdentifier(expr) && this.isIdentifierUsage(expr, currentNames)) {
|
|
@@ -2083,10 +2266,10 @@ export class TypeInferrer {
|
|
|
2083
2266
|
* `query.data`, `envelope` in `envelope.list[0]` — or `undefined` when the
|
|
2084
2267
|
* identifier names the value itself.
|
|
2085
2268
|
*
|
|
2086
|
-
* A member CALL is not a projection: `res.
|
|
2269
|
+
* A member CALL is not a projection: `res.blob()` yields a body rather than
|
|
2087
2270
|
* a part of one, and what it returns stays the walk's business. The
|
|
2088
|
-
* zero-argument json body
|
|
2089
|
-
* is asked.
|
|
2271
|
+
* zero-argument json and text body reads have their own branch and are
|
|
2272
|
+
* taken before this is asked.
|
|
2090
2273
|
*/
|
|
2091
2274
|
projectionOnReceiver(identifier) {
|
|
2092
2275
|
const access = identifier.getParent();
|
|
@@ -2329,12 +2512,18 @@ export class TypeInferrer {
|
|
|
2329
2512
|
!this.leavesAPositionOpen(statedNode));
|
|
2330
2513
|
});
|
|
2331
2514
|
}
|
|
2515
|
+
/**
|
|
2516
|
+
* The zero-argument whole-body read that takes `identifier` as its receiver,
|
|
2517
|
+
* `res.json()` or `res.text()`, or `undefined`. A text read is a body read
|
|
2518
|
+
* like a json one (carrick#1842): without it, `return res.text()` left the
|
|
2519
|
+
* walk on the response binding and published the transport object.
|
|
2520
|
+
*/
|
|
2332
2521
|
bodyReadOnReceiver(identifier) {
|
|
2333
2522
|
const access = identifier.getParent();
|
|
2334
2523
|
if (!access ||
|
|
2335
2524
|
!Node.isPropertyAccessExpression(access) ||
|
|
2336
2525
|
access.getExpression() !== identifier ||
|
|
2337
|
-
access.getName()
|
|
2526
|
+
!WHOLE_BODY_READS.has(access.getName())) {
|
|
2338
2527
|
return undefined;
|
|
2339
2528
|
}
|
|
2340
2529
|
const call = access.getParent();
|
|
@@ -2361,6 +2550,12 @@ export class TypeInferrer {
|
|
|
2361
2550
|
const identifiers = expr.getDescendantsOfKind(SyntaxKind.Identifier);
|
|
2362
2551
|
return identifiers.some((id) => this.isIdentifierUsage(id, names));
|
|
2363
2552
|
}
|
|
2553
|
+
/** `expr` is a use of a tracked name, or contains one. */
|
|
2554
|
+
usesTrackedNames(expr, names) {
|
|
2555
|
+
return Node.isIdentifier(expr)
|
|
2556
|
+
? this.isIdentifierUsage(expr, names)
|
|
2557
|
+
: this.expressionUsesNames(expr, names);
|
|
2558
|
+
}
|
|
2364
2559
|
isIdentifierUsage(id, names) {
|
|
2365
2560
|
if (!Node.isIdentifier(id)) {
|
|
2366
2561
|
return false;
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
import { ts } from 'ts-morph';
|
|
32
32
|
import { canonicalizeUnionsInText, foldBooleanLiterals, } from './type-text-canonicalizer.js';
|
|
33
33
|
import { isExternalOrigin } from './origin.js';
|
|
34
|
+
import { notePrintedType } from './printed-names.js';
|
|
34
35
|
/**
|
|
35
36
|
* Bound on the structural-expansion recursion. Deep enough for every realistic
|
|
36
37
|
* request/response shape; a backstop against pathological/recursive types the
|
|
@@ -385,8 +386,16 @@ function isLibraryType(type, origin) {
|
|
|
385
386
|
export function namedText(type) {
|
|
386
387
|
return canonicalizeUnionsInText(compilerText(type));
|
|
387
388
|
}
|
|
388
|
-
/**
|
|
389
|
+
/**
|
|
390
|
+
* The compiler's print, with the two fallbacks. The print writes names bare,
|
|
391
|
+
* whatever scope reads it, so it is noted for the inference (carrick#1836).
|
|
392
|
+
*/
|
|
389
393
|
function compilerText(type) {
|
|
394
|
+
const text = compilerPrint(type);
|
|
395
|
+
notePrintedType(type, undefined, text);
|
|
396
|
+
return text;
|
|
397
|
+
}
|
|
398
|
+
function compilerPrint(type) {
|
|
390
399
|
try {
|
|
391
400
|
return type.getText(undefined, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.InTypeAlias);
|
|
392
401
|
}
|
|
@@ -823,6 +823,23 @@ export interface InferredType {
|
|
|
823
823
|
* stated, or when the only annotation is further away than the read.
|
|
824
824
|
*/
|
|
825
825
|
stated_body?: StatedBody;
|
|
826
|
+
/**
|
|
827
|
+
* carrick#1836: the declarations behind the names `type_string` prints that
|
|
828
|
+
* the request's file does not resolve. The printer writes names bare where
|
|
829
|
+
* no scope reads them, so this is the only record of what they meant. A
|
|
830
|
+
* name printed for two declarations is listed twice. Absent when there is
|
|
831
|
+
* nothing to list.
|
|
832
|
+
*/
|
|
833
|
+
printed_names?: PrintedName[];
|
|
834
|
+
/**
|
|
835
|
+
* carrick#1842, `call_result` only: the published `string` is the body read
|
|
836
|
+
* as raw text, by a `.text()` read of the call's response or by a call whose
|
|
837
|
+
* signature takes the literal `'text'` the source passes as its body format.
|
|
838
|
+
* Raw text states no structural contract, so the check phase reads the pair
|
|
839
|
+
* unverifiable rather than comparing `string` with the other side's body.
|
|
840
|
+
* Absent when the body is not a raw-text read.
|
|
841
|
+
*/
|
|
842
|
+
raw_text_read?: true;
|
|
826
843
|
}
|
|
827
844
|
/**
|
|
828
845
|
* What the source states a body read to be (carrick#1749). The scanner
|
|
@@ -843,6 +860,11 @@ export interface StatedBody {
|
|
|
843
860
|
root_source?: string;
|
|
844
861
|
/** Array levels peeled on the way to `root`. Omitted when 0. */
|
|
845
862
|
array_depth?: number;
|
|
863
|
+
/**
|
|
864
|
+
* Type arguments written at `root` (`Page<Order>` → 1). Omitted when 0.
|
|
865
|
+
* `root` alone does not name the body such a statement states.
|
|
866
|
+
*/
|
|
867
|
+
root_type_arguments?: number;
|
|
846
868
|
}
|
|
847
869
|
/**
|
|
848
870
|
* Why a type carries `any`/`unknown` at a position, and where.
|
|
@@ -853,6 +875,8 @@ export interface StatedBody {
|
|
|
853
875
|
* across the bundle seam.
|
|
854
876
|
*/
|
|
855
877
|
export type TypeProvenance = import('./capture/api.js').TypeProvenance;
|
|
878
|
+
/** carrick#1836: declared in the capture bundle, which reads it on a literal anchor. */
|
|
879
|
+
export type PrintedName = import('./capture/api.js').PrintedName;
|
|
856
880
|
export type TypeProvenanceReason = import('./capture/api.js').TypeProvenanceReason;
|
|
857
881
|
/**
|
|
858
882
|
* Source location information for a type
|
|
@@ -332,18 +332,44 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
|
|
|
332
332
|
type_text: z.ZodString;
|
|
333
333
|
anchor_origin: z.ZodEnum<["llm-symbol", "deterministic-infer", "anchor-backfill", "manifest-placeholder"]>;
|
|
334
334
|
source_file: z.ZodOptional<z.ZodString>;
|
|
335
|
+
printed_names: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
336
|
+
name: z.ZodString;
|
|
337
|
+
file: z.ZodString;
|
|
338
|
+
export_path: z.ZodArray<z.ZodString, "many">;
|
|
339
|
+
}, "strip", z.ZodTypeAny, {
|
|
340
|
+
name: string;
|
|
341
|
+
file: string;
|
|
342
|
+
export_path: string[];
|
|
343
|
+
}, {
|
|
344
|
+
name: string;
|
|
345
|
+
file: string;
|
|
346
|
+
export_path: string[];
|
|
347
|
+
}>, "many">>;
|
|
348
|
+
raw_text_read: z.ZodOptional<z.ZodLiteral<true>>;
|
|
335
349
|
}, "strip", z.ZodTypeAny, {
|
|
336
350
|
kind: "literal";
|
|
337
351
|
alias: string;
|
|
338
352
|
anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill" | "manifest-placeholder";
|
|
339
353
|
type_text: string;
|
|
340
354
|
source_file?: string | undefined;
|
|
355
|
+
printed_names?: {
|
|
356
|
+
name: string;
|
|
357
|
+
file: string;
|
|
358
|
+
export_path: string[];
|
|
359
|
+
}[] | undefined;
|
|
360
|
+
raw_text_read?: true | undefined;
|
|
341
361
|
}, {
|
|
342
362
|
kind: "literal";
|
|
343
363
|
alias: string;
|
|
344
364
|
anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill" | "manifest-placeholder";
|
|
345
365
|
type_text: string;
|
|
346
366
|
source_file?: string | undefined;
|
|
367
|
+
printed_names?: {
|
|
368
|
+
name: string;
|
|
369
|
+
file: string;
|
|
370
|
+
export_path: string[];
|
|
371
|
+
}[] | undefined;
|
|
372
|
+
raw_text_read?: true | undefined;
|
|
347
373
|
}>]>, "many">;
|
|
348
374
|
out_dir: z.ZodString;
|
|
349
375
|
tsconfig_path: z.ZodOptional<z.ZodString>;
|
|
@@ -382,6 +408,12 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
|
|
|
382
408
|
anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill" | "manifest-placeholder";
|
|
383
409
|
type_text: string;
|
|
384
410
|
source_file?: string | undefined;
|
|
411
|
+
printed_names?: {
|
|
412
|
+
name: string;
|
|
413
|
+
file: string;
|
|
414
|
+
export_path: string[];
|
|
415
|
+
}[] | undefined;
|
|
416
|
+
raw_text_read?: true | undefined;
|
|
385
417
|
})[];
|
|
386
418
|
out_dir: string;
|
|
387
419
|
tsconfig_path?: string | undefined;
|
|
@@ -420,6 +452,12 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
|
|
|
420
452
|
anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill" | "manifest-placeholder";
|
|
421
453
|
type_text: string;
|
|
422
454
|
source_file?: string | undefined;
|
|
455
|
+
printed_names?: {
|
|
456
|
+
name: string;
|
|
457
|
+
file: string;
|
|
458
|
+
export_path: string[];
|
|
459
|
+
}[] | undefined;
|
|
460
|
+
raw_text_read?: true | undefined;
|
|
423
461
|
})[];
|
|
424
462
|
out_dir: string;
|
|
425
463
|
tsconfig_path?: string | undefined;
|
|
@@ -2077,18 +2115,44 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
|
|
|
2077
2115
|
type_text: z.ZodString;
|
|
2078
2116
|
anchor_origin: z.ZodEnum<["llm-symbol", "deterministic-infer", "anchor-backfill", "manifest-placeholder"]>;
|
|
2079
2117
|
source_file: z.ZodOptional<z.ZodString>;
|
|
2118
|
+
printed_names: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
2119
|
+
name: z.ZodString;
|
|
2120
|
+
file: z.ZodString;
|
|
2121
|
+
export_path: z.ZodArray<z.ZodString, "many">;
|
|
2122
|
+
}, "strip", z.ZodTypeAny, {
|
|
2123
|
+
name: string;
|
|
2124
|
+
file: string;
|
|
2125
|
+
export_path: string[];
|
|
2126
|
+
}, {
|
|
2127
|
+
name: string;
|
|
2128
|
+
file: string;
|
|
2129
|
+
export_path: string[];
|
|
2130
|
+
}>, "many">>;
|
|
2131
|
+
raw_text_read: z.ZodOptional<z.ZodLiteral<true>>;
|
|
2080
2132
|
}, "strip", z.ZodTypeAny, {
|
|
2081
2133
|
kind: "literal";
|
|
2082
2134
|
alias: string;
|
|
2083
2135
|
anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill" | "manifest-placeholder";
|
|
2084
2136
|
type_text: string;
|
|
2085
2137
|
source_file?: string | undefined;
|
|
2138
|
+
printed_names?: {
|
|
2139
|
+
name: string;
|
|
2140
|
+
file: string;
|
|
2141
|
+
export_path: string[];
|
|
2142
|
+
}[] | undefined;
|
|
2143
|
+
raw_text_read?: true | undefined;
|
|
2086
2144
|
}, {
|
|
2087
2145
|
kind: "literal";
|
|
2088
2146
|
alias: string;
|
|
2089
2147
|
anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill" | "manifest-placeholder";
|
|
2090
2148
|
type_text: string;
|
|
2091
2149
|
source_file?: string | undefined;
|
|
2150
|
+
printed_names?: {
|
|
2151
|
+
name: string;
|
|
2152
|
+
file: string;
|
|
2153
|
+
export_path: string[];
|
|
2154
|
+
}[] | undefined;
|
|
2155
|
+
raw_text_read?: true | undefined;
|
|
2092
2156
|
}>]>, "many">;
|
|
2093
2157
|
out_dir: z.ZodString;
|
|
2094
2158
|
tsconfig_path: z.ZodOptional<z.ZodString>;
|
|
@@ -2127,6 +2191,12 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
|
|
|
2127
2191
|
anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill" | "manifest-placeholder";
|
|
2128
2192
|
type_text: string;
|
|
2129
2193
|
source_file?: string | undefined;
|
|
2194
|
+
printed_names?: {
|
|
2195
|
+
name: string;
|
|
2196
|
+
file: string;
|
|
2197
|
+
export_path: string[];
|
|
2198
|
+
}[] | undefined;
|
|
2199
|
+
raw_text_read?: true | undefined;
|
|
2130
2200
|
})[];
|
|
2131
2201
|
out_dir: string;
|
|
2132
2202
|
tsconfig_path?: string | undefined;
|
|
@@ -2165,6 +2235,12 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
|
|
|
2165
2235
|
anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill" | "manifest-placeholder";
|
|
2166
2236
|
type_text: string;
|
|
2167
2237
|
source_file?: string | undefined;
|
|
2238
|
+
printed_names?: {
|
|
2239
|
+
name: string;
|
|
2240
|
+
file: string;
|
|
2241
|
+
export_path: string[];
|
|
2242
|
+
}[] | undefined;
|
|
2243
|
+
raw_text_read?: true | undefined;
|
|
2168
2244
|
})[];
|
|
2169
2245
|
out_dir: string;
|
|
2170
2246
|
tsconfig_path?: string | undefined;
|
|
@@ -184,6 +184,14 @@ const CaptureAnchorRequestSchema = z.discriminatedUnion('kind', [
|
|
|
184
184
|
type_text: z.string().min(1),
|
|
185
185
|
anchor_origin: AnchorOriginSchema,
|
|
186
186
|
source_file: z.string().min(1).optional(),
|
|
187
|
+
printed_names: z
|
|
188
|
+
.array(z.object({
|
|
189
|
+
name: z.string().min(1),
|
|
190
|
+
file: z.string().min(1),
|
|
191
|
+
export_path: z.array(z.string().min(1)).min(1),
|
|
192
|
+
}))
|
|
193
|
+
.optional(),
|
|
194
|
+
raw_text_read: z.literal(true).optional(),
|
|
187
195
|
}),
|
|
188
196
|
]);
|
|
189
197
|
export const CaptureV2RequestSchema = BaseRequestSchema.extend({
|