carrick 0.3.65 → 0.3.67
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 +34 -0
- package/bin/carrick.mjs +9 -1
- package/dist/cli.d.ts +20 -2
- package/dist/cli.js +24 -2
- package/dist/cli.js.map +1 -1
- package/dist/contract.d.ts +40 -0
- package/dist/contract.js +4 -0
- package/dist/contract.js.map +1 -1
- package/dist/diagnostics.js +21 -2
- package/dist/diagnostics.js.map +1 -1
- package/dist/hook/post-edit.js +5 -1
- package/dist/hook/post-edit.js.map +1 -1
- package/dist/init/connect.js +10 -8
- package/dist/init/connect.js.map +1 -1
- package/dist/init/hosted.d.ts +23 -1
- package/dist/init/hosted.js +41 -32
- package/dist/init/hosted.js.map +1 -1
- package/dist/init/mcp.d.ts +35 -2
- package/dist/init/mcp.js +142 -15
- package/dist/init/mcp.js.map +1 -1
- package/dist/init/output.d.ts +71 -0
- package/dist/init/output.js +173 -0
- package/dist/init/output.js.map +1 -0
- package/dist/init/remove.d.ts +41 -0
- package/dist/init/remove.js +264 -0
- package/dist/init/remove.js.map +1 -0
- package/dist/init/repos.js +2 -2
- package/dist/init/repos.js.map +1 -1
- package/dist/init/run.d.ts +44 -30
- package/dist/init/run.js +160 -218
- package/dist/init/run.js.map +1 -1
- package/dist/init/settings.d.ts +15 -0
- package/dist/init/settings.js +38 -0
- package/dist/init/settings.js.map +1 -1
- package/dist/render.d.ts +24 -0
- package/dist/render.js +80 -1
- package/dist/render.js.map +1 -1
- package/package.json +8 -6
- package/sidecar/dist/src/capture/machinery.d.ts +10 -0
- package/sidecar/dist/src/capture/machinery.js +19 -9
- package/sidecar/dist/src/type-inferrer.d.ts +66 -4
- package/sidecar/dist/src/type-inferrer.js +249 -18
|
@@ -112,6 +112,67 @@ export const MACHINERY_MEMBER_INDICATORS = new Set([
|
|
|
112
112
|
]);
|
|
113
113
|
/** Machinery needs at least this many indicator members to be recognized. */
|
|
114
114
|
const MACHINERY_INDICATOR_THRESHOLD = 3;
|
|
115
|
+
/**
|
|
116
|
+
* True when a binary expression COMPARES rather than assigns or computes: its
|
|
117
|
+
* value is a `boolean` the operator manufactured, never a payload
|
|
118
|
+
* (carrick#1017).
|
|
119
|
+
*/
|
|
120
|
+
function isComparisonExpression(expr) {
|
|
121
|
+
switch (expr.getOperatorToken().getKind()) {
|
|
122
|
+
case SyntaxKind.EqualsEqualsToken:
|
|
123
|
+
case SyntaxKind.EqualsEqualsEqualsToken:
|
|
124
|
+
case SyntaxKind.ExclamationEqualsToken:
|
|
125
|
+
case SyntaxKind.ExclamationEqualsEqualsToken:
|
|
126
|
+
case SyntaxKind.LessThanToken:
|
|
127
|
+
case SyntaxKind.LessThanEqualsToken:
|
|
128
|
+
case SyntaxKind.GreaterThanToken:
|
|
129
|
+
case SyntaxKind.GreaterThanEqualsToken:
|
|
130
|
+
case SyntaxKind.InstanceOfKeyword:
|
|
131
|
+
case SyntaxKind.InKeyword:
|
|
132
|
+
return true;
|
|
133
|
+
default:
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* How deep the response-branch split (carrick#1017) follows nested
|
|
139
|
+
* conditionals. Two levels covers `a ? x : b ? y : z`; deeper nesting is a
|
|
140
|
+
* dispatch table, not a response contract.
|
|
141
|
+
*/
|
|
142
|
+
const RESPONSE_BRANCH_MAX_DEPTH = 2;
|
|
143
|
+
/**
|
|
144
|
+
* The members the standard response-init object declares. An argument beside
|
|
145
|
+
* the body whose properties are all from this set is telling the platform how
|
|
146
|
+
* to send the response, not what to send (carrick#1017).
|
|
147
|
+
*/
|
|
148
|
+
const RESPONSE_INIT_MEMBER_NAMES = new Set([
|
|
149
|
+
'status',
|
|
150
|
+
'statusText',
|
|
151
|
+
'headers',
|
|
152
|
+
]);
|
|
153
|
+
/**
|
|
154
|
+
* True when a declaration file path is runtime/library origin rather than user
|
|
155
|
+
* source: a TypeScript lib (`lib.dom.d.ts`), an installed package, or the
|
|
156
|
+
* runtime declarations Carrick itself materialises for a non-Node runtime
|
|
157
|
+
* (`.carrick/deno/<hash>/runtime.d.ts`, and the remote modules cached beside
|
|
158
|
+
* it).
|
|
159
|
+
*
|
|
160
|
+
* The third case is carrick#1017: on a Deno service the platform `Response`
|
|
161
|
+
* is declared in Carrick's own generated runtime file, which is neither a
|
|
162
|
+
* `lib.*.d.ts` nor under `node_modules`, so the machinery origin gate stayed
|
|
163
|
+
* shut and every route published the fetch `Response` wrapper as its response
|
|
164
|
+
* contract. The path is Carrick's own artefact, not a framework name.
|
|
165
|
+
*
|
|
166
|
+
* Kept in lockstep with `capture/machinery.ts`'s copy (the capture seam forbids
|
|
167
|
+
* sharing a module across it); `machinery-indicator-mirror.test.ts` guards the
|
|
168
|
+
* pair.
|
|
169
|
+
*/
|
|
170
|
+
export function isExternalOrigin(filePath) {
|
|
171
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
172
|
+
return (normalized.includes('/node_modules/') ||
|
|
173
|
+
/\/lib\.[^/]*\.d\.ts$/.test(normalized) ||
|
|
174
|
+
normalized.includes('/.carrick/deno/'));
|
|
175
|
+
}
|
|
115
176
|
/**
|
|
116
177
|
* How far the response-helper recovery (carrick#631) descends through nested
|
|
117
178
|
* calls looking for the payload argument. `cors(request, json(payload))` needs
|
|
@@ -566,6 +627,44 @@ export class TypeInferrer {
|
|
|
566
627
|
return this.buildFunctionReturnInferredType(request, registryHandler, extractionConfig, true);
|
|
567
628
|
}
|
|
568
629
|
}
|
|
630
|
+
// carrick#1017: the located expression evaluates to the transport wrapper
|
|
631
|
+
// itself — `Response.json(entry)`, `new Response(JSON.stringify(entry))`,
|
|
632
|
+
// `ctx.json(entry)` all have the platform `Response` as their type, and a
|
|
633
|
+
// conditional over two of them has it on both sides. Publishing that type
|
|
634
|
+
// reports the fetch API (`headers`, `ok`, `status`, `json(): Promise<any>`)
|
|
635
|
+
// as the endpoint's response contract, and every consumer reading the real
|
|
636
|
+
// body is then told its type is missing those members. The body is in the
|
|
637
|
+
// ARGUMENT, so read it there — through the same payload rule the
|
|
638
|
+
// function-return path uses, which splits the conditional, drops the
|
|
639
|
+
// branch whose sibling options object states a >= 400 status, and joins
|
|
640
|
+
// what survives. When it recovers nothing the row stays unresolved below:
|
|
641
|
+
// no contract is published off the wrapper either way.
|
|
642
|
+
const locatedType = node.getType();
|
|
643
|
+
const locatedUnwrap = this.unwrapTypeWithConfig(locatedType, node, extractionConfig);
|
|
644
|
+
const machineryAnchor = locatedUnwrap.verifiedMachinery === true ||
|
|
645
|
+
(!locatedUnwrap.wasUnwrapped &&
|
|
646
|
+
this.typeIsOrContainsResponseMachinery(this.unwrapPromiseType(locatedType)));
|
|
647
|
+
if (machineryAnchor) {
|
|
648
|
+
const recovered = this.recoverPayloadFromResponseExpressions([node], false);
|
|
649
|
+
if (recovered) {
|
|
650
|
+
this.log(`Response payload at ${request.file_path}:${request.line_number} is the ` +
|
|
651
|
+
"transport wrapper; recovered the body from the response call's argument");
|
|
652
|
+
const recoveredAnchor = recovered.anchorType
|
|
653
|
+
? this.unwrapArrayLevels(recovered.anchorType)
|
|
654
|
+
: undefined;
|
|
655
|
+
const resolvedSymbol = recoveredAnchor
|
|
656
|
+
? this.primaryTypeSymbol(recoveredAnchor.element)
|
|
657
|
+
: undefined;
|
|
658
|
+
const stated = recovered.statedTypeNode;
|
|
659
|
+
const writtenAnchor = resolvedSymbol === undefined && stated
|
|
660
|
+
? this.writtenAnchorOf(stated)
|
|
661
|
+
: undefined;
|
|
662
|
+
return this.createInferredType(request, recovered.typeString, recovered.isExplicit, this.getNodeLocation(recovered.node), undefined, resolvedSymbol ?? writtenAnchor?.symbol, writtenAnchor ? writtenAnchor.depth : recoveredAnchor?.depth, writtenAnchor?.source);
|
|
663
|
+
}
|
|
664
|
+
this.log(`Response payload at ${request.file_path}:${request.line_number} is the ` +
|
|
665
|
+
'transport wrapper and no branch states a body; leaving it unresolved');
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
569
668
|
// The resolved node IS the payload subexpression in the MVP schema.
|
|
570
669
|
// Transitional fallback: if a caller still supplies a bare call expression
|
|
571
670
|
// (e.g., `res.json(users)`), drill to its first argument. No method-name list.
|
|
@@ -610,10 +709,11 @@ export class TypeInferrer {
|
|
|
610
709
|
}
|
|
611
710
|
else {
|
|
612
711
|
const resolved = this.unwrapPromiseType(payloadType);
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
//
|
|
712
|
+
// The carrick#371 fail-closed guard. The LOCATED expression is handled
|
|
713
|
+
// above, where a wrapper yields its body; this catches the argument the
|
|
714
|
+
// drill reached being machinery itself (`hand(request, upstream)`),
|
|
715
|
+
// which no response call carries a body for. Abstain rather than emit
|
|
716
|
+
// the machinery.
|
|
617
717
|
if (this.typeIsOrContainsResponseMachinery(resolved)) {
|
|
618
718
|
this.log(`Response payload at ${request.file_path}:${request.line_number} is or contains ` +
|
|
619
719
|
'framework machinery (Response/Request-shaped); abstaining rather than ' +
|
|
@@ -940,6 +1040,28 @@ export class TypeInferrer {
|
|
|
940
1040
|
for (const expr of candidates) {
|
|
941
1041
|
if (expr.getStart() <= startPos)
|
|
942
1042
|
continue;
|
|
1043
|
+
// carrick#1017: a comparison is `boolean` by construction, so it is
|
|
1044
|
+
// never the payload a call yields. `const res = await fetch(url); if
|
|
1045
|
+
// (res.status === 404) return null; return await res.json() as Entry`
|
|
1046
|
+
// otherwise ends on the status check — the expected response type of
|
|
1047
|
+
// that call was published as `boolean`, and the producer's real body
|
|
1048
|
+
// was reported as not assignable to it.
|
|
1049
|
+
if (Node.isBinaryExpression(expr) && isComparisonExpression(expr)) {
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
// The body a caller reads OUT of a transport response IS the payload
|
|
1053
|
+
// of the call that produced it: `res.json()` on the tracked binding,
|
|
1054
|
+
// with whatever `as T` the source states around it read by
|
|
1055
|
+
// `extractExplicitTypeFromAncestor` below. A bare identifier never
|
|
1056
|
+
// matches `expressionUsesNames` (it tests DESCENDANTS), so without
|
|
1057
|
+
// this the body read is invisible to the def-use walk.
|
|
1058
|
+
if (Node.isIdentifier(expr) && this.isIdentifierUsage(expr, currentNames)) {
|
|
1059
|
+
const bodyRead = this.bodyReadOnReceiver(expr);
|
|
1060
|
+
if (bodyRead) {
|
|
1061
|
+
lastNode = bodyRead;
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
943
1065
|
if (Node.isVariableDeclaration(expr)) {
|
|
944
1066
|
const initializer = expr.getInitializer();
|
|
945
1067
|
if (initializer &&
|
|
@@ -1023,6 +1145,29 @@ export class TypeInferrer {
|
|
|
1023
1145
|
}
|
|
1024
1146
|
return nameNode;
|
|
1025
1147
|
}
|
|
1148
|
+
/**
|
|
1149
|
+
* The zero-argument `.json()` body read on this identifier — `res` in
|
|
1150
|
+
* `await res.json()` — or `undefined` (carrick#1017). The JSON body of an
|
|
1151
|
+
* HTTP response is read exactly this way whatever produced the response, so
|
|
1152
|
+
* the shape is structural, not a framework's name.
|
|
1153
|
+
*/
|
|
1154
|
+
bodyReadOnReceiver(identifier) {
|
|
1155
|
+
const access = identifier.getParent();
|
|
1156
|
+
if (!access ||
|
|
1157
|
+
!Node.isPropertyAccessExpression(access) ||
|
|
1158
|
+
access.getExpression() !== identifier ||
|
|
1159
|
+
access.getName() !== 'json') {
|
|
1160
|
+
return undefined;
|
|
1161
|
+
}
|
|
1162
|
+
const call = access.getParent();
|
|
1163
|
+
if (!call ||
|
|
1164
|
+
!Node.isCallExpression(call) ||
|
|
1165
|
+
call.getExpression() !== access ||
|
|
1166
|
+
call.getArguments().length > 0) {
|
|
1167
|
+
return undefined;
|
|
1168
|
+
}
|
|
1169
|
+
return call;
|
|
1170
|
+
}
|
|
1026
1171
|
collectDefUseNodes(func) {
|
|
1027
1172
|
const candidates = [];
|
|
1028
1173
|
func.forEachDescendant((node) => {
|
|
@@ -1508,22 +1653,19 @@ export class TypeInferrer {
|
|
|
1508
1653
|
return false;
|
|
1509
1654
|
}
|
|
1510
1655
|
/**
|
|
1511
|
-
* True when the symbol is declared in a TypeScript lib file (`lib.*.d.ts`)
|
|
1512
|
-
* under `node_modules
|
|
1513
|
-
*
|
|
1514
|
-
*
|
|
1656
|
+
* True when the symbol is declared in a TypeScript lib file (`lib.*.d.ts`),
|
|
1657
|
+
* under `node_modules`, or in the runtime declarations Carrick materialises
|
|
1658
|
+
* for a non-Node runtime under `.carrick/deno/` — i.e. framework/runtime
|
|
1659
|
+
* machinery, not user source. Works on a bare checkout: the DOM
|
|
1660
|
+
* `Response`/`Request` resolve from the bundled `lib.dom.d.ts` even with no
|
|
1661
|
+
* installed dependencies.
|
|
1515
1662
|
*/
|
|
1516
1663
|
symbolIsLibOrExternalOrigin(symbol) {
|
|
1517
1664
|
if (!symbol) {
|
|
1518
1665
|
return false;
|
|
1519
1666
|
}
|
|
1520
|
-
const isExternalPath = (filePath) => {
|
|
1521
|
-
const normalized = filePath.replace(/\\/g, '/');
|
|
1522
|
-
return (normalized.includes('/node_modules/') ||
|
|
1523
|
-
/\/lib\.[^/]*\.d\.ts$/.test(normalized));
|
|
1524
|
-
};
|
|
1525
1667
|
for (const decl of symbol.getDeclarations()) {
|
|
1526
|
-
if (
|
|
1668
|
+
if (isExternalOrigin(decl.getSourceFile().getFilePath())) {
|
|
1527
1669
|
return true;
|
|
1528
1670
|
}
|
|
1529
1671
|
}
|
|
@@ -1842,8 +1984,22 @@ export class TypeInferrer {
|
|
|
1842
1984
|
* expression yields an argument type this may read.
|
|
1843
1985
|
*/
|
|
1844
1986
|
recoverPayloadFromReturnStatements(func, statedOnly) {
|
|
1987
|
+
return this.recoverPayloadFromResponseExpressions(this.responseReturnedExpressions(func), statedOnly);
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* The response payload carried by a set of response-carrying expressions —
|
|
1991
|
+
* a handler's return expressions, or the single expression a producer
|
|
1992
|
+
* response locator resolved to (carrick#1017).
|
|
1993
|
+
*
|
|
1994
|
+
* Each expression is first split into its BRANCHES, so a route that answers
|
|
1995
|
+
* `found ? json(entry) : json(problem, { status: 404 })` contributes both
|
|
1996
|
+
* sides: the payload rule reads each one, the error branch drops out on its
|
|
1997
|
+
* stated status, and what survives is joined as a union exactly as several
|
|
1998
|
+
* return statements are.
|
|
1999
|
+
*/
|
|
2000
|
+
recoverPayloadFromResponseExpressions(expressions, statedOnly) {
|
|
1845
2001
|
const candidates = [];
|
|
1846
|
-
const returned = this.
|
|
2002
|
+
const returned = expressions.flatMap((expression) => this.expandResponseBranches(expression, 0));
|
|
1847
2003
|
const serialisers = this.calleesProvenSerialiser(returned);
|
|
1848
2004
|
for (const expression of returned) {
|
|
1849
2005
|
const payloadNode = this.responseHelperPayloadNode(expression, 0, statedOnly, serialisers);
|
|
@@ -2015,8 +2171,9 @@ export class TypeInferrer {
|
|
|
2015
2171
|
* when the compiler has one, else the callee's source text.
|
|
2016
2172
|
*/
|
|
2017
2173
|
calleeIdentity(call) {
|
|
2018
|
-
if (!Node.isCallExpression(call))
|
|
2174
|
+
if (!Node.isCallExpression(call) && !Node.isNewExpression(call)) {
|
|
2019
2175
|
return undefined;
|
|
2176
|
+
}
|
|
2020
2177
|
const callee = call.getExpression();
|
|
2021
2178
|
const symbol = callee.getSymbol();
|
|
2022
2179
|
if (symbol)
|
|
@@ -2046,8 +2203,13 @@ export class TypeInferrer {
|
|
|
2046
2203
|
if (depth > RESPONSE_HELPER_MAX_DEPTH)
|
|
2047
2204
|
return undefined;
|
|
2048
2205
|
const call = this.peelTransparentExpression(expression);
|
|
2049
|
-
|
|
2206
|
+
// A `new` of the platform response constructor carries its body exactly
|
|
2207
|
+
// where a call does — `new Response(JSON.stringify(payload), { status })`
|
|
2208
|
+
// is the same statement as `Response.json(payload, { status })`
|
|
2209
|
+
// (carrick#1017). Both node kinds answer `getArguments()`.
|
|
2210
|
+
if (!Node.isCallExpression(call) && !Node.isNewExpression(call)) {
|
|
2050
2211
|
return undefined;
|
|
2212
|
+
}
|
|
2051
2213
|
const args = call.getArguments().map((a) => this.peelTransparentExpression(a));
|
|
2052
2214
|
if (args.length === 0)
|
|
2053
2215
|
return undefined;
|
|
@@ -2056,7 +2218,14 @@ export class TypeInferrer {
|
|
|
2056
2218
|
const identity = this.calleeIdentity(call);
|
|
2057
2219
|
const proven = identity !== undefined && serialisers.has(identity);
|
|
2058
2220
|
const effectiveStatedOnly = statedOnly && !proven;
|
|
2059
|
-
for (const arg of args) {
|
|
2221
|
+
for (const [index, arg] of args.entries()) {
|
|
2222
|
+
// An options object BESIDE the body states how to send it, never what is
|
|
2223
|
+
// sent (carrick#1017): `new Response(null, { status: 204 })` has no body
|
|
2224
|
+
// at all, and reading its second argument would publish `{ status:
|
|
2225
|
+
// number }` as the endpoint's contract. The first argument is the body
|
|
2226
|
+
// by construction, so the rule applies only past it.
|
|
2227
|
+
if (index > 0 && this.statesResponseInit(arg))
|
|
2228
|
+
continue;
|
|
2060
2229
|
const candidate = this.unwrapJsonStringifyArg(arg);
|
|
2061
2230
|
if (this.nodeCarriesPayloadContract(candidate, effectiveStatedOnly)) {
|
|
2062
2231
|
return candidate;
|
|
@@ -2132,6 +2301,45 @@ export class TypeInferrer {
|
|
|
2132
2301
|
}
|
|
2133
2302
|
return undefined;
|
|
2134
2303
|
}
|
|
2304
|
+
/**
|
|
2305
|
+
* True when an object literal argument is response INIT rather than a body:
|
|
2306
|
+
* every property it declares is one the standard `ResponseInit` declares
|
|
2307
|
+
* (`status`, `statusText`, `headers`), and it states at least one of them as
|
|
2308
|
+
* init really does — a numeric status in the HTTP range, or headers.
|
|
2309
|
+
*
|
|
2310
|
+
* A payload that merely has a `status` member of its own (`{ status: "ok",
|
|
2311
|
+
* service: "ledger" }`) declares members init does not, or states `status` as
|
|
2312
|
+
* something other than an HTTP code, so it stays a payload.
|
|
2313
|
+
*/
|
|
2314
|
+
statesResponseInit(node) {
|
|
2315
|
+
if (!Node.isObjectLiteralExpression(node))
|
|
2316
|
+
return false;
|
|
2317
|
+
const properties = node.getProperties();
|
|
2318
|
+
if (properties.length === 0)
|
|
2319
|
+
return false;
|
|
2320
|
+
let statesInit = false;
|
|
2321
|
+
for (const property of properties) {
|
|
2322
|
+
if (!Node.isPropertyAssignment(property))
|
|
2323
|
+
return false;
|
|
2324
|
+
const name = property.getName();
|
|
2325
|
+
if (!RESPONSE_INIT_MEMBER_NAMES.has(name))
|
|
2326
|
+
return false;
|
|
2327
|
+
const initializer = property.getInitializer();
|
|
2328
|
+
if (!initializer)
|
|
2329
|
+
return false;
|
|
2330
|
+
if (name === 'headers') {
|
|
2331
|
+
statesInit = true;
|
|
2332
|
+
continue;
|
|
2333
|
+
}
|
|
2334
|
+
if (name === 'status' && Node.isNumericLiteral(initializer)) {
|
|
2335
|
+
const value = initializer.getLiteralValue();
|
|
2336
|
+
if (Number.isInteger(value) && value >= 100 && value <= 599) {
|
|
2337
|
+
statesInit = true;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
return statesInit;
|
|
2342
|
+
}
|
|
2135
2343
|
/**
|
|
2136
2344
|
* True when an argument states a >= 400 status: an options object carrying
|
|
2137
2345
|
* `status`/`statusCode`, or a bare status code (`send(body, 404)`).
|
|
@@ -2201,6 +2409,29 @@ export class TypeInferrer {
|
|
|
2201
2409
|
}
|
|
2202
2410
|
return current;
|
|
2203
2411
|
}
|
|
2412
|
+
/**
|
|
2413
|
+
* The branches a single response expression can take — one entry for a plain
|
|
2414
|
+
* expression, one per side of a conditional (carrick#1017).
|
|
2415
|
+
*
|
|
2416
|
+
* `return entry ? json(entry) : json({ error }, { status: 404 })` is one
|
|
2417
|
+
* return statement holding two responses. Read whole, its type is the
|
|
2418
|
+
* transport wrapper both sides share and the payload is lost; read per
|
|
2419
|
+
* branch, each side goes through the ordinary payload rule, which is also
|
|
2420
|
+
* what drops the error branch on its stated status. Nested conditionals are
|
|
2421
|
+
* followed to a small depth; anything else is returned as itself.
|
|
2422
|
+
*/
|
|
2423
|
+
expandResponseBranches(node, depth) {
|
|
2424
|
+
if (depth > RESPONSE_BRANCH_MAX_DEPTH)
|
|
2425
|
+
return [node];
|
|
2426
|
+
const expression = this.peelTransparentExpression(node);
|
|
2427
|
+
if (Node.isConditionalExpression(expression)) {
|
|
2428
|
+
return [
|
|
2429
|
+
...this.expandResponseBranches(expression.getWhenTrue(), depth + 1),
|
|
2430
|
+
...this.expandResponseBranches(expression.getWhenFalse(), depth + 1),
|
|
2431
|
+
];
|
|
2432
|
+
}
|
|
2433
|
+
return [expression];
|
|
2434
|
+
}
|
|
2204
2435
|
/**
|
|
2205
2436
|
* The deterministic source symbol of a resolved type (`Payment` for a payload
|
|
2206
2437
|
* typed `Payment`), or `undefined` when there is no single user-defined
|