carrick 0.3.109 → 0.3.110
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/bundler.d.ts +10 -0
- package/sidecar/dist/src/bundler.js +42 -0
- package/sidecar/dist/src/capture/anchors.js +32 -0
- package/sidecar/dist/src/capture/api.d.ts +5 -0
- package/sidecar/dist/src/capture/check-workspace.d.ts +10 -0
- package/sidecar/dist/src/capture/check-workspace.js +68 -6
- package/sidecar/dist/src/capture/index.d.ts +1 -0
- package/sidecar/dist/src/capture/index.js +18 -3
- package/sidecar/dist/src/capture/installed-package-directory.d.ts +22 -0
- package/sidecar/dist/src/capture/installed-package-directory.js +30 -0
- package/sidecar/dist/src/capture/resolution-edges.d.ts +36 -0
- package/sidecar/dist/src/capture/resolution-edges.js +150 -0
- package/sidecar/dist/src/capture/status-table.d.ts +62 -0
- package/sidecar/dist/src/capture/status-table.js +104 -0
- package/sidecar/dist/src/failure-path.d.ts +42 -2
- package/sidecar/dist/src/failure-path.js +38 -13
- package/sidecar/dist/src/library-claims.js +2 -0
- package/sidecar/dist/src/line-index.d.ts +9 -0
- package/sidecar/dist/src/line-index.js +24 -0
- package/sidecar/dist/src/response-modes.d.ts +77 -0
- package/sidecar/dist/src/response-modes.js +459 -0
- package/sidecar/dist/src/type-inferrer.d.ts +42 -0
- package/sidecar/dist/src/type-inferrer.js +237 -76
- package/sidecar/dist/src/types.d.ts +16 -0
- package/sidecar/dist/src/validators.d.ts +39 -16
- package/sidecar/dist/src/validators.js +4 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response tables keyed by HTTP status code (carrick#1841).
|
|
3
|
+
*
|
|
4
|
+
* An object type whose every key is a status code (`{ 200: Item; 404: Problem
|
|
5
|
+
* }`, or a range key such as `2XX`) is not a body: it is a table of the bodies
|
|
6
|
+
* each status carries. What a caller receives on success is the union of its
|
|
7
|
+
* 2xx values. The producer side reads the same rule off a schema object
|
|
8
|
+
* literal (`successStatusEntry` in the inferrer); this module reads it off a
|
|
9
|
+
* TYPE, through the compiler's own API, so the inferrer, the v1 bundle and
|
|
10
|
+
* the capture all decide a table the same way.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here names a library or a generator. The shape is the HTTP status
|
|
13
|
+
* vocabulary, which is the language of the wire, not of any one client.
|
|
14
|
+
*/
|
|
15
|
+
import ts from 'typescript';
|
|
16
|
+
/** `value` when it is an integer in the HTTP status range, else `undefined`. */
|
|
17
|
+
export declare function httpStatus(value: unknown): number | undefined;
|
|
18
|
+
/** One verdict for a set of status codes, or `mixed` when they disagree. */
|
|
19
|
+
export declare function classifyStatusCodes(codes: number[]): 'success' | 'error' | 'redirect' | 'mixed';
|
|
20
|
+
/**
|
|
21
|
+
* The status code a response-table KEY names: `200`, or a range key (`2XX`,
|
|
22
|
+
* `2xx`) read as the bottom of its range. Quotes around the key are ignored.
|
|
23
|
+
* `undefined` for any other key.
|
|
24
|
+
*/
|
|
25
|
+
export declare function statusKeyCode(name: string): number | undefined;
|
|
26
|
+
/** One row of a response table: the key as written and the status it names. */
|
|
27
|
+
export interface StatusTableEntry {
|
|
28
|
+
symbol: ts.Symbol;
|
|
29
|
+
/** The key as the type declares it (`200`, `2XX`). */
|
|
30
|
+
key: string;
|
|
31
|
+
code: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The rows of `type` when it is a response table: a plain object type with at
|
|
35
|
+
* least one property, every key a status code, no index signature and no call
|
|
36
|
+
* or construct signature. `undefined` for anything else, so a payload that
|
|
37
|
+
* happens to hold one numeric key among others is never read as a table.
|
|
38
|
+
*/
|
|
39
|
+
export declare function statusTableOf(checker: ts.TypeChecker, type: ts.Type): StatusTableEntry[] | undefined;
|
|
40
|
+
/** The rows of a table that answer a success: 2xx, in the order declared. */
|
|
41
|
+
export declare function successEntries(entries: StatusTableEntry[]): StatusTableEntry[];
|
|
42
|
+
/**
|
|
43
|
+
* Whether a body type states nothing: `void`, `undefined`, `never`, or a union
|
|
44
|
+
* made only of them. A `204: void` row is such a body.
|
|
45
|
+
*/
|
|
46
|
+
export declare function statesNoBody(type: ts.Type): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* What a response table's success rows carry, read for a consumer:
|
|
49
|
+
* - `body`: the 2xx rows' value types, at least one of which states a body;
|
|
50
|
+
* - `no_body`: the table has no 2xx row, or every 2xx row states nothing.
|
|
51
|
+
* `undefined` when `type` is not a response table.
|
|
52
|
+
*/
|
|
53
|
+
export declare function statusTableBody(checker: ts.TypeChecker, type: ts.Type, at: ts.Node): {
|
|
54
|
+
kind: 'body';
|
|
55
|
+
entries: StatusTableEntry[];
|
|
56
|
+
types: ts.Type[];
|
|
57
|
+
} | {
|
|
58
|
+
kind: 'no_body';
|
|
59
|
+
entries: StatusTableEntry[];
|
|
60
|
+
} | undefined;
|
|
61
|
+
/** The members of a union, or the type itself: what a set comparison reads. */
|
|
62
|
+
export declare function constituents(type: ts.Type): ts.Type[];
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response tables keyed by HTTP status code (carrick#1841).
|
|
3
|
+
*
|
|
4
|
+
* An object type whose every key is a status code (`{ 200: Item; 404: Problem
|
|
5
|
+
* }`, or a range key such as `2XX`) is not a body: it is a table of the bodies
|
|
6
|
+
* each status carries. What a caller receives on success is the union of its
|
|
7
|
+
* 2xx values. The producer side reads the same rule off a schema object
|
|
8
|
+
* literal (`successStatusEntry` in the inferrer); this module reads it off a
|
|
9
|
+
* TYPE, through the compiler's own API, so the inferrer, the v1 bundle and
|
|
10
|
+
* the capture all decide a table the same way.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here names a library or a generator. The shape is the HTTP status
|
|
13
|
+
* vocabulary, which is the language of the wire, not of any one client.
|
|
14
|
+
*/
|
|
15
|
+
import ts from 'typescript';
|
|
16
|
+
/** `value` when it is an integer in the HTTP status range, else `undefined`. */
|
|
17
|
+
export function httpStatus(value) {
|
|
18
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 100 && value <= 599
|
|
19
|
+
? value
|
|
20
|
+
: undefined;
|
|
21
|
+
}
|
|
22
|
+
/** One verdict for a set of status codes, or `mixed` when they disagree. */
|
|
23
|
+
export function classifyStatusCodes(codes) {
|
|
24
|
+
if (codes.every((code) => code >= 400))
|
|
25
|
+
return 'error';
|
|
26
|
+
if (codes.every((code) => code >= 300 && code < 400))
|
|
27
|
+
return 'redirect';
|
|
28
|
+
if (codes.every((code) => code < 300))
|
|
29
|
+
return 'success';
|
|
30
|
+
return 'mixed';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The status code a response-table KEY names: `200`, or a range key (`2XX`,
|
|
34
|
+
* `2xx`) read as the bottom of its range. Quotes around the key are ignored.
|
|
35
|
+
* `undefined` for any other key.
|
|
36
|
+
*/
|
|
37
|
+
export function statusKeyCode(name) {
|
|
38
|
+
const key = name.replace(/['"`]/g, '').trim();
|
|
39
|
+
if (/^\d{3}$/.test(key))
|
|
40
|
+
return httpStatus(Number(key));
|
|
41
|
+
if (/^[1-5]xx$/i.test(key))
|
|
42
|
+
return Number(key[0]) * 100;
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The rows of `type` when it is a response table: a plain object type with at
|
|
47
|
+
* least one property, every key a status code, no index signature and no call
|
|
48
|
+
* or construct signature. `undefined` for anything else, so a payload that
|
|
49
|
+
* happens to hold one numeric key among others is never read as a table.
|
|
50
|
+
*/
|
|
51
|
+
export function statusTableOf(checker, type) {
|
|
52
|
+
if (!(type.flags & ts.TypeFlags.Object))
|
|
53
|
+
return undefined;
|
|
54
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
if (checker.getIndexInfosOfType(type).length > 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
const properties = checker.getPropertiesOfType(type);
|
|
60
|
+
if (properties.length === 0)
|
|
61
|
+
return undefined;
|
|
62
|
+
const entries = [];
|
|
63
|
+
for (const symbol of properties) {
|
|
64
|
+
const key = symbol.getName();
|
|
65
|
+
const code = statusKeyCode(key);
|
|
66
|
+
if (code === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
entries.push({ symbol, key, code });
|
|
69
|
+
}
|
|
70
|
+
return entries;
|
|
71
|
+
}
|
|
72
|
+
/** The rows of a table that answer a success: 2xx, in the order declared. */
|
|
73
|
+
export function successEntries(entries) {
|
|
74
|
+
return entries.filter((entry) => entry.code >= 200 && entry.code <= 299);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether a body type states nothing: `void`, `undefined`, `never`, or a union
|
|
78
|
+
* made only of them. A `204: void` row is such a body.
|
|
79
|
+
*/
|
|
80
|
+
export function statesNoBody(type) {
|
|
81
|
+
const members = type.isUnion() ? type.types : [type];
|
|
82
|
+
return members.every((member) => (member.flags & (ts.TypeFlags.Void | ts.TypeFlags.Undefined | ts.TypeFlags.Never)) !== 0);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* What a response table's success rows carry, read for a consumer:
|
|
86
|
+
* - `body`: the 2xx rows' value types, at least one of which states a body;
|
|
87
|
+
* - `no_body`: the table has no 2xx row, or every 2xx row states nothing.
|
|
88
|
+
* `undefined` when `type` is not a response table.
|
|
89
|
+
*/
|
|
90
|
+
export function statusTableBody(checker, type, at) {
|
|
91
|
+
const entries = statusTableOf(checker, type);
|
|
92
|
+
if (!entries)
|
|
93
|
+
return undefined;
|
|
94
|
+
const success = successEntries(entries);
|
|
95
|
+
const types = success.map((entry) => checker.getTypeOfSymbolAtLocation(entry.symbol, at));
|
|
96
|
+
if (success.length === 0 || types.every((body) => statesNoBody(body))) {
|
|
97
|
+
return { kind: 'no_body', entries };
|
|
98
|
+
}
|
|
99
|
+
return { kind: 'body', entries: success, types };
|
|
100
|
+
}
|
|
101
|
+
/** The members of a union, or the type itself: what a set comparison reads. */
|
|
102
|
+
export function constituents(type) {
|
|
103
|
+
return type.isUnion() ? type.types : [type];
|
|
104
|
+
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* the other way round: after `if (res.status === 204) return null`, 200 still
|
|
28
28
|
* gets through, and the json read that follows is the payload.
|
|
29
29
|
*/
|
|
30
|
-
import { Node } from 'ts-morph';
|
|
30
|
+
import { Node, type CaseOrDefaultClause, type SwitchStatement } from 'ts-morph';
|
|
31
31
|
/** A set of HTTP statuses, as the predicate that admits them. */
|
|
32
32
|
export type Statuses = (status: number) => boolean;
|
|
33
33
|
/** The statuses `ok` is true for. */
|
|
@@ -60,8 +60,48 @@ export declare function reachedOnlyOnFailure(node: Node, boundary: Node, isRespo
|
|
|
60
60
|
* an `if` or a conditional expression it sits in, or an earlier `if` in an
|
|
61
61
|
* enclosing block one of whose branches cannot complete, which leaves the
|
|
62
62
|
* rest of the block to the other side. A test that does not read the
|
|
63
|
-
* response's `ok` or `status` is not listed.
|
|
63
|
+
* response's `ok` or `status` is not listed. A `switch` is not read here:
|
|
64
|
+
* `retype.ts` finds one by its own climb.
|
|
64
65
|
*/
|
|
65
66
|
export declare function testsOnPath(node: Node, boundary: Node | undefined, isResponse: (node: Node) => boolean): SideTaken[];
|
|
67
|
+
/**
|
|
68
|
+
* One test on the path to a node, as the side the node is on. `test` is the
|
|
69
|
+
* statement or expression that tests, so two nodes under the same test share
|
|
70
|
+
* it.
|
|
71
|
+
*
|
|
72
|
+
* - `branch`: an `if` or a conditional expression, and whether the node is
|
|
73
|
+
* on its true side: the branch the node sits in or, for an earlier `if` in
|
|
74
|
+
* an enclosing block one of whose branches cannot complete, the side that
|
|
75
|
+
* did not leave.
|
|
76
|
+
* - `clause`: the node sits in this clause of a `switch`.
|
|
77
|
+
* - `after-switch`: the node follows a `switch` in an enclosing block.
|
|
78
|
+
*/
|
|
79
|
+
export type PathStep = {
|
|
80
|
+
kind: 'branch';
|
|
81
|
+
test: Node;
|
|
82
|
+
condition: Node;
|
|
83
|
+
whenTrue: boolean;
|
|
84
|
+
} | {
|
|
85
|
+
kind: 'clause';
|
|
86
|
+
test: SwitchStatement;
|
|
87
|
+
clause: CaseOrDefaultClause;
|
|
88
|
+
} | {
|
|
89
|
+
kind: 'after-switch';
|
|
90
|
+
test: SwitchStatement;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Every test on the path from `node` up to `boundary` (the whole file when it
|
|
94
|
+
* is `undefined`), whatever it reads, innermost first. `testsOnPath` reads
|
|
95
|
+
* the branches as statuses; `response-modes.ts` reads branches and switches
|
|
96
|
+
* as values of a request field (carrick#2054).
|
|
97
|
+
*/
|
|
98
|
+
export declare function stepsOnPath(node: Node, boundary: Node | undefined): PathStep[];
|
|
66
99
|
/** `node` reads the response's `ok` or `status`, itself or anywhere inside it. */
|
|
67
100
|
export declare function readsResponseStatus(node: Node, isResponse: (node: Node) => boolean): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* A statement that never runs on into the statement after it: it returns,
|
|
103
|
+
* throws, breaks or continues, or every path through it does. A loop, a
|
|
104
|
+
* `switch` or a `try` is read as one that can complete, which only ever
|
|
105
|
+
* leaves a read on the path it was on.
|
|
106
|
+
*/
|
|
107
|
+
export declare function cannotComplete(statement: Node): boolean;
|
|
@@ -64,30 +64,51 @@ export function reachedOnlyOnFailure(node, boundary, isResponse) {
|
|
|
64
64
|
* an `if` or a conditional expression it sits in, or an earlier `if` in an
|
|
65
65
|
* enclosing block one of whose branches cannot complete, which leaves the
|
|
66
66
|
* rest of the block to the other side. A test that does not read the
|
|
67
|
-
* response's `ok` or `status` is not listed.
|
|
67
|
+
* response's `ok` or `status` is not listed. A `switch` is not read here:
|
|
68
|
+
* `retype.ts` finds one by its own climb.
|
|
68
69
|
*/
|
|
69
70
|
export function testsOnPath(node, boundary, isResponse) {
|
|
70
71
|
const sides = [];
|
|
71
|
-
const
|
|
72
|
+
for (const step of stepsOnPath(node, boundary)) {
|
|
73
|
+
if (step.kind !== 'branch')
|
|
74
|
+
continue;
|
|
75
|
+
const test = readTest(step.condition, isResponse);
|
|
72
76
|
if (test === UNRELATED)
|
|
73
|
-
|
|
74
|
-
sides.push({ admits: whenTrue ? test.whenTrue : test.whenFalse, inexact: test.inexact });
|
|
75
|
-
}
|
|
77
|
+
continue;
|
|
78
|
+
sides.push({ admits: step.whenTrue ? test.whenTrue : test.whenFalse, inexact: test.inexact });
|
|
79
|
+
}
|
|
80
|
+
return sides;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Every test on the path from `node` up to `boundary` (the whole file when it
|
|
84
|
+
* is `undefined`), whatever it reads, innermost first. `testsOnPath` reads
|
|
85
|
+
* the branches as statuses; `response-modes.ts` reads branches and switches
|
|
86
|
+
* as values of a request field (carrick#2054).
|
|
87
|
+
*/
|
|
88
|
+
export function stepsOnPath(node, boundary) {
|
|
89
|
+
const steps = [];
|
|
90
|
+
const branch = (test, condition, whenTrue) => steps.push({ kind: 'branch', test, condition, whenTrue });
|
|
76
91
|
for (let child = node, parent = node.getParent(); parent && parent !== boundary; child = parent, parent = parent.getParent()) {
|
|
77
92
|
if (Node.isIfStatement(parent)) {
|
|
78
93
|
if (child === parent.getThenStatement()) {
|
|
79
|
-
|
|
94
|
+
branch(parent, parent.getExpression(), true);
|
|
80
95
|
}
|
|
81
96
|
else if (child === parent.getElseStatement()) {
|
|
82
|
-
|
|
97
|
+
branch(parent, parent.getExpression(), false);
|
|
83
98
|
}
|
|
84
99
|
}
|
|
85
100
|
else if (Node.isConditionalExpression(parent)) {
|
|
86
101
|
if (child === parent.getWhenTrue()) {
|
|
87
|
-
|
|
102
|
+
branch(parent, parent.getCondition(), true);
|
|
88
103
|
}
|
|
89
104
|
else if (child === parent.getWhenFalse()) {
|
|
90
|
-
|
|
105
|
+
branch(parent, parent.getCondition(), false);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (Node.isCaseClause(parent) || Node.isDefaultClause(parent)) {
|
|
109
|
+
const statement = parent.getParent()?.getParent();
|
|
110
|
+
if (statement && Node.isSwitchStatement(statement)) {
|
|
111
|
+
steps.push({ kind: 'clause', test: statement, clause: parent });
|
|
91
112
|
}
|
|
92
113
|
}
|
|
93
114
|
if (Node.isBlock(parent) ||
|
|
@@ -97,21 +118,25 @@ export function testsOnPath(node, boundary, isResponse) {
|
|
|
97
118
|
for (const statement of parent.getStatements()) {
|
|
98
119
|
if (statement === child)
|
|
99
120
|
break;
|
|
121
|
+
if (Node.isSwitchStatement(statement)) {
|
|
122
|
+
steps.push({ kind: 'after-switch', test: statement });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
100
125
|
if (!Node.isIfStatement(statement))
|
|
101
126
|
continue;
|
|
102
127
|
const otherwise = statement.getElseStatement();
|
|
103
128
|
const thenLeaves = cannotComplete(statement.getThenStatement());
|
|
104
129
|
const elseLeaves = otherwise !== undefined && cannotComplete(otherwise);
|
|
105
130
|
if (thenLeaves && !elseLeaves) {
|
|
106
|
-
|
|
131
|
+
branch(statement, statement.getExpression(), false);
|
|
107
132
|
}
|
|
108
133
|
else if (elseLeaves && !thenLeaves) {
|
|
109
|
-
|
|
134
|
+
branch(statement, statement.getExpression(), true);
|
|
110
135
|
}
|
|
111
136
|
}
|
|
112
137
|
}
|
|
113
138
|
}
|
|
114
|
-
return
|
|
139
|
+
return steps;
|
|
115
140
|
}
|
|
116
141
|
/** The statuses `condition` lets through when it is true and when it is false. */
|
|
117
142
|
function readTest(condition, isResponse) {
|
|
@@ -216,7 +241,7 @@ export function readsResponseStatus(node, isResponse) {
|
|
|
216
241
|
* `switch` or a `try` is read as one that can complete, which only ever
|
|
217
242
|
* leaves a read on the path it was on.
|
|
218
243
|
*/
|
|
219
|
-
function cannotComplete(statement) {
|
|
244
|
+
export function cannotComplete(statement) {
|
|
220
245
|
if (Node.isReturnStatement(statement) ||
|
|
221
246
|
Node.isThrowStatement(statement) ||
|
|
222
247
|
Node.isBreakStatement(statement) ||
|
|
@@ -2584,6 +2584,8 @@ class DeclarationReader {
|
|
|
2584
2584
|
* repository checked out under a `node_modules` ancestor, is not installed.
|
|
2585
2585
|
* An install hoisted above the service root (`../../node_modules/pkg`) and
|
|
2586
2586
|
* a pnpm store (`node_modules/.pnpm/pkg@1/node_modules/pkg`) are.
|
|
2587
|
+
* Mirrored by `installedPackageDirectory` in capture/, which the capture
|
|
2588
|
+
* seam keeps from being shared.
|
|
2587
2589
|
*/
|
|
2588
2590
|
installedPackage(fileName) {
|
|
2589
2591
|
const segments = path.relative(this.root, this.realpath(fileName)).split(path.sep);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Node } from 'ts-morph';
|
|
1
2
|
/**
|
|
2
3
|
* The 1-based line of a position in a text, counted by line feeds alone: one
|
|
3
4
|
* more than the `\n`s before the position. That is the count ts-morph's
|
|
@@ -7,3 +8,11 @@
|
|
|
7
8
|
* The text is read once; each lookup after that is a binary search.
|
|
8
9
|
*/
|
|
9
10
|
export declare function lineIndex(text: string): (pos: number) => number;
|
|
11
|
+
/**
|
|
12
|
+
* `node.getStartLineNumber()` from a per-file index. ts-morph counts the
|
|
13
|
+
* file's line feeds from position 0 on every call, so a walk that asks of
|
|
14
|
+
* every node of a file pays the file's size once per node (carrick#1935).
|
|
15
|
+
*/
|
|
16
|
+
export declare function startLineOf(node: Node): number;
|
|
17
|
+
/** `node.getEndLineNumber()` from the same per-file index. */
|
|
18
|
+
export declare function endLineOf(node: Node): number;
|
|
@@ -24,3 +24,27 @@ export function lineIndex(text) {
|
|
|
24
24
|
return lo + 1;
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
const fileLineIndexes = new WeakMap();
|
|
28
|
+
function lineAtIn(node, pos) {
|
|
29
|
+
// Keyed on the compiler node: a manipulation (`replaceWithText`, a retype)
|
|
30
|
+
// gives the file a new one, so the index is built again from the new text.
|
|
31
|
+
const key = node.getSourceFile().compilerNode;
|
|
32
|
+
let lineAt = fileLineIndexes.get(key);
|
|
33
|
+
if (!lineAt) {
|
|
34
|
+
lineAt = lineIndex(key.text);
|
|
35
|
+
fileLineIndexes.set(key, lineAt);
|
|
36
|
+
}
|
|
37
|
+
return lineAt(pos);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* `node.getStartLineNumber()` from a per-file index. ts-morph counts the
|
|
41
|
+
* file's line feeds from position 0 on every call, so a walk that asks of
|
|
42
|
+
* every node of a file pays the file's size once per node (carrick#1935).
|
|
43
|
+
*/
|
|
44
|
+
export function startLineOf(node) {
|
|
45
|
+
return lineAtIn(node, node.getStart());
|
|
46
|
+
}
|
|
47
|
+
/** `node.getEndLineNumber()` from the same per-file index. */
|
|
48
|
+
export function endLineOf(node) {
|
|
49
|
+
return lineAtIn(node, node.getEnd());
|
|
50
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which incoming-message field picks each member of a response union (carrick#2054).
|
|
3
|
+
*
|
|
4
|
+
* A handler that sends `{ x }` when `?mode=a` and `{ y }` otherwise publishes
|
|
5
|
+
* `{ x } | { y }`. A call that states `mode=a` receives only `{ x }`, and a
|
|
6
|
+
* call that states nothing may receive either. The union alone cannot say
|
|
7
|
+
* which, so this reads it from the handler: for each success body the union
|
|
8
|
+
* joined, the tests on its path that compare a value read from the incoming message
|
|
9
|
+
* with a string literal.
|
|
10
|
+
*
|
|
11
|
+
* The reading, in order:
|
|
12
|
+
*
|
|
13
|
+
* - A test counts only when it separates two of the joined bodies: one on
|
|
14
|
+
* each side of it, or, for an earlier `if` that cannot complete, one in
|
|
15
|
+
* its branch and one after it. A validation guard whose branch only sends
|
|
16
|
+
* an error separates nothing the join kept, and is ignored.
|
|
17
|
+
* - A counted test is a message read when a value it tests is read from a
|
|
18
|
+
* parameter of a function enclosing the send (`messageRead`). A test that
|
|
19
|
+
* reads nothing from the incoming message (server state, a local count) is ignored,
|
|
20
|
+
* so a body under it is under no test of the field and stays in every case.
|
|
21
|
+
* - `===`, `==`, `!==` and `!=` against a string literal, with `!` flipping
|
|
22
|
+
* the test, and `switch` labels, are read as values. Any other test of a
|
|
23
|
+
* message read leaves its field read with its values unread.
|
|
24
|
+
* - `reads` lists every message read among the counted tests. `cases` is
|
|
25
|
+
* read only for one placed field whose tests were all read: one case per
|
|
26
|
+
* literal compared, plus "any other value" (`value: null`) when a body
|
|
27
|
+
* admits one, each the union of the bodies it admits.
|
|
28
|
+
* - When every case is the same union, the route has no modes.
|
|
29
|
+
*
|
|
30
|
+
* The published union is not changed by any of this.
|
|
31
|
+
*/
|
|
32
|
+
import { Node, type Symbol as TsSymbol } from 'ts-morph';
|
|
33
|
+
/**
|
|
34
|
+
* Where in the incoming message a field is read from. Only the HTTP sources
|
|
35
|
+
* are read so far; another protocol (a socket message, a pub/sub payload)
|
|
36
|
+
* adds its own source here.
|
|
37
|
+
*/
|
|
38
|
+
export type MessageSource = 'query' | 'body' | 'unplaced';
|
|
39
|
+
/** One incoming-message field a response union depends on. */
|
|
40
|
+
export interface ResponseModeRead {
|
|
41
|
+
/**
|
|
42
|
+
* `query`: read with `.get(...)` on the default lib's `URLSearchParams`.
|
|
43
|
+
* `body`: a top-level property of the awaited result of a zero-argument
|
|
44
|
+
* `json()` the default lib or an installed library declares. `unplaced`:
|
|
45
|
+
* anything else (a header, a path parameter, the method, a framework's own
|
|
46
|
+
* accessor, a `json()` the repo declares).
|
|
47
|
+
*/
|
|
48
|
+
location: MessageSource;
|
|
49
|
+
field: string;
|
|
50
|
+
}
|
|
51
|
+
/** The union a stated value of the field receives. */
|
|
52
|
+
export interface ResponseModeCase {
|
|
53
|
+
/** The value; `null` for any value no other case names. */
|
|
54
|
+
value: string | null;
|
|
55
|
+
type_string: string;
|
|
56
|
+
}
|
|
57
|
+
export interface ResponseModes {
|
|
58
|
+
/** Every message field the union depends on, sorted by location, then field. */
|
|
59
|
+
reads: ResponseModeRead[];
|
|
60
|
+
/**
|
|
61
|
+
* Present only when `reads` is one placed field and every test of it was
|
|
62
|
+
* read. Sorted by value, the `null` case last.
|
|
63
|
+
*/
|
|
64
|
+
cases?: ResponseModeCase[];
|
|
65
|
+
}
|
|
66
|
+
/** One success body the union joined, in source order. */
|
|
67
|
+
export interface JoinedBody {
|
|
68
|
+
node: Node;
|
|
69
|
+
type_string: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The modes of the union joined from `bodies`, or `undefined` when no message
|
|
73
|
+
* read separates them. `join` builds a union text from member texts with the
|
|
74
|
+
* rules the published union is built with. `isLibOrExternal` says whether a
|
|
75
|
+
* symbol is declared by a TypeScript lib or an installed library.
|
|
76
|
+
*/
|
|
77
|
+
export declare function readResponseModes(bodies: JoinedBody[], join: (texts: string[]) => string, isLibOrExternal: (symbol: TsSymbol | undefined) => boolean): ResponseModes | undefined;
|