carrick 0.3.103 → 0.3.105
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 +6 -0
- package/sidecar/dist/src/capture/anchors.js +78 -13
- package/sidecar/dist/src/capture/api.d.ts +27 -2
- package/sidecar/dist/src/capture/check-classify.d.ts +1 -0
- package/sidecar/dist/src/capture/check-classify.js +23 -0
- package/sidecar/dist/src/capture/check-fields.js +4 -6
- package/sidecar/dist/src/capture/check-probe.d.ts +1 -1
- package/sidecar/dist/src/capture/check-probe.js +28 -4
- 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 +64 -50
- 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.d.ts +1 -0
- package/sidecar/dist/src/capture/index.js +11 -3
- 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 +41 -9
- package/sidecar/dist/src/capture/service-config.d.ts +19 -0
- package/sidecar/dist/src/capture/service-config.js +57 -0
- package/sidecar/dist/src/failure-path.d.ts +36 -0
- package/sidecar/dist/src/failure-path.js +212 -0
- package/sidecar/dist/src/index.js +2 -0
- package/sidecar/dist/src/project-loader.d.ts +10 -2
- package/sidecar/dist/src/project-loader.js +7 -16
- package/sidecar/dist/src/retype.js +15 -2
- package/sidecar/dist/src/type-inferrer.d.ts +2 -0
- package/sidecar/dist/src/type-inferrer.js +17 -0
- package/sidecar/dist/src/types.d.ts +8 -0
- package/sidecar/dist/src/validators.d.ts +12 -0
- package/sidecar/dist/src/validators.js +2 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the source reaches a node only when an HTTP response FAILED
|
|
3
|
+
* (carrick#1796).
|
|
4
|
+
*
|
|
5
|
+
* A consumer reads the success body on one path and the error text or the
|
|
6
|
+
* error body on another. Only the first is the call's response contract, so
|
|
7
|
+
* the def-use walk behind a `call_result` row must not take a read from the
|
|
8
|
+
* second. Which path a read is on is told by the source's own tests of the
|
|
9
|
+
* response, read two ways:
|
|
10
|
+
*
|
|
11
|
+
* - the read sits in a branch of a test: `if (!res.ok) { ... }`, the `else`
|
|
12
|
+
* of `if (res.ok)`, either arm of a conditional expression;
|
|
13
|
+
* - the read follows an `if` whose other branch cannot complete, so the rest
|
|
14
|
+
* of the block runs only on the side that did not leave:
|
|
15
|
+
* `if (res.ok) { return ... } const text = await res.text()`.
|
|
16
|
+
*
|
|
17
|
+
* A test is read as the set of statuses it lets through. `ok` is true for
|
|
18
|
+
* 200-299 and false for every other status; a comparison of the status with a
|
|
19
|
+
* number lets through what it admits; `!`, `&&` and `||` combine the sets, and
|
|
20
|
+
* any other condition lets everything through. A node is on the failure path
|
|
21
|
+
* only when NO status in 200-299 can reach it.
|
|
22
|
+
*
|
|
23
|
+
* That is deliberately stricter than the retype check's reading of a status
|
|
24
|
+
* test (`okWhenTrue` in retype.ts), which reads the false side of
|
|
25
|
+
* `res.status === 200` as the failure path so it can find a success read to
|
|
26
|
+
* judge. Here a decided failure REMOVES a read, so the reading has to be sound
|
|
27
|
+
* the other way round: after `if (res.status === 204) return null`, 200 still
|
|
28
|
+
* gets through, and the json read that follows is the payload.
|
|
29
|
+
*/
|
|
30
|
+
import { Node, SyntaxKind } from 'ts-morph';
|
|
31
|
+
const FIRST_STATUS = 100;
|
|
32
|
+
const LAST_STATUS = 599;
|
|
33
|
+
const EVERY_STATUS = () => true;
|
|
34
|
+
/** The statuses `ok` is true for. */
|
|
35
|
+
const SUCCEEDED = (status) => status >= 200 && status <= 299;
|
|
36
|
+
const UNDECIDED = { whenTrue: EVERY_STATUS, whenFalse: EVERY_STATUS };
|
|
37
|
+
/**
|
|
38
|
+
* True when the source reaches `node` only after the response named by
|
|
39
|
+
* `isResponse` failed. The walk climbs from `node` to `boundary` (the function
|
|
40
|
+
* the call sits in) and never looks at a test outside it.
|
|
41
|
+
*/
|
|
42
|
+
export function reachedOnlyOnFailure(node, boundary, isResponse) {
|
|
43
|
+
let admitted = EVERY_STATUS;
|
|
44
|
+
let narrowed = false;
|
|
45
|
+
const narrow = (by) => {
|
|
46
|
+
if (by === EVERY_STATUS)
|
|
47
|
+
return;
|
|
48
|
+
const before = admitted;
|
|
49
|
+
admitted = (status) => before(status) && by(status);
|
|
50
|
+
narrowed = true;
|
|
51
|
+
};
|
|
52
|
+
for (let child = node, parent = node.getParent(); parent && parent !== boundary; child = parent, parent = parent.getParent()) {
|
|
53
|
+
if (Node.isIfStatement(parent)) {
|
|
54
|
+
if (child === parent.getThenStatement()) {
|
|
55
|
+
narrow(readTest(parent.getExpression(), isResponse).whenTrue);
|
|
56
|
+
}
|
|
57
|
+
else if (child === parent.getElseStatement()) {
|
|
58
|
+
narrow(readTest(parent.getExpression(), isResponse).whenFalse);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (Node.isConditionalExpression(parent)) {
|
|
62
|
+
if (child === parent.getWhenTrue()) {
|
|
63
|
+
narrow(readTest(parent.getCondition(), isResponse).whenTrue);
|
|
64
|
+
}
|
|
65
|
+
else if (child === parent.getWhenFalse()) {
|
|
66
|
+
narrow(readTest(parent.getCondition(), isResponse).whenFalse);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (Node.isBlock(parent) ||
|
|
70
|
+
Node.isSourceFile(parent) ||
|
|
71
|
+
Node.isCaseClause(parent) ||
|
|
72
|
+
Node.isDefaultClause(parent)) {
|
|
73
|
+
for (const statement of parent.getStatements()) {
|
|
74
|
+
if (statement === child)
|
|
75
|
+
break;
|
|
76
|
+
if (!Node.isIfStatement(statement))
|
|
77
|
+
continue;
|
|
78
|
+
const otherwise = statement.getElseStatement();
|
|
79
|
+
const thenLeaves = cannotComplete(statement.getThenStatement());
|
|
80
|
+
const elseLeaves = otherwise !== undefined && cannotComplete(otherwise);
|
|
81
|
+
if (thenLeaves && !elseLeaves) {
|
|
82
|
+
narrow(readTest(statement.getExpression(), isResponse).whenFalse);
|
|
83
|
+
}
|
|
84
|
+
else if (elseLeaves && !thenLeaves) {
|
|
85
|
+
narrow(readTest(statement.getExpression(), isResponse).whenTrue);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!narrowed)
|
|
91
|
+
return false;
|
|
92
|
+
let reachable = false;
|
|
93
|
+
for (let status = FIRST_STATUS; status <= LAST_STATUS; status++) {
|
|
94
|
+
if (!admitted(status))
|
|
95
|
+
continue;
|
|
96
|
+
if (SUCCEEDED(status))
|
|
97
|
+
return false;
|
|
98
|
+
reachable = true;
|
|
99
|
+
}
|
|
100
|
+
return reachable;
|
|
101
|
+
}
|
|
102
|
+
/** The statuses `condition` lets through when it is true and when it is false. */
|
|
103
|
+
function readTest(condition, isResponse) {
|
|
104
|
+
let test = condition;
|
|
105
|
+
while (Node.isParenthesizedExpression(test))
|
|
106
|
+
test = test.getExpression();
|
|
107
|
+
if (Node.isPrefixUnaryExpression(test) &&
|
|
108
|
+
test.getOperatorToken() === SyntaxKind.ExclamationToken) {
|
|
109
|
+
const inner = readTest(test.getOperand(), isResponse);
|
|
110
|
+
return { whenTrue: inner.whenFalse, whenFalse: inner.whenTrue };
|
|
111
|
+
}
|
|
112
|
+
if (isMemberOfResponse(test, 'ok', isResponse)) {
|
|
113
|
+
return { whenTrue: SUCCEEDED, whenFalse: (status) => !SUCCEEDED(status) };
|
|
114
|
+
}
|
|
115
|
+
if (Node.isBinaryExpression(test)) {
|
|
116
|
+
const operator = test.getOperatorToken().getKind();
|
|
117
|
+
if (operator === SyntaxKind.AmpersandAmpersandToken ||
|
|
118
|
+
operator === SyntaxKind.BarBarToken) {
|
|
119
|
+
const left = readTest(test.getLeft(), isResponse);
|
|
120
|
+
const right = readTest(test.getRight(), isResponse);
|
|
121
|
+
if (left === UNDECIDED && right === UNDECIDED)
|
|
122
|
+
return UNDECIDED;
|
|
123
|
+
return operator === SyntaxKind.AmpersandAmpersandToken
|
|
124
|
+
? {
|
|
125
|
+
whenTrue: (status) => left.whenTrue(status) && right.whenTrue(status),
|
|
126
|
+
whenFalse: (status) => left.whenFalse(status) || right.whenFalse(status),
|
|
127
|
+
}
|
|
128
|
+
: {
|
|
129
|
+
whenTrue: (status) => left.whenTrue(status) || right.whenTrue(status),
|
|
130
|
+
whenFalse: (status) => left.whenFalse(status) && right.whenFalse(status),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const compared = statusComparison(test.getLeft(), operator, test.getRight(), isResponse);
|
|
134
|
+
if (compared) {
|
|
135
|
+
return { whenTrue: compared, whenFalse: (status) => !compared(status) };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return UNDECIDED;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* `res.status <op> N` or `N <op> res.status`, as the statuses it is true for;
|
|
142
|
+
* `undefined` when the comparison is not one of the response's status with a
|
|
143
|
+
* number.
|
|
144
|
+
*/
|
|
145
|
+
function statusComparison(left, operator, right, isResponse) {
|
|
146
|
+
let op = operator;
|
|
147
|
+
let value;
|
|
148
|
+
if (isMemberOfResponse(left, 'status', isResponse) && Node.isNumericLiteral(right)) {
|
|
149
|
+
value = right.getLiteralValue();
|
|
150
|
+
}
|
|
151
|
+
else if (isMemberOfResponse(right, 'status', isResponse) && Node.isNumericLiteral(left)) {
|
|
152
|
+
value = left.getLiteralValue();
|
|
153
|
+
op = MIRRORED[op] ?? op;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
switch (op) {
|
|
159
|
+
case SyntaxKind.EqualsEqualsEqualsToken:
|
|
160
|
+
case SyntaxKind.EqualsEqualsToken:
|
|
161
|
+
return (status) => status === value;
|
|
162
|
+
case SyntaxKind.ExclamationEqualsEqualsToken:
|
|
163
|
+
case SyntaxKind.ExclamationEqualsToken:
|
|
164
|
+
return (status) => status !== value;
|
|
165
|
+
case SyntaxKind.LessThanToken:
|
|
166
|
+
return (status) => status < value;
|
|
167
|
+
case SyntaxKind.LessThanEqualsToken:
|
|
168
|
+
return (status) => status <= value;
|
|
169
|
+
case SyntaxKind.GreaterThanToken:
|
|
170
|
+
return (status) => status > value;
|
|
171
|
+
case SyntaxKind.GreaterThanEqualsToken:
|
|
172
|
+
return (status) => status >= value;
|
|
173
|
+
default:
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** `400 <= res.status` reads as `res.status >= 400`. */
|
|
178
|
+
const MIRRORED = {
|
|
179
|
+
[SyntaxKind.LessThanToken]: SyntaxKind.GreaterThanToken,
|
|
180
|
+
[SyntaxKind.LessThanEqualsToken]: SyntaxKind.GreaterThanEqualsToken,
|
|
181
|
+
[SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken,
|
|
182
|
+
[SyntaxKind.GreaterThanEqualsToken]: SyntaxKind.LessThanEqualsToken,
|
|
183
|
+
};
|
|
184
|
+
function isMemberOfResponse(node, name, isResponse) {
|
|
185
|
+
return (Node.isPropertyAccessExpression(node) &&
|
|
186
|
+
node.getName() === name &&
|
|
187
|
+
isResponse(node.getExpression()));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* A statement that never runs on into the statement after it: it returns,
|
|
191
|
+
* throws, breaks or continues, or every path through it does. A loop, a
|
|
192
|
+
* `switch` or a `try` is read as one that can complete, which only ever
|
|
193
|
+
* leaves a read on the path it was on.
|
|
194
|
+
*/
|
|
195
|
+
function cannotComplete(statement) {
|
|
196
|
+
if (Node.isReturnStatement(statement) ||
|
|
197
|
+
Node.isThrowStatement(statement) ||
|
|
198
|
+
Node.isBreakStatement(statement) ||
|
|
199
|
+
Node.isContinueStatement(statement)) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (Node.isBlock(statement)) {
|
|
203
|
+
return statement.getStatements().some((inner) => cannotComplete(inner));
|
|
204
|
+
}
|
|
205
|
+
if (Node.isIfStatement(statement)) {
|
|
206
|
+
const otherwise = statement.getElseStatement();
|
|
207
|
+
return (otherwise !== undefined &&
|
|
208
|
+
cannotComplete(statement.getThenStatement()) &&
|
|
209
|
+
cannotComplete(otherwise));
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
@@ -125,6 +125,7 @@ function handleInit(request) {
|
|
|
125
125
|
projectLoader = new ProjectLoader({
|
|
126
126
|
repoRoot: request.repo_root,
|
|
127
127
|
tsconfigPath: request.tsconfig_path,
|
|
128
|
+
scanRoot: request.scan_root,
|
|
128
129
|
tsconfigSnapshot: request.tsconfig_snapshot,
|
|
129
130
|
pinnedDependencies: request.pinned_dependencies,
|
|
130
131
|
});
|
|
@@ -213,6 +214,7 @@ function handleCaptureV2(request) {
|
|
|
213
214
|
anchors: request.anchors,
|
|
214
215
|
outDir: request.out_dir,
|
|
215
216
|
tsconfigPath: request.tsconfig_path,
|
|
217
|
+
scanRoot: request.scan_root,
|
|
216
218
|
});
|
|
217
219
|
return {
|
|
218
220
|
request_id: request.request_id,
|
|
@@ -19,6 +19,12 @@ export interface ProjectLoaderOptions {
|
|
|
19
19
|
repoRoot: string;
|
|
20
20
|
/** Optional path to tsconfig.json (relative to repo root or absolute) */
|
|
21
21
|
tsconfigPath?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The scanned repo's root, the upper bound of the search for a tsconfig
|
|
24
|
+
* above `repoRoot` when none is named (carrick#1776). Without it only
|
|
25
|
+
* `repoRoot` is searched.
|
|
26
|
+
*/
|
|
27
|
+
scanRoot?: string;
|
|
22
28
|
/** Optional tsconfig snapshot (closed/merged) - preferred over tsconfigPath */
|
|
23
29
|
tsconfigSnapshot?: TsconfigSnapshot;
|
|
24
30
|
/** Optional pinned dependencies for this repo */
|
|
@@ -69,6 +75,7 @@ export declare class ProjectLoader {
|
|
|
69
75
|
private readonly ownerProjects;
|
|
70
76
|
private readonly repoRoot;
|
|
71
77
|
private readonly tsconfigPath;
|
|
78
|
+
private readonly scanRoot;
|
|
72
79
|
private readonly tsconfigSnapshot;
|
|
73
80
|
private readonly pinnedDependencies;
|
|
74
81
|
private initialized;
|
|
@@ -140,9 +147,10 @@ export declare class ProjectLoader {
|
|
|
140
147
|
*/
|
|
141
148
|
getPinnedDependencies(): PinnedDependencySnapshot | undefined;
|
|
142
149
|
/**
|
|
143
|
-
*
|
|
150
|
+
* The tsconfig to build from: the named one when it exists, else the one
|
|
151
|
+
* `findServiceTsconfig` finds for the service, which capture also reads.
|
|
144
152
|
*
|
|
145
|
-
* @returns Absolute path to tsconfig
|
|
153
|
+
* @returns Absolute path to the tsconfig, or undefined if not found
|
|
146
154
|
*/
|
|
147
155
|
private findTsConfig;
|
|
148
156
|
/**
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { Project } from 'ts-morph';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
import * as fs from 'node:fs';
|
|
15
|
-
import { DenoProject, findDenoConfig, serviceConfigPath } from './capture/index.js';
|
|
15
|
+
import { DenoProject, findDenoConfig, findServiceTsconfig, serviceConfigPath } from './capture/index.js';
|
|
16
16
|
import { moduleFormatResolutionHost } from './module-format.js';
|
|
17
17
|
import { ExternalImports, registerExternalImports } from './origin.js';
|
|
18
18
|
/**
|
|
@@ -125,6 +125,7 @@ export class ProjectLoader {
|
|
|
125
125
|
ownerProjects = new Map();
|
|
126
126
|
repoRoot;
|
|
127
127
|
tsconfigPath;
|
|
128
|
+
scanRoot;
|
|
128
129
|
tsconfigSnapshot;
|
|
129
130
|
pinnedDependencies;
|
|
130
131
|
initialized = false;
|
|
@@ -141,6 +142,7 @@ export class ProjectLoader {
|
|
|
141
142
|
? options.tsconfigPath
|
|
142
143
|
: path.resolve(this.repoRoot, options.tsconfigPath);
|
|
143
144
|
}
|
|
145
|
+
this.scanRoot = options.scanRoot;
|
|
144
146
|
// Store snapshot if provided
|
|
145
147
|
this.tsconfigSnapshot = options.tsconfigSnapshot;
|
|
146
148
|
this.pinnedDependencies = options.pinnedDependencies;
|
|
@@ -422,30 +424,19 @@ export class ProjectLoader {
|
|
|
422
424
|
return this.pinnedDependencies;
|
|
423
425
|
}
|
|
424
426
|
/**
|
|
425
|
-
*
|
|
427
|
+
* The tsconfig to build from: the named one when it exists, else the one
|
|
428
|
+
* `findServiceTsconfig` finds for the service, which capture also reads.
|
|
426
429
|
*
|
|
427
|
-
* @returns Absolute path to tsconfig
|
|
430
|
+
* @returns Absolute path to the tsconfig, or undefined if not found
|
|
428
431
|
*/
|
|
429
432
|
findTsConfig() {
|
|
430
|
-
// If a specific path was provided, try to use it
|
|
431
433
|
if (this.tsconfigPath) {
|
|
432
434
|
if (fs.existsSync(this.tsconfigPath)) {
|
|
433
435
|
return this.tsconfigPath;
|
|
434
436
|
}
|
|
435
437
|
this.log(`Specified tsconfig not found: ${this.tsconfigPath}`);
|
|
436
438
|
}
|
|
437
|
-
|
|
438
|
-
const candidates = [
|
|
439
|
-
path.join(this.repoRoot, 'tsconfig.json'),
|
|
440
|
-
path.join(this.repoRoot, 'tsconfig.build.json'),
|
|
441
|
-
path.join(this.repoRoot, 'tsconfig.app.json'),
|
|
442
|
-
];
|
|
443
|
-
for (const candidate of candidates) {
|
|
444
|
-
if (fs.existsSync(candidate)) {
|
|
445
|
-
return candidate;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
return undefined;
|
|
439
|
+
return findServiceTsconfig(this.repoRoot, this.scanRoot);
|
|
449
440
|
}
|
|
450
441
|
/**
|
|
451
442
|
* Add source files from common project locations when no tsconfig is found.
|
|
@@ -735,11 +735,23 @@ function branchSide(condition, whenTrue, isResponse) {
|
|
|
735
735
|
return ok;
|
|
736
736
|
return ok === whenTrue ? 'success' : 'failure';
|
|
737
737
|
}
|
|
738
|
+
/**
|
|
739
|
+
* The success statuses whose response carries no content (RFC 9110: 204 No
|
|
740
|
+
* Content, 205 Reset Content).
|
|
741
|
+
*/
|
|
742
|
+
const NO_CONTENT_STATUSES = new Set([204, 205]);
|
|
738
743
|
/**
|
|
739
744
|
* Whether `condition` being true means the response succeeded: `res.ok`,
|
|
740
745
|
* `res.status === 200`, `res.status !== 200`, `res.status >= 400` and their
|
|
741
746
|
* negations. Any other test of the response is `'unclear'`; a condition that does not
|
|
742
747
|
* test the response is `undefined`.
|
|
748
|
+
*
|
|
749
|
+
* An equality or inequality with a no-content status is `undefined` too
|
|
750
|
+
* (carrick#1813). The retype only runs against a producer that publishes a
|
|
751
|
+
* response body, and that body never arrives with a 204 or 205, so
|
|
752
|
+
* `if (res.status === 204) return null` only takes away a status the body
|
|
753
|
+
* cannot come with. The read after it sits where a read no test decides
|
|
754
|
+
* sits, not on the error path.
|
|
743
755
|
*/
|
|
744
756
|
function okWhenTrue(condition, isResponse) {
|
|
745
757
|
let e = condition;
|
|
@@ -759,13 +771,14 @@ function okWhenTrue(condition, isResponse) {
|
|
|
759
771
|
: undefined;
|
|
760
772
|
if (value !== undefined) {
|
|
761
773
|
const success = value >= 200 && value < 300;
|
|
774
|
+
const noContent = NO_CONTENT_STATUSES.has(value);
|
|
762
775
|
switch (op) {
|
|
763
776
|
case SyntaxKind.EqualsEqualsEqualsToken:
|
|
764
777
|
case SyntaxKind.EqualsEqualsToken:
|
|
765
|
-
return success;
|
|
778
|
+
return noContent ? undefined : success;
|
|
766
779
|
case SyntaxKind.ExclamationEqualsEqualsToken:
|
|
767
780
|
case SyntaxKind.ExclamationEqualsToken:
|
|
768
|
-
return !success;
|
|
781
|
+
return noContent ? undefined : !success;
|
|
769
782
|
case SyntaxKind.GreaterThanEqualsToken:
|
|
770
783
|
if (value >= 300)
|
|
771
784
|
return false;
|
|
@@ -434,6 +434,8 @@ export declare class TypeInferrer {
|
|
|
434
434
|
private bodyReadOnReceiver;
|
|
435
435
|
private collectDefUseNodes;
|
|
436
436
|
private expressionUsesNames;
|
|
437
|
+
/** `expr` is a use of a tracked name, or contains one. */
|
|
438
|
+
private usesTrackedNames;
|
|
437
439
|
private isIdentifierUsage;
|
|
438
440
|
private isInFunctionScope;
|
|
439
441
|
/**
|
|
@@ -21,6 +21,7 @@ import * as path from 'node:path';
|
|
|
21
21
|
import { Node, SyntaxKind, ts, } from 'ts-morph';
|
|
22
22
|
import { validateInferRequestItem } from './validators.js';
|
|
23
23
|
import { externalImportsOf, isExternalOrigin } from './origin.js';
|
|
24
|
+
import { reachedOnlyOnFailure } from './failure-path.js';
|
|
24
25
|
import { addedDiagnostics, applyInsertions, fileDiagnostics, literalInsertions, mapBack, mapForward, normalise, pathOf, } from './unwidened.js';
|
|
25
26
|
import { expandTypeStructural, } from './type-structural-expander.js';
|
|
26
27
|
/**
|
|
@@ -1813,6 +1814,16 @@ export class TypeInferrer {
|
|
|
1813
1814
|
for (const expr of candidates) {
|
|
1814
1815
|
if (expr.getStart() <= startPos)
|
|
1815
1816
|
continue;
|
|
1817
|
+
// carrick#1796: what the source does with the response once it has
|
|
1818
|
+
// FAILED describes the failure, not the payload. `if (res.ok) {
|
|
1819
|
+
// return ... } const errorText = await res.text()` published the error
|
|
1820
|
+
// text as the response contract because the walk keeps the LAST whole
|
|
1821
|
+
// read, and the failure path's read comes after the success path's
|
|
1822
|
+
// return. A read there is neither the payload nor a part of it.
|
|
1823
|
+
if (this.usesTrackedNames(expr, currentNames) &&
|
|
1824
|
+
reachedOnlyOnFailure(expr, func, (node) => this.isIdentifierUsage(node, currentNames))) {
|
|
1825
|
+
continue;
|
|
1826
|
+
}
|
|
1816
1827
|
// carrick#1017: a comparison is `boolean` by construction, so it is
|
|
1817
1828
|
// never the payload a call yields. `const res = await fetch(url); if
|
|
1818
1829
|
// (res.status === 404) return null; return await res.json() as Entry`
|
|
@@ -2361,6 +2372,12 @@ export class TypeInferrer {
|
|
|
2361
2372
|
const identifiers = expr.getDescendantsOfKind(SyntaxKind.Identifier);
|
|
2362
2373
|
return identifiers.some((id) => this.isIdentifierUsage(id, names));
|
|
2363
2374
|
}
|
|
2375
|
+
/** `expr` is a use of a tracked name, or contains one. */
|
|
2376
|
+
usesTrackedNames(expr, names) {
|
|
2377
|
+
return Node.isIdentifier(expr)
|
|
2378
|
+
? this.isIdentifierUsage(expr, names)
|
|
2379
|
+
: this.expressionUsesNames(expr, names);
|
|
2380
|
+
}
|
|
2364
2381
|
isIdentifierUsage(id, names) {
|
|
2365
2382
|
if (!Node.isIdentifier(id)) {
|
|
2366
2383
|
return false;
|
|
@@ -110,6 +110,12 @@ export interface InitRequest extends BaseRequest {
|
|
|
110
110
|
action: 'init';
|
|
111
111
|
repo_root: string;
|
|
112
112
|
tsconfig_path?: string;
|
|
113
|
+
/**
|
|
114
|
+
* The scanned repo's root: with no tsconfig named or in `repo_root`, the
|
|
115
|
+
* nearest `tsconfig.json` above `repo_root` up to this root types the
|
|
116
|
+
* service (carrick#1776). Without it only `repo_root` is searched.
|
|
117
|
+
*/
|
|
118
|
+
scan_root?: string;
|
|
113
119
|
/** Optional tsconfig snapshot (closed/merged) - preferred over tsconfig_path */
|
|
114
120
|
tsconfig_snapshot?: TsconfigSnapshot;
|
|
115
121
|
/** Optional pinned dependencies for this repo */
|
|
@@ -141,6 +147,8 @@ export interface CaptureV2Request extends BaseRequest {
|
|
|
141
147
|
out_dir: string;
|
|
142
148
|
/** Optional explicit tsconfig path (defaults to <repo_root>/tsconfig.json) */
|
|
143
149
|
tsconfig_path?: string;
|
|
150
|
+
/** The scanned repo's root, as on `init` (carrick#1776). */
|
|
151
|
+
scan_root?: string;
|
|
144
152
|
}
|
|
145
153
|
/**
|
|
146
154
|
* Response for the capture_v2 action
|
|
@@ -69,6 +69,7 @@ export declare const InitRequestSchema: z.ZodObject<{
|
|
|
69
69
|
action: z.ZodLiteral<"init">;
|
|
70
70
|
repo_root: z.ZodString;
|
|
71
71
|
tsconfig_path: z.ZodOptional<z.ZodString>;
|
|
72
|
+
scan_root: z.ZodOptional<z.ZodString>;
|
|
72
73
|
tsconfig_snapshot: z.ZodOptional<z.ZodObject<{
|
|
73
74
|
compilerOptions: z.ZodObject<{
|
|
74
75
|
module: z.ZodOptional<z.ZodString>;
|
|
@@ -161,6 +162,7 @@ export declare const InitRequestSchema: z.ZodObject<{
|
|
|
161
162
|
action: "init";
|
|
162
163
|
repo_root: string;
|
|
163
164
|
tsconfig_path?: string | undefined;
|
|
165
|
+
scan_root?: string | undefined;
|
|
164
166
|
tsconfig_snapshot?: {
|
|
165
167
|
compilerOptions: {
|
|
166
168
|
module?: string | undefined;
|
|
@@ -187,6 +189,7 @@ export declare const InitRequestSchema: z.ZodObject<{
|
|
|
187
189
|
action: "init";
|
|
188
190
|
repo_root: string;
|
|
189
191
|
tsconfig_path?: string | undefined;
|
|
192
|
+
scan_root?: string | undefined;
|
|
190
193
|
tsconfig_snapshot?: {
|
|
191
194
|
compilerOptions: {
|
|
192
195
|
module?: string | undefined;
|
|
@@ -344,6 +347,7 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
|
|
|
344
347
|
}>]>, "many">;
|
|
345
348
|
out_dir: z.ZodString;
|
|
346
349
|
tsconfig_path: z.ZodOptional<z.ZodString>;
|
|
350
|
+
scan_root: z.ZodOptional<z.ZodString>;
|
|
347
351
|
}, "strip", z.ZodTypeAny, {
|
|
348
352
|
request_id: string;
|
|
349
353
|
action: "capture_v2";
|
|
@@ -381,6 +385,7 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
|
|
|
381
385
|
})[];
|
|
382
386
|
out_dir: string;
|
|
383
387
|
tsconfig_path?: string | undefined;
|
|
388
|
+
scan_root?: string | undefined;
|
|
384
389
|
}, {
|
|
385
390
|
request_id: string;
|
|
386
391
|
action: "capture_v2";
|
|
@@ -418,6 +423,7 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
|
|
|
418
423
|
})[];
|
|
419
424
|
out_dir: string;
|
|
420
425
|
tsconfig_path?: string | undefined;
|
|
426
|
+
scan_root?: string | undefined;
|
|
421
427
|
}>;
|
|
422
428
|
export declare const CheckV2RequestSchema: z.ZodObject<{
|
|
423
429
|
request_id: z.ZodString;
|
|
@@ -1810,6 +1816,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
|
|
|
1810
1816
|
action: z.ZodLiteral<"init">;
|
|
1811
1817
|
repo_root: z.ZodString;
|
|
1812
1818
|
tsconfig_path: z.ZodOptional<z.ZodString>;
|
|
1819
|
+
scan_root: z.ZodOptional<z.ZodString>;
|
|
1813
1820
|
tsconfig_snapshot: z.ZodOptional<z.ZodObject<{
|
|
1814
1821
|
compilerOptions: z.ZodObject<{
|
|
1815
1822
|
module: z.ZodOptional<z.ZodString>;
|
|
@@ -1902,6 +1909,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
|
|
|
1902
1909
|
action: "init";
|
|
1903
1910
|
repo_root: string;
|
|
1904
1911
|
tsconfig_path?: string | undefined;
|
|
1912
|
+
scan_root?: string | undefined;
|
|
1905
1913
|
tsconfig_snapshot?: {
|
|
1906
1914
|
compilerOptions: {
|
|
1907
1915
|
module?: string | undefined;
|
|
@@ -1928,6 +1936,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
|
|
|
1928
1936
|
action: "init";
|
|
1929
1937
|
repo_root: string;
|
|
1930
1938
|
tsconfig_path?: string | undefined;
|
|
1939
|
+
scan_root?: string | undefined;
|
|
1931
1940
|
tsconfig_snapshot?: {
|
|
1932
1941
|
compilerOptions: {
|
|
1933
1942
|
module?: string | undefined;
|
|
@@ -2083,6 +2092,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
|
|
|
2083
2092
|
}>]>, "many">;
|
|
2084
2093
|
out_dir: z.ZodString;
|
|
2085
2094
|
tsconfig_path: z.ZodOptional<z.ZodString>;
|
|
2095
|
+
scan_root: z.ZodOptional<z.ZodString>;
|
|
2086
2096
|
}, "strip", z.ZodTypeAny, {
|
|
2087
2097
|
request_id: string;
|
|
2088
2098
|
action: "capture_v2";
|
|
@@ -2120,6 +2130,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
|
|
|
2120
2130
|
})[];
|
|
2121
2131
|
out_dir: string;
|
|
2122
2132
|
tsconfig_path?: string | undefined;
|
|
2133
|
+
scan_root?: string | undefined;
|
|
2123
2134
|
}, {
|
|
2124
2135
|
request_id: string;
|
|
2125
2136
|
action: "capture_v2";
|
|
@@ -2157,6 +2168,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
|
|
|
2157
2168
|
})[];
|
|
2158
2169
|
out_dir: string;
|
|
2159
2170
|
tsconfig_path?: string | undefined;
|
|
2171
|
+
scan_root?: string | undefined;
|
|
2160
2172
|
}>, z.ZodObject<{
|
|
2161
2173
|
request_id: z.ZodString;
|
|
2162
2174
|
} & {
|
|
@@ -136,6 +136,7 @@ export const InitRequestSchema = BaseRequestSchema.extend({
|
|
|
136
136
|
action: z.literal('init'),
|
|
137
137
|
repo_root: z.string().min(1, 'Repo root cannot be empty'),
|
|
138
138
|
tsconfig_path: z.string().optional(),
|
|
139
|
+
scan_root: z.string().min(1).optional(),
|
|
139
140
|
tsconfig_snapshot: TsconfigSnapshotSchema.optional(),
|
|
140
141
|
pinned_dependencies: PinnedDependencySnapshotSchema.optional(),
|
|
141
142
|
});
|
|
@@ -192,6 +193,7 @@ export const CaptureV2RequestSchema = BaseRequestSchema.extend({
|
|
|
192
193
|
anchors: z.array(CaptureAnchorRequestSchema).min(1, 'At least one anchor is required'),
|
|
193
194
|
out_dir: z.string().min(1, 'Output dir cannot be empty'),
|
|
194
195
|
tsconfig_path: z.string().optional(),
|
|
196
|
+
scan_root: z.string().min(1).optional(),
|
|
195
197
|
});
|
|
196
198
|
const CheckStubInputSchema = z.object({
|
|
197
199
|
service_name: z.string().min(1),
|