carrick 0.3.125 → 0.3.126

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.
@@ -190,6 +190,18 @@ const SERIALISED_BODY_UNREAD_DETAIL = {
190
190
  };
191
191
  /** `any` or `unknown` as a type in printed type text, not as a member name or a string. */
192
192
  const TOP_TYPE_TOKEN = /(?<![\w$'"`])(?:any|unknown)(?![\w$'"`?:])/;
193
+ /**
194
+ * The type an assertion states, or undefined when it states none. A const
195
+ * assertion (`x as const`, `<const>x`) narrows its operand's own type and
196
+ * names no type: its type node prints as `const`, a name nothing declares
197
+ * (carrick#2590). The walk stops there rather than reaching for an assertion
198
+ * further out, which describes a larger value than the read.
199
+ */
200
+ function statedAssertionType(typeNode) {
201
+ if (!typeNode || ts.isConstTypeReference(typeNode.compilerNode))
202
+ return undefined;
203
+ return typeNode;
204
+ }
193
205
  /**
194
206
  * The items whose whitespace-collapsed texts differ, first of each kept, in
195
207
  * order: how a joined union drops a member it already has.
@@ -2553,7 +2565,24 @@ export class TypeInferrer {
2553
2565
  // is its response, and a config's type is the config, so either one read
2554
2566
  // as the body publishes the wrong contract. Follow the call's signature to
2555
2567
  // the argument it sends as the body.
2556
- const inCall = this.requestBodyInCall(unwrapped);
2568
+ let inCall = this.requestBodyInCall(unwrapped);
2569
+ if (inCall.kind === 'none') {
2570
+ // carrick#2628: the argument goes into a function that builds the body
2571
+ // itself. An abstain here is a decision: capture's own re-read of the
2572
+ // locator would publish the argument as the body.
2573
+ const built = this.bodyBuiltByCallee(unwrapped);
2574
+ if (built.kind === 'abstain') {
2575
+ this.log(`Request locator at ${request.file_path}:${request.line_number}: ${built.why}; ` +
2576
+ 'the argument is not the body');
2577
+ return this.decidedAbstain(request, this.getNodeLocation(unwrapped), {
2578
+ path: '',
2579
+ kind: 'unknown',
2580
+ reason: 'body_built_by_callee',
2581
+ detail: `the located argument is not the request body: ${built.why}`,
2582
+ });
2583
+ }
2584
+ inCall = built;
2585
+ }
2557
2586
  if (inCall.kind === 'body') {
2558
2587
  this.log(`Request locator at ${request.file_path}:${request.line_number} names ${inCall.from}; ` +
2559
2588
  'reading the body argument its signature declares');
@@ -2736,9 +2765,14 @@ export class TypeInferrer {
2736
2765
  // (res) => resolve(…)) })`): what the caller receives is what that
2737
2766
  // promise settles with, and the call made in the executor produces it.
2738
2767
  const nearestReturn = callExpr.getFirstAncestorByKind(SyntaxKind.ReturnStatement);
2768
+ // A return whose value is an object or array literal built around the
2769
+ // call (`return { ok: true, count: countOf(await client.add(…)) }`) hands
2770
+ // back the caller's own value, which holds the result as one part: it
2771
+ // answers for the call no more than a command table does (carrick#2628).
2739
2772
  const returnStmt = nearestReturn &&
2740
2773
  (this.findContainingFunctionForNode(nearestReturn) === func ||
2741
- this.callIsInReturnedPromiseExecutor(nearestReturn, func))
2774
+ this.callIsInReturnedPromiseExecutor(nearestReturn, func)) &&
2775
+ !this.callIsPartOfReturnedLiteral(callExpr, nearestReturn)
2742
2776
  ? nearestReturn
2743
2777
  : undefined;
2744
2778
  if (returnStmt) {
@@ -2752,8 +2786,9 @@ export class TypeInferrer {
2752
2786
  // the response, so the walk below has nothing to follow, and the call
2753
2787
  // itself would stand as the terminal and publish the transport object.
2754
2788
  // The read is the terminal, as it is when the walk finds it on a binding,
2755
- // and a call that takes the read and states what it returns says more
2756
- // than the read does (carrick#1382).
2789
+ // and a call that takes the read and states it (a schema, or a function
2790
+ // that returns it unchanged) says more than the read does (carrick#1382,
2791
+ // carrick#2595).
2757
2792
  const inPlaceRead = this.bodyReadOnCallValue(callExpr);
2758
2793
  if (inPlaceRead) {
2759
2794
  return {
@@ -2771,8 +2806,11 @@ export class TypeInferrer {
2771
2806
  /** The value itself was read: returned, passed, aliased, or body-read. */
2772
2807
  let sawWholeRead = false;
2773
2808
  // Where the call's own value states no type, a call that takes it whole
2774
- // is the first place the source states one, and the walk follows it
2775
- // (`parse(raw)` after `const raw = await readRaw()`, carrick#1375).
2809
+ // and states it (a schema, or a function that returns it unchanged) is
2810
+ // the first place the source states one, and the walk follows it
2811
+ // (`parse(raw)` after `const raw = await readRaw()`, carrick#1375). A
2812
+ // converter, or a declaration whose body cannot be read, states nothing
2813
+ // about the value (carrick#2595).
2776
2814
  const callValue = this.unwrapPromiseType(callExpr.getType());
2777
2815
  const callValueIsUntyped = callValue.isAny() || callValue.isUnknown();
2778
2816
  // A transport response handed whole to a helper is a body read through
@@ -2871,7 +2909,7 @@ export class TypeInferrer {
2871
2909
  // body, and answers as it did.
2872
2910
  if (this.expressionUsesNames(expr, currentNames) &&
2873
2911
  !this.usesNamesOnlyByProjection(expr, currentNames)) {
2874
- if (callValueIsUntyped ||
2912
+ if ((callValueIsUntyped && this.statesTrackedValue(expr, currentNames)) ||
2875
2913
  this.restatesTrackedValue(expr, currentNames) ||
2876
2914
  this.holdsBinaryBodyRead(expr, currentNames) ||
2877
2915
  (callValueIsTransport && this.handsTrackedValueToACall(expr, currentNames))) {
@@ -2907,18 +2945,51 @@ export class TypeInferrer {
2907
2945
  }
2908
2946
  return callExpr;
2909
2947
  }
2948
+ /**
2949
+ * Between `callExpr` and the return statement around it there is an object
2950
+ * or array literal: the call's result is one part of a value the function
2951
+ * builds and returns.
2952
+ */
2953
+ callIsPartOfReturnedLiteral(callExpr, returnStmt) {
2954
+ const ancestors = callExpr.getAncestors();
2955
+ return ancestors
2956
+ .slice(0, ancestors.indexOf(returnStmt))
2957
+ .some((a) => Node.isObjectLiteralExpression(a) || Node.isArrayLiteralExpression(a));
2958
+ }
2910
2959
  /**
2911
2960
  * `expr` declares a name for what a call returns when it is handed a
2912
2961
  * tracked name whole (`const data: T = await parseJson(response, 'op')`).
2913
2962
  */
2914
2963
  handsTrackedValueToACall(expr, names) {
2915
- if (!Node.isVariableDeclaration(expr))
2916
- return false;
2917
- const init = this.unwrapExpressionNode(expr.getInitializer());
2918
- return (Node.isCallExpression(init) &&
2919
- init
2920
- .getArguments()
2921
- .some((argument) => this.isIdentifierUsage(this.unwrapExpressionNode(argument), names)));
2964
+ return Node.isVariableDeclaration(expr) && this.callTakingTrackedValue(expr, names) !== undefined;
2965
+ }
2966
+ /**
2967
+ * The call whose result `expr` declares or assigns a name for, and the
2968
+ * index of its argument that is a tracked name whole, or undefined.
2969
+ */
2970
+ callTakingTrackedValue(expr, names) {
2971
+ const value = Node.isVariableDeclaration(expr)
2972
+ ? expr.getInitializer()
2973
+ : Node.isBinaryExpression(expr) && expr.getOperatorToken().getKind() === SyntaxKind.EqualsToken
2974
+ ? expr.getRight()
2975
+ : undefined;
2976
+ const init = this.unwrapExpressionNode(value);
2977
+ if (!Node.isCallExpression(init))
2978
+ return undefined;
2979
+ const index = init
2980
+ .getArguments()
2981
+ .findIndex((argument) => this.isIdentifierUsage(this.unwrapExpressionNode(argument), names));
2982
+ return index >= 0 ? { call: init, index } : undefined;
2983
+ }
2984
+ /**
2985
+ * `expr` names the result of a call that states the tracked value it takes
2986
+ * (`callStatesItsArgument`): a schema validates it, or the callee returns it
2987
+ * unchanged. A converter's result describes what it built from the value,
2988
+ * not the value (carrick#2595).
2989
+ */
2990
+ statesTrackedValue(expr, names) {
2991
+ const taking = this.callTakingTrackedValue(expr, names);
2992
+ return !!taking && this.callStatesItsArgument(taking.call, taking.index);
2922
2993
  }
2923
2994
  /** `expr` holds a binary whole-body read taken on a tracked name. */
2924
2995
  holdsBinaryBodyRead(expr, names) {
@@ -3505,12 +3576,18 @@ export class TypeInferrer {
3505
3576
  * serialiser answering `string` describes what the caller did with the
3506
3577
  * body, not what the body is, and publishing it would be a
3507
3578
  * concrete-but-wrong contract where the honest `any` of the read is
3508
- * merely unresolved.
3579
+ * merely unresolved;
3580
+ * - the call states its argument (`callStatesItsArgument`): a schema
3581
+ * validates it, or the callee returns it unchanged. A converter's
3582
+ * object result describes what it built, not the body (carrick#2595).
3509
3583
  */
3510
3584
  statedPayloadAroundBodyRead(bodyRead) {
3511
3585
  return this.receivingCallOf(bodyRead, (candidate) => Node.isCallExpression(candidate) &&
3512
3586
  this.callResultIsBound(candidate) &&
3513
- this.isObjectShape(this.unwrapPromiseType(candidate.getType())));
3587
+ this.isObjectShape(this.unwrapPromiseType(candidate.getType())) &&
3588
+ this.callStatesItsArgument(candidate, candidate
3589
+ .getArguments()
3590
+ .findIndex((argument) => argument.getStart() <= bodyRead.getStart() && bodyRead.getEnd() <= argument.getEnd())));
3514
3591
  }
3515
3592
  /**
3516
3593
  * The source keeps this call's result: it initializes a declaration, is
@@ -4515,22 +4592,14 @@ export class TypeInferrer {
4515
4592
  const asExpr = Node.isAsExpression(node)
4516
4593
  ? node
4517
4594
  : node.getFirstAncestorByKind(SyntaxKind.AsExpression);
4518
- if (asExpr) {
4519
- const typeNode = asExpr.getTypeNode();
4520
- if (typeNode) {
4521
- return typeNode;
4522
- }
4595
+ const asType = statedAssertionType(asExpr?.getTypeNode());
4596
+ if (asType) {
4597
+ return asType;
4523
4598
  }
4524
4599
  const typeAssertion = Node.isTypeAssertion(node)
4525
4600
  ? node
4526
4601
  : node.getFirstAncestorByKind(SyntaxKind.TypeAssertionExpression);
4527
- if (typeAssertion) {
4528
- const typeNode = typeAssertion.getTypeNode();
4529
- if (typeNode) {
4530
- return typeNode;
4531
- }
4532
- }
4533
- return undefined;
4602
+ return statedAssertionType(typeAssertion?.getTypeNode());
4534
4603
  }
4535
4604
  /**
4536
4605
  * Render an explicit annotation (`as T`, `<T>`, or a typed binding) as
@@ -6951,6 +7020,231 @@ export class TypeInferrer {
6951
7020
  }
6952
7021
  return { kind: 'none' };
6953
7022
  }
7023
+ /**
7024
+ * carrick#2628: the located node is an argument of a call into a function
7025
+ * whose body the program holds (an in-repo client method,
7026
+ * `client.addTags(project, unique(tags))`), at a parameter that is not a
7027
+ * body slot. What the call sends is decided inside that function, so the
7028
+ * argument is the body only when the function sends it unchanged. One
7029
+ * level, read off each use of the parameter in the function's body:
7030
+ *
7031
+ * - the use IS the value of a body-named member (`body: tags`,
7032
+ * `body: JSON.stringify(tags)`) or of a body slot of a call: the
7033
+ * argument is the body, and its own type stands (`none`);
7034
+ * - the use sits inside such a value (`body: { tags }`,
7035
+ * `body: (event ?? {}) as never`): the body is that value, read at the
7036
+ * function without the casts around it;
7037
+ * - the use sits inside an object or array literal that is no body value
7038
+ * (`send(project, [labels])`): the body cannot be told, abstain.
7039
+ *
7040
+ * Uses that disagree abstain. A generic function's body may be typed by
7041
+ * its type parameters, so it abstains too. A library's declaration
7042
+ * (`fetch`, an installed client) has no body to read and is left alone,
7043
+ * and so is an untyped argument (a handler's `request.body` handed to a
7044
+ * helper): it states no contract to mistake, and the untyped-read path
7045
+ * below reads it.
7046
+ */
7047
+ bodyBuiltByCallee(node) {
7048
+ const own = node.getType();
7049
+ if (own.isAny() || own.isUnknown())
7050
+ return { kind: 'none' };
7051
+ const call = this.receivingCallOf(node, (candidate) => Node.isCallExpression(candidate));
7052
+ if (!call || !Node.isCallExpression(call))
7053
+ return { kind: 'none' };
7054
+ const index = call
7055
+ .getArguments()
7056
+ .findIndex((a) => a.getPos() <= node.getPos() && node.getEnd() <= a.getEnd());
7057
+ if (index < 0)
7058
+ return { kind: 'none' };
7059
+ if (this.requestBodySlots(call)[index])
7060
+ return { kind: 'none' };
7061
+ const callee = this.project.getTypeChecker().getResolvedSignature(call)?.getDeclaration();
7062
+ if (!callee ||
7063
+ !(Node.isFunctionDeclaration(callee) ||
7064
+ Node.isMethodDeclaration(callee) ||
7065
+ Node.isArrowFunction(callee) ||
7066
+ Node.isFunctionExpression(callee))) {
7067
+ return { kind: 'none' };
7068
+ }
7069
+ const body = callee.getBody();
7070
+ if (!body)
7071
+ return { kind: 'none' };
7072
+ const program = this.project.getProgram().compilerObject;
7073
+ if (isExternalOrigin(program, callee.getSourceFile().compilerNode, this.repoRoot, externalImportsOf(this.project))) {
7074
+ return { kind: 'none' };
7075
+ }
7076
+ const param = callee.getParameters()[index];
7077
+ if (!param || param.isRestParameter())
7078
+ return { kind: 'none' };
7079
+ const paramName = param.getNameNode();
7080
+ if (!Node.isIdentifier(paramName))
7081
+ return { kind: 'none' };
7082
+ const paramSymbol = paramName.getSymbol()?.compilerSymbol;
7083
+ if (!paramSymbol)
7084
+ return { kind: 'none' };
7085
+ const checker = this.project.getTypeChecker().compilerObject;
7086
+ const uses = body.getDescendantsOfKind(SyntaxKind.Identifier).filter((id) => {
7087
+ const parent = id.getParent();
7088
+ const symbol = parent && Node.isShorthandPropertyAssignment(parent)
7089
+ ? checker.getShorthandAssignmentValueSymbol(parent.compilerNode)
7090
+ : id.getSymbol()?.compilerSymbol;
7091
+ return symbol === paramSymbol;
7092
+ });
7093
+ const from = 'a call into a function that builds the request body inside itself';
7094
+ let passedThrough = false;
7095
+ const built = [];
7096
+ for (const use of uses) {
7097
+ // carrick#2668: `data ? JSON.stringify(data) : undefined` tests the
7098
+ // argument before it sends it; the test is no body.
7099
+ if (this.inSendNothingTest(use, body))
7100
+ continue;
7101
+ const value = this.bodyValueAround(use, body);
7102
+ if (value === use) {
7103
+ passedThrough = true;
7104
+ continue;
7105
+ }
7106
+ if (value) {
7107
+ built.push(value);
7108
+ continue;
7109
+ }
7110
+ const ancestors = use.getAncestors();
7111
+ const insideLiteral = ancestors
7112
+ .slice(0, ancestors.indexOf(body))
7113
+ .some((a) => Node.isObjectLiteralExpression(a) || Node.isArrayLiteralExpression(a));
7114
+ if (insideLiteral) {
7115
+ return {
7116
+ kind: 'abstain',
7117
+ why: `the located argument goes into a value the called function builds and does not send as a body (${from})`,
7118
+ };
7119
+ }
7120
+ }
7121
+ if (built.length === 0)
7122
+ return { kind: 'none' };
7123
+ if (passedThrough) {
7124
+ return { kind: 'abstain', why: `the called function both sends the argument and builds a body from it (${from})` };
7125
+ }
7126
+ if (callee.getTypeParameters().length > 0) {
7127
+ return { kind: 'abstain', why: `the called function is generic, so the body it builds may be typed by its type parameters (${from})` };
7128
+ }
7129
+ const types = built.map((value) => this.sentValue(value));
7130
+ const texts = new Set(types.map((value) => typeText(value.getType(), value)));
7131
+ if (texts.size !== 1) {
7132
+ return { kind: 'abstain', why: `the called function builds more than one body from the argument (${from})` };
7133
+ }
7134
+ const at = types[0];
7135
+ const type = at.getType();
7136
+ if (type.isTypeParameter() || printedTypeHoldsTopType([...texts][0])) {
7137
+ return { kind: 'abstain', why: `the body the called function builds is not fully typed (${from})` };
7138
+ }
7139
+ return { kind: 'body_type', type, at, from };
7140
+ }
7141
+ /**
7142
+ * The request body value `use` is part of, inside `scope`: the value of a
7143
+ * body-named object member, or the argument at a body slot of a call,
7144
+ * looking through parentheses, casts, `await` and a `JSON.stringify`
7145
+ * around it. `use` itself when it is that value whole.
7146
+ */
7147
+ bodyValueAround(use, scope) {
7148
+ let current = use;
7149
+ while (current !== scope) {
7150
+ const parent = current.getParent();
7151
+ if (!parent)
7152
+ return undefined;
7153
+ if (Node.isShorthandPropertyAssignment(parent) &&
7154
+ current === use &&
7155
+ BODY_MEMBER_NAMES.has(parent.getName())) {
7156
+ return use;
7157
+ }
7158
+ if (Node.isPropertyAssignment(parent) &&
7159
+ parent.getInitializer() === current &&
7160
+ BODY_MEMBER_NAMES.has(parent.getName())) {
7161
+ return this.isSameValue(current, use) ? use : current;
7162
+ }
7163
+ if (Node.isCallExpression(parent)) {
7164
+ const index = parent.getArguments().indexOf(current);
7165
+ if (index >= 0 && this.requestBodySlots(parent)[index]?.kind === 'body') {
7166
+ return this.isSameValue(current, use) ? use : current;
7167
+ }
7168
+ }
7169
+ current = parent;
7170
+ }
7171
+ return undefined;
7172
+ }
7173
+ /** `value` is `use` behind what `sentValue` sees through. */
7174
+ isSameValue(value, use) {
7175
+ return this.sentValue(value) === use;
7176
+ }
7177
+ /**
7178
+ * The value a body slot sends when it sends anything, behind parentheses,
7179
+ * casts, `await`, a `JSON.stringify`, and (carrick#2668) a choice whose
7180
+ * other side sends nothing: `x ? JSON.stringify(x) : undefined`,
7181
+ * `x || null`, `x ?? undefined`. A `?? {}` sends `{}`, so it is a body the
7182
+ * function builds and is not seen through.
7183
+ */
7184
+ sentValue(value) {
7185
+ let current = value;
7186
+ for (;;) {
7187
+ let inner = this.unwrapJsonStringifyArg(this.unwrapExpressionNode(current));
7188
+ if (Node.isConditionalExpression(inner)) {
7189
+ inner = this.sendingBranch(inner) ?? inner;
7190
+ }
7191
+ else if (Node.isBinaryExpression(inner)) {
7192
+ const operator = inner.getOperatorToken().getKind();
7193
+ if ((operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) &&
7194
+ this.sendsNothing(inner.getRight(), false)) {
7195
+ inner = inner.getLeft();
7196
+ }
7197
+ }
7198
+ if (inner === current)
7199
+ return current;
7200
+ current = inner;
7201
+ }
7202
+ }
7203
+ /**
7204
+ * The branch of `choice` that sends something, when its other branch sends
7205
+ * nothing (`undefined`, `null`, `void 0`, or `{}` spread into a request).
7206
+ */
7207
+ sendingBranch(choice) {
7208
+ const whenTrue = choice.getWhenTrue();
7209
+ const whenFalse = choice.getWhenFalse();
7210
+ if (this.sendsNothing(whenFalse, true) && !this.sendsNothing(whenTrue, true))
7211
+ return whenTrue;
7212
+ if (this.sendsNothing(whenTrue, true) && !this.sendsNothing(whenFalse, true))
7213
+ return whenFalse;
7214
+ return undefined;
7215
+ }
7216
+ /** `undefined`, `null`, `void 0`, or (where `emptyObject`) `{}`. */
7217
+ sendsNothing(node, emptyObject) {
7218
+ const value = this.unwrapExpressionNode(node);
7219
+ if (Node.isNullLiteral(value) || Node.isVoidExpression(value))
7220
+ return true;
7221
+ if (Node.isIdentifier(value) && value.getText() === 'undefined')
7222
+ return true;
7223
+ return (emptyObject &&
7224
+ Node.isObjectLiteralExpression(value) &&
7225
+ value.getProperties().length === 0);
7226
+ }
7227
+ /**
7228
+ * carrick#2668, #2664: `use` sits in the test of a choice whose other side
7229
+ * sends nothing (`body ? JSON.stringify(body) : null`,
7230
+ * `...(body === undefined ? {} : { body: JSON.stringify(body) })`). The
7231
+ * test only decides whether the argument is sent.
7232
+ */
7233
+ inSendNothingTest(use, scope) {
7234
+ let current = use;
7235
+ while (current !== scope) {
7236
+ const parent = current.getParent();
7237
+ if (!parent)
7238
+ return false;
7239
+ if (Node.isConditionalExpression(parent) &&
7240
+ parent.getCondition() === current &&
7241
+ this.sendingBranch(parent)) {
7242
+ return true;
7243
+ }
7244
+ current = parent;
7245
+ }
7246
+ return false;
7247
+ }
6954
7248
  /** A string, a string literal, a template literal, or a union of strings. */
6955
7249
  isStringLikeType(type) {
6956
7250
  return (type.isString() ||
@@ -7480,25 +7774,10 @@ export class TypeInferrer {
7480
7774
  * `reads.parameters`, the parameter's own type (rank 4).
7481
7775
  */
7482
7776
  stateAtReceivingCall(call, index, reads, found, stateNode) {
7483
- const schemaCandidates = [];
7484
- const callee = call.getExpression();
7485
- if (Node.isPropertyAccessExpression(callee))
7486
- schemaCandidates.push(callee.getExpression());
7487
- call.getArguments().forEach((argument, at) => {
7488
- if (at !== index)
7489
- schemaCandidates.push(this.unwrapExpressionNode(argument));
7490
- });
7491
- for (const candidate of schemaCandidates) {
7492
- const candidateType = candidate.getType();
7493
- if (!this.schemaOutputType(candidateType, candidate) &&
7494
- !this.schemaInputType(candidateType, candidate)) {
7495
- continue;
7496
- }
7497
- const contract = this.schemaContract(candidate, 'input');
7498
- if (contract) {
7499
- found.push({ rank: 1, text: contract.text, provenance: contract.provenance, at: call, how: 'a schema it is handed to' });
7500
- return;
7501
- }
7777
+ const contract = this.schemaHandedArgument(call, index);
7778
+ if (contract) {
7779
+ found.push({ rank: 1, text: contract.text, provenance: contract.provenance, at: call, how: 'a schema it is handed to' });
7780
+ return;
7502
7781
  }
7503
7782
  const passThrough = this.passThroughFunction(call, index);
7504
7783
  if (passThrough) {
@@ -7527,6 +7806,44 @@ export class TypeInferrer {
7527
7806
  return;
7528
7807
  found.push({ rank: 4, text, at: call, how: 'the parameter of a call it is handed to' });
7529
7808
  }
7809
+ /**
7810
+ * The input contract of a schema `call` validates its argument at `index`
7811
+ * with: its receiver (`Schema.safeParse(body)`) or one of its other
7812
+ * arguments (`parse(Schema, body)`), or null. A candidate counts only when
7813
+ * it declares schema types, so no method or library name is matched
7814
+ * (carrick#1166).
7815
+ */
7816
+ schemaHandedArgument(call, index) {
7817
+ const candidates = [];
7818
+ const callee = call.getExpression();
7819
+ if (Node.isPropertyAccessExpression(callee))
7820
+ candidates.push(callee.getExpression());
7821
+ call.getArguments().forEach((argument, at) => {
7822
+ if (at !== index)
7823
+ candidates.push(this.unwrapExpressionNode(argument));
7824
+ });
7825
+ for (const candidate of candidates) {
7826
+ const candidateType = candidate.getType();
7827
+ if (!this.schemaOutputType(candidateType, candidate) &&
7828
+ !this.schemaInputType(candidateType, candidate)) {
7829
+ continue;
7830
+ }
7831
+ const contract = this.schemaContract(candidate, 'input');
7832
+ if (contract)
7833
+ return contract;
7834
+ }
7835
+ return null;
7836
+ }
7837
+ /**
7838
+ * `call`'s result describes the value it takes at argument `index`: it
7839
+ * validates it with a schema, or its callee returns it unchanged
7840
+ * (`passThroughFunction`). Any other call builds something new from the
7841
+ * value, and its result describes what it built, never the value it was
7842
+ * handed (carrick#2595).
7843
+ */
7844
+ callStatesItsArgument(call, index) {
7845
+ return index >= 0 && (!!this.schemaHandedArgument(call, index) || !!this.passThroughFunction(call, index));
7846
+ }
7530
7847
  /**
7531
7848
  * The project function `call` invokes, when every value it returns is its
7532
7849
  * argument at `index` as it was handed in (through parentheses, casts,
@@ -271,6 +271,43 @@ export interface PositionRowsRequest extends BaseRequest {
271
271
  /** The service the rows are for, written in the header. */
272
272
  service: string;
273
273
  }
274
+ /**
275
+ * The implementations of the bodyless declarations some calls go through
276
+ * (carrick#2432 slice B), asked of the analysis process: one batch per
277
+ * service.
278
+ */
279
+ export interface ImplementationsRequest extends BaseRequest {
280
+ action: 'implementations';
281
+ /** One question per call, each the span of its `call` (or `member`) position row. */
282
+ queries: ImplementationsQuery[];
283
+ /** The scanned repo's root: every name in an answer is relative to it. */
284
+ scan_root: string;
285
+ }
286
+ export interface ImplementationsQuery {
287
+ /** Absolute path of the file holding the call. */
288
+ file: string;
289
+ /** The row's span: UTF-16 code units from the start of the file. */
290
+ start: number;
291
+ end: number;
292
+ }
293
+ /** A declaration as a position row names one. */
294
+ export interface NamedDeclaration {
295
+ symbol: string | null;
296
+ decl_file: string | null;
297
+ decl_line: number | null;
298
+ decl_package: string | null;
299
+ }
300
+ /** Why an answer names no implementation, where it is not because there is none. */
301
+ export type ImplementationsReason = 'not_in_program' | 'no_call' | 'no_symbol' | 'no_declaration' | 'has_body' | 'checker_threw';
302
+ export interface ImplementationsAnswer {
303
+ /** The declaration the call goes through, as its row names it. */
304
+ declaration: NamedDeclaration | null;
305
+ /** By file, then position; empty when `too_many`. */
306
+ implementations: NamedDeclaration[];
307
+ /** More than the cap (16) were found, and none is listed. */
308
+ too_many: boolean;
309
+ reason?: ImplementationsReason;
310
+ }
274
311
  /**
275
312
  * Request to resolve type definitions from a v2 capture stub package.
276
313
  * Returns both the original declaration and the compiler-expanded form for
@@ -585,7 +622,7 @@ export interface ListLibrarySurfaceRequest extends BaseRequest {
585
622
  /**
586
623
  * Union type for all possible sidecar requests
587
624
  */
588
- export type SidecarRequest = RetypeCheckRequest | VerifyClientSemanticsRequest | VerifyLibraryClaimsRequest | ListLibrarySurfaceRequest | InitRequest | BundleRequest | CaptureV2Request | CheckV2Request | InferRequest | ResolveDefinitionsRequest | HealthRequest | ShutdownRequest | ListProgramFilesRequest | AddProgramFilesRequest | PositionRowsRequest;
625
+ export type SidecarRequest = RetypeCheckRequest | VerifyClientSemanticsRequest | VerifyLibraryClaimsRequest | ListLibrarySurfaceRequest | InitRequest | BundleRequest | CaptureV2Request | CheckV2Request | InferRequest | ResolveDefinitionsRequest | HealthRequest | ShutdownRequest | ListProgramFilesRequest | AddProgramFilesRequest | PositionRowsRequest | ImplementationsRequest;
589
626
  /**
590
627
  * Request for a specific symbol to be bundled
591
628
  */
@@ -775,6 +812,13 @@ export interface HealthResponse extends BaseResponse {
775
812
  export interface ShutdownResponse extends BaseResponse {
776
813
  status: 'success';
777
814
  }
815
+ /** Response for implementations: one answer per question, in request order. */
816
+ export interface ImplementationsResponse extends BaseResponse {
817
+ result?: {
818
+ answers: ImplementationsAnswer[];
819
+ };
820
+ errors?: string[];
821
+ }
778
822
  /** Response for position_rows: what was written, counted. */
779
823
  export interface PositionRowsResponse extends BaseResponse {
780
824
  result?: PositionRowsCounts;
@@ -962,7 +1006,7 @@ export interface ListLibrarySurfaceResponse extends BaseResponse {
962
1006
  /**
963
1007
  * Union type for all possible sidecar responses
964
1008
  */
965
- export type SidecarResponse = RetypeCheckResponse | VerifyClientSemanticsResponse | VerifyLibraryClaimsResponse | ListLibrarySurfaceResponse | InitResponse | BundleResponse | CaptureV2Response | CheckV2Response | InferResponse | ResolveDefinitionsResponse | HealthResponse | ShutdownResponse | ListProgramFilesResponse | AddProgramFilesResponse | PositionRowsResponse | ErrorResponse;
1009
+ export type SidecarResponse = RetypeCheckResponse | VerifyClientSemanticsResponse | VerifyLibraryClaimsResponse | ListLibrarySurfaceResponse | InitResponse | BundleResponse | CaptureV2Response | CheckV2Response | InferResponse | ResolveDefinitionsResponse | HealthResponse | ShutdownResponse | ListProgramFilesResponse | AddProgramFilesResponse | PositionRowsResponse | ImplementationsResponse | ErrorResponse;
966
1010
  /**
967
1011
  * An entry in the type manifest
968
1012
  */
@@ -848,6 +848,43 @@ export declare const PositionRowsRequestSchema: z.ZodObject<{
848
848
  scan_root: string;
849
849
  out_path: string;
850
850
  }>;
851
+ export declare const ImplementationsRequestSchema: z.ZodObject<{
852
+ request_id: z.ZodString;
853
+ } & {
854
+ action: z.ZodLiteral<"implementations">;
855
+ queries: z.ZodArray<z.ZodObject<{
856
+ file: z.ZodString;
857
+ start: z.ZodNumber;
858
+ end: z.ZodNumber;
859
+ }, "strip", z.ZodTypeAny, {
860
+ file: string;
861
+ end: number;
862
+ start: number;
863
+ }, {
864
+ file: string;
865
+ end: number;
866
+ start: number;
867
+ }>, "many">;
868
+ scan_root: z.ZodString;
869
+ }, "strip", z.ZodTypeAny, {
870
+ queries: {
871
+ file: string;
872
+ end: number;
873
+ start: number;
874
+ }[];
875
+ request_id: string;
876
+ action: "implementations";
877
+ scan_root: string;
878
+ }, {
879
+ queries: {
880
+ file: string;
881
+ end: number;
882
+ start: number;
883
+ }[];
884
+ request_id: string;
885
+ action: "implementations";
886
+ scan_root: string;
887
+ }>;
851
888
  export declare const ResolveDefinitionsRequestSchema: z.ZodObject<{
852
889
  request_id: z.ZodString;
853
890
  } & {
@@ -4506,6 +4543,42 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
4506
4543
  action: "position_rows";
4507
4544
  scan_root: string;
4508
4545
  out_path: string;
4546
+ }>, z.ZodObject<{
4547
+ request_id: z.ZodString;
4548
+ } & {
4549
+ action: z.ZodLiteral<"implementations">;
4550
+ queries: z.ZodArray<z.ZodObject<{
4551
+ file: z.ZodString;
4552
+ start: z.ZodNumber;
4553
+ end: z.ZodNumber;
4554
+ }, "strip", z.ZodTypeAny, {
4555
+ file: string;
4556
+ end: number;
4557
+ start: number;
4558
+ }, {
4559
+ file: string;
4560
+ end: number;
4561
+ start: number;
4562
+ }>, "many">;
4563
+ scan_root: z.ZodString;
4564
+ }, "strip", z.ZodTypeAny, {
4565
+ queries: {
4566
+ file: string;
4567
+ end: number;
4568
+ start: number;
4569
+ }[];
4570
+ request_id: string;
4571
+ action: "implementations";
4572
+ scan_root: string;
4573
+ }, {
4574
+ queries: {
4575
+ file: string;
4576
+ end: number;
4577
+ start: number;
4578
+ }[];
4579
+ request_id: string;
4580
+ action: "implementations";
4581
+ scan_root: string;
4509
4582
  }>]>;
4510
4583
  /**
4511
4584
  * Result type for parseRequest