@typespec/http-server-js 0.58.0-alpha.10-dev.3
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/CHANGELOG.md +69 -0
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/build-helpers.ts +170 -0
- package/dist/generated-defs/helpers/header.d.ts +4 -0
- package/dist/generated-defs/helpers/header.d.ts.map +1 -0
- package/dist/generated-defs/helpers/header.js +76 -0
- package/dist/generated-defs/helpers/header.js.map +1 -0
- package/dist/generated-defs/helpers/http.d.ts +4 -0
- package/dist/generated-defs/helpers/http.d.ts.map +1 -0
- package/dist/generated-defs/helpers/http.js +134 -0
- package/dist/generated-defs/helpers/http.js.map +1 -0
- package/dist/generated-defs/helpers/index.d.ts +4 -0
- package/dist/generated-defs/helpers/index.d.ts.map +1 -0
- package/dist/generated-defs/helpers/index.js +21 -0
- package/dist/generated-defs/helpers/index.js.map +1 -0
- package/dist/generated-defs/helpers/multipart.d.ts +4 -0
- package/dist/generated-defs/helpers/multipart.d.ts.map +1 -0
- package/dist/generated-defs/helpers/multipart.js +249 -0
- package/dist/generated-defs/helpers/multipart.js.map +1 -0
- package/dist/generated-defs/helpers/router.d.ts +4 -0
- package/dist/generated-defs/helpers/router.d.ts.map +1 -0
- package/dist/generated-defs/helpers/router.js +259 -0
- package/dist/generated-defs/helpers/router.js.map +1 -0
- package/dist/src/common/declaration.d.ts +13 -0
- package/dist/src/common/declaration.d.ts.map +1 -0
- package/dist/src/common/declaration.js +45 -0
- package/dist/src/common/declaration.js.map +1 -0
- package/dist/src/common/documentation.d.ts +12 -0
- package/dist/src/common/documentation.d.ts.map +1 -0
- package/dist/src/common/documentation.js +21 -0
- package/dist/src/common/documentation.js.map +1 -0
- package/dist/src/common/enum.d.ts +10 -0
- package/dist/src/common/enum.d.ts.map +1 -0
- package/dist/src/common/enum.js +21 -0
- package/dist/src/common/enum.js.map +1 -0
- package/dist/src/common/interface.d.ts +50 -0
- package/dist/src/common/interface.d.ts.map +1 -0
- package/dist/src/common/interface.js +194 -0
- package/dist/src/common/interface.js.map +1 -0
- package/dist/src/common/model.d.ts +26 -0
- package/dist/src/common/model.d.ts.map +1 -0
- package/dist/src/common/model.js +115 -0
- package/dist/src/common/model.js.map +1 -0
- package/dist/src/common/namespace.d.ts +38 -0
- package/dist/src/common/namespace.d.ts.map +1 -0
- package/dist/src/common/namespace.js +184 -0
- package/dist/src/common/namespace.js.map +1 -0
- package/dist/src/common/reference.d.ts +46 -0
- package/dist/src/common/reference.d.ts.map +1 -0
- package/dist/src/common/reference.js +243 -0
- package/dist/src/common/reference.js.map +1 -0
- package/dist/src/common/scalar.d.ts +50 -0
- package/dist/src/common/scalar.d.ts.map +1 -0
- package/dist/src/common/scalar.js +144 -0
- package/dist/src/common/scalar.js.map +1 -0
- package/dist/src/common/serialization/index.d.ts +11 -0
- package/dist/src/common/serialization/index.d.ts.map +1 -0
- package/dist/src/common/serialization/index.js +72 -0
- package/dist/src/common/serialization/index.js.map +1 -0
- package/dist/src/common/serialization/json.d.ts +6 -0
- package/dist/src/common/serialization/json.d.ts.map +1 -0
- package/dist/src/common/serialization/json.js +341 -0
- package/dist/src/common/serialization/json.js.map +1 -0
- package/dist/src/common/union.d.ts +23 -0
- package/dist/src/common/union.d.ts.map +1 -0
- package/dist/src/common/union.js +57 -0
- package/dist/src/common/union.js.map +1 -0
- package/dist/src/ctx.d.ts +242 -0
- package/dist/src/ctx.d.ts.map +1 -0
- package/dist/src/ctx.js +211 -0
- package/dist/src/ctx.js.map +1 -0
- package/dist/src/helpers/header.d.ts +14 -0
- package/dist/src/helpers/header.d.ts.map +1 -0
- package/dist/src/helpers/header.js +38 -0
- package/dist/src/helpers/header.js.map +1 -0
- package/dist/src/helpers/http.d.ts +70 -0
- package/dist/src/helpers/http.d.ts.map +1 -0
- package/dist/src/helpers/http.js +86 -0
- package/dist/src/helpers/http.js.map +1 -0
- package/dist/src/helpers/multipart.d.ts +26 -0
- package/dist/src/helpers/multipart.d.ts.map +1 -0
- package/dist/src/helpers/multipart.js +182 -0
- package/dist/src/helpers/multipart.js.map +1 -0
- package/dist/src/helpers/router.d.ts +176 -0
- package/dist/src/helpers/router.d.ts.map +1 -0
- package/dist/src/helpers/router.js +55 -0
- package/dist/src/helpers/router.js.map +1 -0
- package/dist/src/http/index.d.ts +24 -0
- package/dist/src/http/index.d.ts.map +1 -0
- package/dist/src/http/index.js +52 -0
- package/dist/src/http/index.js.map +1 -0
- package/dist/src/http/server/index.d.ts +11 -0
- package/dist/src/http/server/index.d.ts.map +1 -0
- package/dist/src/http/server/index.js +413 -0
- package/dist/src/http/server/index.js.map +1 -0
- package/dist/src/http/server/multipart.d.ts +16 -0
- package/dist/src/http/server/multipart.d.ts.map +1 -0
- package/dist/src/http/server/multipart.js +214 -0
- package/dist/src/http/server/multipart.js.map +1 -0
- package/dist/src/http/server/router.d.ts +15 -0
- package/dist/src/http/server/router.d.ts.map +1 -0
- package/dist/src/http/server/router.js +459 -0
- package/dist/src/http/server/router.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib.d.ts +141 -0
- package/dist/src/lib.d.ts.map +1 -0
- package/dist/src/lib.js +116 -0
- package/dist/src/lib.js.map +1 -0
- package/dist/src/scripts/scaffold/bin.d.mts +14 -0
- package/dist/src/scripts/scaffold/bin.d.mts.map +1 -0
- package/dist/src/scripts/scaffold/bin.mjs +559 -0
- package/dist/src/scripts/scaffold/bin.mjs.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +6 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/util/case.d.ts +81 -0
- package/dist/src/util/case.d.ts.map +1 -0
- package/dist/src/util/case.js +111 -0
- package/dist/src/util/case.js.map +1 -0
- package/dist/src/util/differentiate.d.ts +251 -0
- package/dist/src/util/differentiate.d.ts.map +1 -0
- package/dist/src/util/differentiate.js +580 -0
- package/dist/src/util/differentiate.js.map +1 -0
- package/dist/src/util/error.d.ts +13 -0
- package/dist/src/util/error.d.ts.map +1 -0
- package/dist/src/util/error.js +25 -0
- package/dist/src/util/error.js.map +1 -0
- package/dist/src/util/extends.d.ts +10 -0
- package/dist/src/util/extends.d.ts.map +1 -0
- package/dist/src/util/extends.js +31 -0
- package/dist/src/util/extends.js.map +1 -0
- package/dist/src/util/iter.d.ts +39 -0
- package/dist/src/util/iter.d.ts.map +1 -0
- package/dist/src/util/iter.js +72 -0
- package/dist/src/util/iter.js.map +1 -0
- package/dist/src/util/keywords.d.ts +10 -0
- package/dist/src/util/keywords.d.ts.map +1 -0
- package/dist/src/util/keywords.js +85 -0
- package/dist/src/util/keywords.js.map +1 -0
- package/dist/src/util/name.d.ts +12 -0
- package/dist/src/util/name.d.ts.map +1 -0
- package/dist/src/util/name.js +26 -0
- package/dist/src/util/name.js.map +1 -0
- package/dist/src/util/once-queue.d.ts +24 -0
- package/dist/src/util/once-queue.d.ts.map +1 -0
- package/dist/src/util/once-queue.js +34 -0
- package/dist/src/util/once-queue.js.map +1 -0
- package/dist/src/util/openapi3.d.ts +23 -0
- package/dist/src/util/openapi3.d.ts.map +1 -0
- package/dist/src/util/openapi3.js +40 -0
- package/dist/src/util/openapi3.js.map +1 -0
- package/dist/src/util/pluralism.d.ts +23 -0
- package/dist/src/util/pluralism.d.ts.map +1 -0
- package/dist/src/util/pluralism.js +36 -0
- package/dist/src/util/pluralism.js.map +1 -0
- package/dist/src/util/scope.d.ts +85 -0
- package/dist/src/util/scope.d.ts.map +1 -0
- package/dist/src/util/scope.js +111 -0
- package/dist/src/util/scope.js.map +1 -0
- package/dist/src/write.d.ts +23 -0
- package/dist/src/write.d.ts.map +1 -0
- package/dist/src/write.js +62 -0
- package/dist/src/write.js.map +1 -0
- package/generated-defs/helpers/header.ts +83 -0
- package/generated-defs/helpers/http.ts +141 -0
- package/generated-defs/helpers/index.ts +27 -0
- package/generated-defs/helpers/multipart.ts +256 -0
- package/generated-defs/helpers/router.ts +266 -0
- package/package.json +71 -0
- package/src/common/declaration.ts +52 -0
- package/src/common/documentation.ts +26 -0
- package/src/common/enum.ts +28 -0
- package/src/common/interface.ts +264 -0
- package/src/common/model.ts +160 -0
- package/src/common/namespace.ts +243 -0
- package/src/common/reference.ts +319 -0
- package/src/common/scalar.ts +173 -0
- package/src/common/serialization/index.ts +124 -0
- package/src/common/serialization/json.ts +444 -0
- package/src/common/union.ts +76 -0
- package/src/ctx.ts +497 -0
- package/src/helpers/header.ts +55 -0
- package/src/helpers/http.ts +113 -0
- package/src/helpers/multipart.ts +228 -0
- package/src/helpers/router.ts +238 -0
- package/src/http/index.ts +81 -0
- package/src/http/server/index.ts +548 -0
- package/src/http/server/multipart.ts +272 -0
- package/src/http/server/router.ts +686 -0
- package/src/index.ts +56 -0
- package/src/lib.ts +130 -0
- package/src/scripts/scaffold/bin.mts +781 -0
- package/src/testing/index.ts +10 -0
- package/src/util/case.ts +182 -0
- package/src/util/differentiate.ts +957 -0
- package/src/util/error.ts +28 -0
- package/src/util/extends.ts +43 -0
- package/src/util/iter.ts +85 -0
- package/src/util/keywords.ts +90 -0
- package/src/util/name.ts +33 -0
- package/src/util/once-queue.ts +55 -0
- package/src/util/openapi3.ts +53 -0
- package/src/util/pluralism.ts +37 -0
- package/src/util/scope.ts +211 -0
- package/src/write.ts +88 -0
- package/temp/tsconfig.tsbuildinfo +1 -0
- package/test/header.test.ts +26 -0
- package/test/multipart.test.ts +169 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +4 -0
|
@@ -0,0 +1,957 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
BooleanLiteral,
|
|
6
|
+
EnumMember,
|
|
7
|
+
Model,
|
|
8
|
+
ModelProperty,
|
|
9
|
+
NullType,
|
|
10
|
+
NumericLiteral,
|
|
11
|
+
Scalar,
|
|
12
|
+
StringLiteral,
|
|
13
|
+
Type,
|
|
14
|
+
Union,
|
|
15
|
+
UnknownType,
|
|
16
|
+
VoidType,
|
|
17
|
+
getDiscriminator,
|
|
18
|
+
getMaxValue,
|
|
19
|
+
getMinValue,
|
|
20
|
+
isNeverType,
|
|
21
|
+
isUnknownType,
|
|
22
|
+
} from "@typespec/compiler";
|
|
23
|
+
import { getJsScalar } from "../common/scalar.js";
|
|
24
|
+
import { JsContext } from "../ctx.js";
|
|
25
|
+
import { reportDiagnostic } from "../lib.js";
|
|
26
|
+
import { isUnspeakable, parseCase } from "./case.js";
|
|
27
|
+
import { UnimplementedError, UnreachableError } from "./error.js";
|
|
28
|
+
import { getAllProperties } from "./extends.js";
|
|
29
|
+
import { categorize, indent } from "./iter.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A tree structure representing a body of TypeScript code.
|
|
33
|
+
*/
|
|
34
|
+
export type CodeTree = Result | IfChain | Switch | Verbatim;
|
|
35
|
+
|
|
36
|
+
export type JsLiteralType = StringLiteral | BooleanLiteral | NumericLiteral | EnumMember;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A TypeSpec type that is precise, i.e. the type of a single value.
|
|
40
|
+
*/
|
|
41
|
+
export type PreciseType = Scalar | Model | JsLiteralType | VoidType | NullType;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Determines if `t` is a precise type.
|
|
45
|
+
* @param t - the type to test
|
|
46
|
+
* @returns true if `t` is precise, false otherwise.
|
|
47
|
+
*/
|
|
48
|
+
export function isPreciseType(t: Type): t is PreciseType {
|
|
49
|
+
return (
|
|
50
|
+
t.kind === "Scalar" ||
|
|
51
|
+
t.kind === "Model" ||
|
|
52
|
+
t.kind === "Boolean" ||
|
|
53
|
+
t.kind === "Number" ||
|
|
54
|
+
t.kind === "String" ||
|
|
55
|
+
(t.kind === "Intrinsic" && (t.name === "void" || t.name === "null"))
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* An if-chain structure in the CodeTree DSL. This represents a cascading series of if-else-if statements with an optional
|
|
61
|
+
* final `else` branch.
|
|
62
|
+
*/
|
|
63
|
+
export interface IfChain {
|
|
64
|
+
kind: "if-chain";
|
|
65
|
+
branches: IfBranch[];
|
|
66
|
+
else?: CodeTree;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A branch in an if-chain.
|
|
71
|
+
*/
|
|
72
|
+
export interface IfBranch {
|
|
73
|
+
/**
|
|
74
|
+
* A condition to test for this branch.
|
|
75
|
+
*/
|
|
76
|
+
condition: Expression;
|
|
77
|
+
/**
|
|
78
|
+
* The body of this branch, to be executed if the condition is true.
|
|
79
|
+
*/
|
|
80
|
+
body: CodeTree;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A node in the code tree indicating that a precise type has been determined.
|
|
85
|
+
*/
|
|
86
|
+
export interface Result {
|
|
87
|
+
kind: "result";
|
|
88
|
+
type: PreciseType | UnknownType;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A switch structure in the CodeTree DSL.
|
|
93
|
+
*/
|
|
94
|
+
export interface Switch {
|
|
95
|
+
kind: "switch";
|
|
96
|
+
/**
|
|
97
|
+
* The expression to switch on.
|
|
98
|
+
*/
|
|
99
|
+
condition: Expression;
|
|
100
|
+
/**
|
|
101
|
+
* The cases to test for.
|
|
102
|
+
*/
|
|
103
|
+
cases: SwitchCase[];
|
|
104
|
+
/**
|
|
105
|
+
* The default case, if any.
|
|
106
|
+
*/
|
|
107
|
+
default?: CodeTree;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* A verbatim code block.
|
|
112
|
+
*/
|
|
113
|
+
export interface Verbatim {
|
|
114
|
+
kind: "verbatim";
|
|
115
|
+
body: Iterable<string>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A case in a switch statement.
|
|
120
|
+
*/
|
|
121
|
+
export interface SwitchCase {
|
|
122
|
+
/**
|
|
123
|
+
* The value to test for in this case.
|
|
124
|
+
*/
|
|
125
|
+
value: Expression;
|
|
126
|
+
/**
|
|
127
|
+
* The body of this case.
|
|
128
|
+
*/
|
|
129
|
+
body: CodeTree;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* An expression in the CodeTree DSL.
|
|
134
|
+
*/
|
|
135
|
+
export type Expression =
|
|
136
|
+
| BinaryOp
|
|
137
|
+
| UnaryOp
|
|
138
|
+
| TypeOf
|
|
139
|
+
| Literal
|
|
140
|
+
| VerbatimExpression
|
|
141
|
+
| SubjectReference
|
|
142
|
+
| ModelPropertyReference
|
|
143
|
+
| InRange;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A binary operation.
|
|
147
|
+
*/
|
|
148
|
+
export interface BinaryOp {
|
|
149
|
+
kind: "binary-op";
|
|
150
|
+
/**
|
|
151
|
+
* The operator to apply. This operation may be sensitive to the order of the left and right expressions.
|
|
152
|
+
*/
|
|
153
|
+
operator:
|
|
154
|
+
| "==="
|
|
155
|
+
| "!=="
|
|
156
|
+
| "<"
|
|
157
|
+
| "<="
|
|
158
|
+
| ">"
|
|
159
|
+
| ">="
|
|
160
|
+
| "+"
|
|
161
|
+
| "-"
|
|
162
|
+
| "*"
|
|
163
|
+
| "/"
|
|
164
|
+
| "%"
|
|
165
|
+
| "&&"
|
|
166
|
+
| "||"
|
|
167
|
+
| "instanceof"
|
|
168
|
+
| "in";
|
|
169
|
+
/**
|
|
170
|
+
* The left-hand-side operand.
|
|
171
|
+
*/
|
|
172
|
+
left: Expression;
|
|
173
|
+
/**
|
|
174
|
+
* The right-hand-side operand.
|
|
175
|
+
*/
|
|
176
|
+
right: Expression;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* A unary operation.
|
|
181
|
+
*/
|
|
182
|
+
export interface UnaryOp {
|
|
183
|
+
kind: "unary-op";
|
|
184
|
+
/**
|
|
185
|
+
* The operator to apply.
|
|
186
|
+
*/
|
|
187
|
+
operator: "!" | "-";
|
|
188
|
+
/**
|
|
189
|
+
* The operand to apply the operator to.
|
|
190
|
+
*/
|
|
191
|
+
operand: Expression;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* A type-of operation.
|
|
196
|
+
*/
|
|
197
|
+
export interface TypeOf {
|
|
198
|
+
kind: "typeof";
|
|
199
|
+
/**
|
|
200
|
+
* The operand to apply the `typeof` operator to.
|
|
201
|
+
*/
|
|
202
|
+
operand: Expression;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* A literal JavaScript value. The value will be converted to the text of an expression that will yield the same value.
|
|
207
|
+
*/
|
|
208
|
+
export interface Literal {
|
|
209
|
+
kind: "literal";
|
|
210
|
+
/**
|
|
211
|
+
* The value of the literal.
|
|
212
|
+
*/
|
|
213
|
+
value: LiteralValue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* A verbatim expression, written as-is with no modification.
|
|
218
|
+
*/
|
|
219
|
+
export interface VerbatimExpression {
|
|
220
|
+
kind: "verbatim";
|
|
221
|
+
/**
|
|
222
|
+
* The exact text of the expression.
|
|
223
|
+
*/
|
|
224
|
+
text: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* A reference to the "subject" of the code tree.
|
|
229
|
+
*
|
|
230
|
+
* The "subject" is a special expression denoting an input value.
|
|
231
|
+
*/
|
|
232
|
+
export interface SubjectReference {
|
|
233
|
+
kind: "subject";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const SUBJECT = { kind: "subject" } as SubjectReference;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* A reference to a model property. Model property references are rendered by the `referenceModelProperty` function in the
|
|
240
|
+
* options given to `writeCodeTree`, allowing the caller to define how model properties are stored.
|
|
241
|
+
*/
|
|
242
|
+
export interface ModelPropertyReference {
|
|
243
|
+
kind: "model-property";
|
|
244
|
+
property: ModelProperty;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* A check to see if a value is in an integer range.
|
|
249
|
+
*/
|
|
250
|
+
export interface InRange {
|
|
251
|
+
kind: "in-range";
|
|
252
|
+
/**
|
|
253
|
+
* The expression to check.
|
|
254
|
+
*/
|
|
255
|
+
expr: Expression;
|
|
256
|
+
/**
|
|
257
|
+
* The range to check against.
|
|
258
|
+
*/
|
|
259
|
+
range: IntegerRange;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* A literal value that can be used in a JavaScript expression.
|
|
264
|
+
*/
|
|
265
|
+
export type LiteralValue = string | number | boolean | bigint;
|
|
266
|
+
|
|
267
|
+
function isLiteralValueType(type: Type): type is JsLiteralType {
|
|
268
|
+
return (
|
|
269
|
+
type.kind === "Boolean" ||
|
|
270
|
+
type.kind === "Number" ||
|
|
271
|
+
type.kind === "String" ||
|
|
272
|
+
type.kind === "EnumMember"
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const PROPERTY_ID = (prop: ModelProperty) => parseCase(prop.name).camelCase;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Differentiates the variants of a union type. This function returns a CodeTree that will test an input "subject" and
|
|
280
|
+
* determine which of the cases it matches.
|
|
281
|
+
*
|
|
282
|
+
* Compared to `differentiateTypes`, this function is specialized for union types, and will consider union
|
|
283
|
+
* discriminators first, then delegate to `differentiateTypes` for the remaining cases.
|
|
284
|
+
*
|
|
285
|
+
* @param ctx
|
|
286
|
+
* @param type
|
|
287
|
+
*/
|
|
288
|
+
export function differentiateUnion(
|
|
289
|
+
ctx: JsContext,
|
|
290
|
+
union: Union,
|
|
291
|
+
renderPropertyName: (prop: ModelProperty) => string = PROPERTY_ID,
|
|
292
|
+
): CodeTree {
|
|
293
|
+
const discriminator = getDiscriminator(ctx.program, union)?.propertyName;
|
|
294
|
+
// Exclude `never` from the union variants.
|
|
295
|
+
const variants = [...union.variants.values()].filter((v) => !isNeverType(v.type));
|
|
296
|
+
|
|
297
|
+
if (variants.some((v) => isUnknownType(v.type))) {
|
|
298
|
+
// Collapse the whole union to `unknown`.
|
|
299
|
+
return { kind: "result", type: ctx.program.checker.anyType };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!discriminator) {
|
|
303
|
+
const cases = new Set<PreciseType>();
|
|
304
|
+
|
|
305
|
+
for (const variant of variants) {
|
|
306
|
+
if (!isPreciseType(variant.type)) {
|
|
307
|
+
reportDiagnostic(ctx.program, {
|
|
308
|
+
code: "undifferentiable-union-variant",
|
|
309
|
+
target: variant,
|
|
310
|
+
});
|
|
311
|
+
} else {
|
|
312
|
+
cases.add(variant.type);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return differentiateTypes(ctx, cases, renderPropertyName);
|
|
317
|
+
} else {
|
|
318
|
+
const property = (variants[0].type as Model).properties.get(discriminator)!;
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
kind: "switch",
|
|
322
|
+
condition: {
|
|
323
|
+
kind: "model-property",
|
|
324
|
+
property,
|
|
325
|
+
},
|
|
326
|
+
cases: variants.map((v) => {
|
|
327
|
+
const discriminatorPropertyType = (v.type as Model).properties.get(discriminator)!.type as
|
|
328
|
+
| JsLiteralType
|
|
329
|
+
| EnumMember;
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
value: { kind: "literal", value: getJsValue(ctx, discriminatorPropertyType) },
|
|
333
|
+
body: { kind: "result", type: v.type },
|
|
334
|
+
} as SwitchCase;
|
|
335
|
+
}),
|
|
336
|
+
default: {
|
|
337
|
+
kind: "verbatim",
|
|
338
|
+
body: [
|
|
339
|
+
'throw new Error("Unreachable: discriminator did not match any known value or was not present.");',
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Differentiates a set of input types. This function returns a CodeTree that will test an input "subject" and determine
|
|
348
|
+
* which of the cases it matches, executing the corresponding code block.
|
|
349
|
+
*
|
|
350
|
+
* @param ctx - The emitter context.
|
|
351
|
+
* @param cases - A map of cases to differentiate to their respective code blocks.
|
|
352
|
+
* @returns a CodeTree to use with `writeCodeTree`
|
|
353
|
+
*/
|
|
354
|
+
export function differentiateTypes(
|
|
355
|
+
ctx: JsContext,
|
|
356
|
+
cases: Set<PreciseType>,
|
|
357
|
+
renderPropertyName: (prop: ModelProperty) => string = PROPERTY_ID,
|
|
358
|
+
): CodeTree {
|
|
359
|
+
if (cases.size === 0) {
|
|
360
|
+
return {
|
|
361
|
+
kind: "verbatim",
|
|
362
|
+
body: [
|
|
363
|
+
'throw new Error("Unreachable: encountered a value in differentiation where no variants exist.");',
|
|
364
|
+
],
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const categories = categorize(cases.keys(), (type) => type.kind);
|
|
369
|
+
|
|
370
|
+
const literals = [
|
|
371
|
+
...(categories.Boolean ?? []),
|
|
372
|
+
...(categories.Number ?? []),
|
|
373
|
+
...(categories.String ?? []),
|
|
374
|
+
] as JsLiteralType[];
|
|
375
|
+
const models = (categories.Model as Model[]) ?? [];
|
|
376
|
+
const scalars = (categories.Scalar as Scalar[]) ?? [];
|
|
377
|
+
|
|
378
|
+
const intrinsics = (categories.Intrinsic as (VoidType | NullType)[]) ?? [];
|
|
379
|
+
|
|
380
|
+
if (literals.length + scalars.length + intrinsics.length === 0) {
|
|
381
|
+
return differentiateModelTypes(ctx, select(models, cases), renderPropertyName);
|
|
382
|
+
} else {
|
|
383
|
+
const branches: IfBranch[] = [];
|
|
384
|
+
|
|
385
|
+
for (const intrinsic of intrinsics) {
|
|
386
|
+
const intrinsicValue = intrinsic.name === "void" ? "undefined" : "null";
|
|
387
|
+
branches.push({
|
|
388
|
+
condition: {
|
|
389
|
+
kind: "binary-op",
|
|
390
|
+
operator: "===",
|
|
391
|
+
left: SUBJECT,
|
|
392
|
+
right: {
|
|
393
|
+
kind: "verbatim",
|
|
394
|
+
text: intrinsicValue,
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
body: {
|
|
398
|
+
kind: "result",
|
|
399
|
+
type: intrinsic,
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
for (const literal of literals) {
|
|
405
|
+
branches.push({
|
|
406
|
+
condition: {
|
|
407
|
+
kind: "binary-op",
|
|
408
|
+
operator: "===",
|
|
409
|
+
left: SUBJECT,
|
|
410
|
+
right: { kind: "literal", value: getJsValue(ctx, literal) },
|
|
411
|
+
},
|
|
412
|
+
body: {
|
|
413
|
+
kind: "result",
|
|
414
|
+
type: literal,
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const scalarRepresentations = new Map<string, Scalar>();
|
|
420
|
+
|
|
421
|
+
for (const scalar of scalars) {
|
|
422
|
+
const jsScalar = getJsScalar(ctx.program, scalar, scalar);
|
|
423
|
+
|
|
424
|
+
if (scalarRepresentations.has(jsScalar)) {
|
|
425
|
+
reportDiagnostic(ctx.program, {
|
|
426
|
+
code: "undifferentiable-scalar",
|
|
427
|
+
target: scalar,
|
|
428
|
+
format: {
|
|
429
|
+
competitor: scalarRepresentations.get(jsScalar)!.name,
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let test: Expression;
|
|
436
|
+
|
|
437
|
+
switch (jsScalar) {
|
|
438
|
+
case "Uint8Array":
|
|
439
|
+
test = {
|
|
440
|
+
kind: "binary-op",
|
|
441
|
+
operator: "instanceof",
|
|
442
|
+
left: SUBJECT,
|
|
443
|
+
right: { kind: "verbatim", text: "Uint8Array" },
|
|
444
|
+
};
|
|
445
|
+
break;
|
|
446
|
+
case "number":
|
|
447
|
+
test = {
|
|
448
|
+
kind: "binary-op",
|
|
449
|
+
operator: "===",
|
|
450
|
+
left: { kind: "typeof", operand: SUBJECT },
|
|
451
|
+
right: { kind: "literal", value: "number" },
|
|
452
|
+
};
|
|
453
|
+
break;
|
|
454
|
+
case "bigint":
|
|
455
|
+
test = {
|
|
456
|
+
kind: "binary-op",
|
|
457
|
+
operator: "===",
|
|
458
|
+
left: { kind: "typeof", operand: SUBJECT },
|
|
459
|
+
right: { kind: "literal", value: "bigint" },
|
|
460
|
+
};
|
|
461
|
+
break;
|
|
462
|
+
case "string":
|
|
463
|
+
test = {
|
|
464
|
+
kind: "binary-op",
|
|
465
|
+
operator: "===",
|
|
466
|
+
left: { kind: "typeof", operand: SUBJECT },
|
|
467
|
+
right: { kind: "literal", value: "string" },
|
|
468
|
+
};
|
|
469
|
+
break;
|
|
470
|
+
case "boolean":
|
|
471
|
+
test = {
|
|
472
|
+
kind: "binary-op",
|
|
473
|
+
operator: "===",
|
|
474
|
+
left: { kind: "typeof", operand: SUBJECT },
|
|
475
|
+
right: { kind: "literal", value: "boolean" },
|
|
476
|
+
};
|
|
477
|
+
break;
|
|
478
|
+
case "Date":
|
|
479
|
+
test = {
|
|
480
|
+
kind: "binary-op",
|
|
481
|
+
operator: "instanceof",
|
|
482
|
+
left: SUBJECT,
|
|
483
|
+
right: { kind: "verbatim", text: "Date" },
|
|
484
|
+
};
|
|
485
|
+
break;
|
|
486
|
+
default:
|
|
487
|
+
throw new UnimplementedError(
|
|
488
|
+
`scalar differentiation for unknown JS Scalar '${jsScalar}'.`,
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
branches.push({
|
|
493
|
+
condition: test,
|
|
494
|
+
body: {
|
|
495
|
+
kind: "result",
|
|
496
|
+
type: scalar,
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
kind: "if-chain",
|
|
503
|
+
branches,
|
|
504
|
+
else:
|
|
505
|
+
models.length > 0
|
|
506
|
+
? differentiateModelTypes(ctx, select(models, cases), renderPropertyName)
|
|
507
|
+
: undefined,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Select a subset of keys from a map.
|
|
513
|
+
*
|
|
514
|
+
* @param keys - The keys to select.
|
|
515
|
+
* @param map - The map to select from.
|
|
516
|
+
* @returns a map containing only those keys of the original map that were also in the `keys` iterable.
|
|
517
|
+
*/
|
|
518
|
+
function select<V1, V2 extends V1>(keys: Iterable<V2>, set: Set<V1>): Set<V2> {
|
|
519
|
+
const result = new Set<V2>();
|
|
520
|
+
for (const key of keys) {
|
|
521
|
+
if (set.has(key)) result.add(key);
|
|
522
|
+
}
|
|
523
|
+
return result;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Gets a JavaScript literal value for a given LiteralType.
|
|
529
|
+
*/
|
|
530
|
+
function getJsValue(ctx: JsContext, literal: JsLiteralType | EnumMember): LiteralValue {
|
|
531
|
+
switch (literal.kind) {
|
|
532
|
+
case "Boolean":
|
|
533
|
+
return literal.value;
|
|
534
|
+
case "Number": {
|
|
535
|
+
const asNumber = literal.numericValue.asNumber();
|
|
536
|
+
|
|
537
|
+
if (asNumber) return asNumber;
|
|
538
|
+
|
|
539
|
+
const asBigInt = literal.numericValue.asBigInt();
|
|
540
|
+
|
|
541
|
+
if (asBigInt) return asBigInt;
|
|
542
|
+
|
|
543
|
+
reportDiagnostic(ctx.program, {
|
|
544
|
+
code: "unrepresentable-numeric-constant",
|
|
545
|
+
target: literal,
|
|
546
|
+
});
|
|
547
|
+
return 0;
|
|
548
|
+
}
|
|
549
|
+
case "String":
|
|
550
|
+
return literal.value;
|
|
551
|
+
case "EnumMember":
|
|
552
|
+
return literal.value ?? literal.name;
|
|
553
|
+
default:
|
|
554
|
+
throw new UnreachableError(
|
|
555
|
+
"getJsValue for " + (literal satisfies never as JsLiteralType).kind,
|
|
556
|
+
{ literal },
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* An integer range, inclusive.
|
|
563
|
+
*/
|
|
564
|
+
type IntegerRange = [number, number];
|
|
565
|
+
|
|
566
|
+
function getIntegerRange(ctx: JsContext, property: ModelProperty): IntegerRange | false {
|
|
567
|
+
if (
|
|
568
|
+
property.type.kind === "Scalar" &&
|
|
569
|
+
getJsScalar(ctx.program, property.type, property) === "number"
|
|
570
|
+
) {
|
|
571
|
+
const minValue = getMinValue(ctx.program, property);
|
|
572
|
+
const maxValue = getMaxValue(ctx.program, property);
|
|
573
|
+
|
|
574
|
+
if (minValue !== undefined && maxValue !== undefined) {
|
|
575
|
+
return [minValue, maxValue];
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function overlaps(range: IntegerRange, other: IntegerRange): boolean {
|
|
583
|
+
return range[0] <= other[1] && range[1] >= other[0];
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Differentiate a set of model types based on their properties. This function returns a CodeTree that will test an input
|
|
588
|
+
* "subject" and determine which of the cases it matches, executing the corresponding code block.
|
|
589
|
+
*
|
|
590
|
+
* @param ctx - The emitter context.
|
|
591
|
+
* @param models - A map of models to differentiate to their respective code blocks.
|
|
592
|
+
* @param renderPropertyName - A function that converts a model property reference over the subject to a string.
|
|
593
|
+
* @returns a CodeTree to use with `writeCodeTree`
|
|
594
|
+
*/
|
|
595
|
+
export function differentiateModelTypes(
|
|
596
|
+
ctx: JsContext,
|
|
597
|
+
models: Set<Model>,
|
|
598
|
+
renderPropertyName: (prop: ModelProperty) => string = PROPERTY_ID,
|
|
599
|
+
): CodeTree {
|
|
600
|
+
// Horrible n^2 operation to get the unique properties of all models in the map, but hopefully n is small, so it should
|
|
601
|
+
// be okay until you have a lot of models to differentiate.
|
|
602
|
+
|
|
603
|
+
type PropertyName = string;
|
|
604
|
+
type RenderedPropertyName = string & { __brand: "RenderedPropertyName" };
|
|
605
|
+
|
|
606
|
+
const uniqueProps = new Map<Model, Set<PropertyName>>();
|
|
607
|
+
|
|
608
|
+
// Map of property names to maps of literal values that identify a model.
|
|
609
|
+
const propertyLiterals = new Map<RenderedPropertyName, Map<LiteralValue, Model>>();
|
|
610
|
+
// Map of models to properties with values that can uniquely identify it
|
|
611
|
+
const uniqueLiterals = new Map<Model, Set<RenderedPropertyName>>();
|
|
612
|
+
|
|
613
|
+
const propertyRanges = new Map<RenderedPropertyName, Map<IntegerRange, Model>>();
|
|
614
|
+
const uniqueRanges = new Map<Model, Set<RenderedPropertyName>>();
|
|
615
|
+
|
|
616
|
+
for (const model of models) {
|
|
617
|
+
const props = new Set<string>();
|
|
618
|
+
|
|
619
|
+
for (const prop of getAllProperties(model)) {
|
|
620
|
+
// Don't consider optional properties for differentiation.
|
|
621
|
+
if (prop.optional) continue;
|
|
622
|
+
|
|
623
|
+
// Ignore properties that have no parseable name.
|
|
624
|
+
if (isUnspeakable(prop.name)) continue;
|
|
625
|
+
|
|
626
|
+
const renderedPropName = renderPropertyName(prop) as RenderedPropertyName;
|
|
627
|
+
|
|
628
|
+
// CASE - literal value
|
|
629
|
+
|
|
630
|
+
if (isLiteralValueType(prop.type)) {
|
|
631
|
+
let literals = propertyLiterals.get(renderedPropName);
|
|
632
|
+
if (!literals) {
|
|
633
|
+
literals = new Map();
|
|
634
|
+
propertyLiterals.set(renderedPropName, literals);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const value = getJsValue(ctx, prop.type);
|
|
638
|
+
|
|
639
|
+
const other = literals.get(value);
|
|
640
|
+
|
|
641
|
+
if (other) {
|
|
642
|
+
// Literal already used. Leave the literal in the propertyLiterals map to prevent future collisions,
|
|
643
|
+
// but remove the model from the uniqueLiterals map.
|
|
644
|
+
uniqueLiterals.get(other)?.delete(renderedPropName);
|
|
645
|
+
} else {
|
|
646
|
+
// Literal is available. Add the model to the uniqueLiterals map and set this value.
|
|
647
|
+
literals.set(value, model);
|
|
648
|
+
let modelsUniqueLiterals = uniqueLiterals.get(model);
|
|
649
|
+
if (!modelsUniqueLiterals) {
|
|
650
|
+
modelsUniqueLiterals = new Set();
|
|
651
|
+
uniqueLiterals.set(model, modelsUniqueLiterals);
|
|
652
|
+
}
|
|
653
|
+
modelsUniqueLiterals.add(renderedPropName);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// CASE - unique range
|
|
658
|
+
|
|
659
|
+
const range = getIntegerRange(ctx, prop);
|
|
660
|
+
if (range) {
|
|
661
|
+
let ranges = propertyRanges.get(renderedPropName);
|
|
662
|
+
if (!ranges) {
|
|
663
|
+
ranges = new Map();
|
|
664
|
+
propertyRanges.set(renderedPropName, ranges);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const overlappingRanges = [...ranges.entries()].filter(([r]) => overlaps(r, range));
|
|
668
|
+
|
|
669
|
+
if (overlappingRanges.length > 0) {
|
|
670
|
+
// Overlapping range found. Remove the model from the uniqueRanges map.
|
|
671
|
+
for (const [, other] of overlappingRanges) {
|
|
672
|
+
uniqueRanges.get(other)?.delete(renderedPropName);
|
|
673
|
+
}
|
|
674
|
+
} else {
|
|
675
|
+
// No overlapping range found. Add the model to the uniqueRanges map and set this range.
|
|
676
|
+
ranges.set(range, model);
|
|
677
|
+
let modelsUniqueRanges = uniqueRanges.get(model);
|
|
678
|
+
if (!modelsUniqueRanges) {
|
|
679
|
+
modelsUniqueRanges = new Set();
|
|
680
|
+
uniqueRanges.set(model, modelsUniqueRanges);
|
|
681
|
+
}
|
|
682
|
+
modelsUniqueRanges.add(renderedPropName);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// CASE - unique property
|
|
687
|
+
|
|
688
|
+
let valid = true;
|
|
689
|
+
for (const [, other] of uniqueProps) {
|
|
690
|
+
if (
|
|
691
|
+
other.has(prop.name) ||
|
|
692
|
+
(isLiteralValueType(prop.type) &&
|
|
693
|
+
propertyLiterals
|
|
694
|
+
.get(renderedPropName)
|
|
695
|
+
?.has(getJsValue(ctx, prop.type as JsLiteralType)))
|
|
696
|
+
) {
|
|
697
|
+
valid = false;
|
|
698
|
+
other.delete(prop.name);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (valid) {
|
|
703
|
+
props.add(prop.name);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
uniqueProps.set(model, props);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const branches: IfBranch[] = [];
|
|
711
|
+
|
|
712
|
+
let defaultCase: Model | undefined = undefined;
|
|
713
|
+
|
|
714
|
+
for (const [model, unique] of uniqueProps) {
|
|
715
|
+
const literals = uniqueLiterals.get(model);
|
|
716
|
+
const ranges = uniqueRanges.get(model);
|
|
717
|
+
if (unique.size === 0 && (!literals || literals.size === 0) && (!ranges || ranges.size === 0)) {
|
|
718
|
+
if (defaultCase) {
|
|
719
|
+
reportDiagnostic(ctx.program, {
|
|
720
|
+
code: "undifferentiable-model",
|
|
721
|
+
target: model,
|
|
722
|
+
});
|
|
723
|
+
return {
|
|
724
|
+
kind: "result",
|
|
725
|
+
type: defaultCase,
|
|
726
|
+
};
|
|
727
|
+
} else {
|
|
728
|
+
// Allow a single default case. This covers more APIs that have a single model that is not differentiated by a
|
|
729
|
+
// unique property, in which case we can make it the `else` case.
|
|
730
|
+
defaultCase = model;
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (literals && literals.size > 0) {
|
|
736
|
+
// A literal property value exists that can differentiate this model.
|
|
737
|
+
const firstUniqueLiteral = literals.values().next().value as RenderedPropertyName;
|
|
738
|
+
|
|
739
|
+
const property = [...model.properties.values()].find(
|
|
740
|
+
(p) => (renderPropertyName(p) as RenderedPropertyName) === firstUniqueLiteral,
|
|
741
|
+
)!;
|
|
742
|
+
|
|
743
|
+
branches.push({
|
|
744
|
+
condition: {
|
|
745
|
+
kind: "binary-op",
|
|
746
|
+
left: {
|
|
747
|
+
kind: "binary-op",
|
|
748
|
+
left: { kind: "literal", value: renderPropertyName(property) },
|
|
749
|
+
operator: "in",
|
|
750
|
+
right: SUBJECT,
|
|
751
|
+
},
|
|
752
|
+
operator: "&&",
|
|
753
|
+
right: {
|
|
754
|
+
kind: "binary-op",
|
|
755
|
+
left: { kind: "model-property", property },
|
|
756
|
+
operator: "===",
|
|
757
|
+
right: {
|
|
758
|
+
kind: "literal",
|
|
759
|
+
value: getJsValue(ctx, property.type as JsLiteralType),
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
body: { kind: "result", type: model },
|
|
764
|
+
});
|
|
765
|
+
} else if (ranges && ranges.size > 0) {
|
|
766
|
+
// A range property value exists that can differentiate this model.
|
|
767
|
+
const firstUniqueRange = ranges.values().next().value as RenderedPropertyName;
|
|
768
|
+
|
|
769
|
+
const property = [...model.properties.values()].find(
|
|
770
|
+
(p) => renderPropertyName(p) === firstUniqueRange,
|
|
771
|
+
)!;
|
|
772
|
+
|
|
773
|
+
const range = [...propertyRanges.get(firstUniqueRange)!.entries()].find(
|
|
774
|
+
([range, candidate]) => candidate === model,
|
|
775
|
+
)![0];
|
|
776
|
+
|
|
777
|
+
branches.push({
|
|
778
|
+
condition: {
|
|
779
|
+
kind: "binary-op",
|
|
780
|
+
left: {
|
|
781
|
+
kind: "binary-op",
|
|
782
|
+
left: { kind: "literal", value: renderPropertyName(property) },
|
|
783
|
+
operator: "in",
|
|
784
|
+
right: SUBJECT,
|
|
785
|
+
},
|
|
786
|
+
operator: "&&",
|
|
787
|
+
right: {
|
|
788
|
+
kind: "in-range",
|
|
789
|
+
expr: { kind: "model-property", property },
|
|
790
|
+
range,
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
body: { kind: "result", type: model },
|
|
794
|
+
});
|
|
795
|
+
} else {
|
|
796
|
+
const firstUniqueProp = unique.values().next().value as PropertyName;
|
|
797
|
+
|
|
798
|
+
branches.push({
|
|
799
|
+
condition: {
|
|
800
|
+
kind: "binary-op",
|
|
801
|
+
left: { kind: "literal", value: firstUniqueProp },
|
|
802
|
+
operator: "in",
|
|
803
|
+
right: SUBJECT,
|
|
804
|
+
},
|
|
805
|
+
body: { kind: "result", type: model },
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return {
|
|
811
|
+
kind: "if-chain",
|
|
812
|
+
branches,
|
|
813
|
+
else: defaultCase
|
|
814
|
+
? {
|
|
815
|
+
kind: "result",
|
|
816
|
+
type: defaultCase,
|
|
817
|
+
}
|
|
818
|
+
: undefined,
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Options for the `writeCodeTree` function.
|
|
824
|
+
*/
|
|
825
|
+
export interface CodeTreeOptions {
|
|
826
|
+
/**
|
|
827
|
+
* The subject expression to use in the code tree.
|
|
828
|
+
*
|
|
829
|
+
* This text is used whenever a `SubjectReference` is encountered in the code tree, allowing the caller to specify
|
|
830
|
+
* how the subject is stored and referenced.
|
|
831
|
+
*/
|
|
832
|
+
subject: string;
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* A function that converts a model property to a string reference.
|
|
836
|
+
*
|
|
837
|
+
* This function is used whenever a `ModelPropertyReference` is encountered in the code tree, allowing the caller to
|
|
838
|
+
* specify how model properties are stored and referenced.
|
|
839
|
+
*/
|
|
840
|
+
referenceModelProperty: (p: ModelProperty) => string;
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Renders a result when encountered in the code tree.
|
|
844
|
+
*/
|
|
845
|
+
renderResult: (type: PreciseType | UnknownType) => Iterable<string>;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Writes a code tree to text, given a set of options.
|
|
850
|
+
*
|
|
851
|
+
* @param ctx - The emitter context.
|
|
852
|
+
* @param tree - The code tree to write.
|
|
853
|
+
* @param options - The options to use when writing the code tree.
|
|
854
|
+
*/
|
|
855
|
+
export function* writeCodeTree(
|
|
856
|
+
ctx: JsContext,
|
|
857
|
+
tree: CodeTree,
|
|
858
|
+
options: CodeTreeOptions,
|
|
859
|
+
): Iterable<string> {
|
|
860
|
+
switch (tree.kind) {
|
|
861
|
+
case "result":
|
|
862
|
+
yield* options.renderResult(tree.type);
|
|
863
|
+
break;
|
|
864
|
+
case "if-chain": {
|
|
865
|
+
let first = true;
|
|
866
|
+
for (const branch of tree.branches) {
|
|
867
|
+
const condition = writeExpression(ctx, branch.condition, options);
|
|
868
|
+
if (first) {
|
|
869
|
+
first = false;
|
|
870
|
+
yield `if (${condition}) {`;
|
|
871
|
+
} else {
|
|
872
|
+
yield `} else if (${condition}) {`;
|
|
873
|
+
}
|
|
874
|
+
yield* indent(writeCodeTree(ctx, branch.body, options));
|
|
875
|
+
}
|
|
876
|
+
if (tree.else) {
|
|
877
|
+
yield "} else {";
|
|
878
|
+
yield* indent(writeCodeTree(ctx, tree.else, options));
|
|
879
|
+
}
|
|
880
|
+
yield "}";
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
case "switch": {
|
|
884
|
+
yield `switch (${writeExpression(ctx, tree.condition, options)}) {`;
|
|
885
|
+
for (const _case of tree.cases) {
|
|
886
|
+
yield ` case ${writeExpression(ctx, _case.value, options)}: {`;
|
|
887
|
+
yield* indent(indent(writeCodeTree(ctx, _case.body, options)));
|
|
888
|
+
yield " }";
|
|
889
|
+
}
|
|
890
|
+
if (tree.default) {
|
|
891
|
+
yield " default: {";
|
|
892
|
+
yield* indent(indent(writeCodeTree(ctx, tree.default, options)));
|
|
893
|
+
yield " }";
|
|
894
|
+
}
|
|
895
|
+
yield "}";
|
|
896
|
+
break;
|
|
897
|
+
}
|
|
898
|
+
case "verbatim":
|
|
899
|
+
yield* tree.body;
|
|
900
|
+
break;
|
|
901
|
+
default:
|
|
902
|
+
throw new UnreachableError("writeCodeTree for " + (tree satisfies never as CodeTree).kind, {
|
|
903
|
+
tree,
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function writeExpression(ctx: JsContext, expression: Expression, options: CodeTreeOptions): string {
|
|
909
|
+
switch (expression.kind) {
|
|
910
|
+
case "binary-op":
|
|
911
|
+
return `(${writeExpression(ctx, expression.left, options)}) ${expression.operator} (${writeExpression(
|
|
912
|
+
ctx,
|
|
913
|
+
expression.right,
|
|
914
|
+
options,
|
|
915
|
+
)})`;
|
|
916
|
+
case "unary-op":
|
|
917
|
+
return `${expression.operator}(${writeExpression(ctx, expression.operand, options)})`;
|
|
918
|
+
case "typeof":
|
|
919
|
+
return `typeof (${writeExpression(ctx, expression.operand, options)})`;
|
|
920
|
+
case "literal":
|
|
921
|
+
switch (typeof expression.value) {
|
|
922
|
+
case "string":
|
|
923
|
+
return JSON.stringify(expression.value);
|
|
924
|
+
case "number":
|
|
925
|
+
case "bigint":
|
|
926
|
+
return String(expression.value);
|
|
927
|
+
case "boolean":
|
|
928
|
+
return expression.value ? "true" : "false";
|
|
929
|
+
default:
|
|
930
|
+
throw new UnreachableError(
|
|
931
|
+
`writeExpression for literal value type '${typeof expression.value}'`,
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
case "in-range": {
|
|
935
|
+
const {
|
|
936
|
+
expr,
|
|
937
|
+
range: [min, max],
|
|
938
|
+
} = expression;
|
|
939
|
+
const exprText = writeExpression(ctx, expr, options);
|
|
940
|
+
|
|
941
|
+
return `(${exprText} >= ${min} && ${exprText} <= ${max})`;
|
|
942
|
+
}
|
|
943
|
+
case "verbatim":
|
|
944
|
+
return expression.text;
|
|
945
|
+
case "subject":
|
|
946
|
+
return options.subject;
|
|
947
|
+
case "model-property":
|
|
948
|
+
return options.referenceModelProperty(expression.property);
|
|
949
|
+
default:
|
|
950
|
+
throw new UnreachableError(
|
|
951
|
+
"writeExpression for " + (expression satisfies never as Expression).kind,
|
|
952
|
+
{
|
|
953
|
+
expression,
|
|
954
|
+
},
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
}
|