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.
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/api.d.ts +5 -1
- package/sidecar/dist/src/capture/deno-project.d.ts +7 -0
- package/sidecar/dist/src/capture/deno-project.js +7 -0
- package/sidecar/dist/src/declaration-attribution.d.ts +11 -0
- package/sidecar/dist/src/declaration-attribution.js +16 -0
- package/sidecar/dist/src/implementations.d.ts +43 -0
- package/sidecar/dist/src/implementations.js +224 -0
- package/sidecar/dist/src/index.js +35 -1
- package/sidecar/dist/src/library-claims.d.ts +17 -2
- package/sidecar/dist/src/library-claims.js +242 -47
- package/sidecar/dist/src/position-rows.d.ts +119 -1
- package/sidecar/dist/src/position-rows.js +163 -9
- package/sidecar/dist/src/project-loader.d.ts +15 -0
- package/sidecar/dist/src/project-loader.js +16 -0
- package/sidecar/dist/src/type-inferrer.d.ts +94 -1
- package/sidecar/dist/src/type-inferrer.js +364 -47
- package/sidecar/dist/src/types.d.ts +46 -2
- package/sidecar/dist/src/validators.d.ts +73 -0
- package/sidecar/dist/src/validators.js +10 -0
|
@@ -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
|
-
|
|
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
|
|
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
|
-
//
|
|
2775
|
-
//
|
|
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
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
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
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
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
|
-
|
|
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
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
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
|