@xlr-lib/xlr-utils 0.2.0--canary.2.131 → 1.0.0
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/dist/cjs/index.cjs +74 -522
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.legacy-esm.js +73 -494
- package/dist/index.mjs +73 -494
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -3
- package/src/__tests__/validation-helpers.test.ts +2 -2
- package/src/__tests__/xlr-helpers.test.ts +543 -0
- package/src/index.ts +1 -3
- package/src/type-checks.ts +0 -72
- package/src/validation-helpers.ts +2 -8
- package/src/{ts-helpers.ts → xlr-helpers.ts} +70 -178
- package/types/index.d.ts +1 -3
- package/types/type-checks.d.ts +0 -27
- package/types/xlr-helpers.d.ts +13 -0
- package/src/__tests__/annotations.test.ts +0 -40
- package/src/__tests__/documentation.test.ts +0 -116
- package/src/__tests__/ts-helpers.test.ts +0 -180
- package/src/__tests__/type-check.test.ts +0 -39
- package/src/annotations.ts +0 -237
- package/src/documentation.ts +0 -243
- package/types/annotations.d.ts +0 -7
- package/types/documentation.d.ts +0 -14
- package/types/ts-helpers.d.ts +0 -50
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import { test, expect, describe } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
NodeType,
|
|
4
|
+
NodeTypeWithGenerics,
|
|
5
|
+
ObjectNode,
|
|
6
|
+
OrType,
|
|
7
|
+
} from "@xlr-lib/xlr";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
applyPickOrOmitToNodeType,
|
|
11
|
+
applyPartialOrRequiredToNodeType,
|
|
12
|
+
fillInGenerics,
|
|
13
|
+
applyExcludeToNodeType,
|
|
14
|
+
} from "../xlr-helpers";
|
|
15
|
+
|
|
16
|
+
describe("fillInGenerics", () => {
|
|
17
|
+
test("returns primitive node unchanged when no generics", () => {
|
|
18
|
+
const node: NodeType = { type: "string" };
|
|
19
|
+
expect(fillInGenerics(node)).toBe(node);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("fills ref with value from generics map", () => {
|
|
23
|
+
const refNode: NodeType = {
|
|
24
|
+
type: "ref",
|
|
25
|
+
ref: "T",
|
|
26
|
+
};
|
|
27
|
+
const generics = new Map<string, NodeType>([["T", { type: "string" }]]);
|
|
28
|
+
const result = fillInGenerics(refNode, generics);
|
|
29
|
+
expect(result).toEqual({ type: "string" });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("ref with genericArguments fills each argument", () => {
|
|
33
|
+
const refNode: NodeType = {
|
|
34
|
+
type: "ref",
|
|
35
|
+
ref: "Outer",
|
|
36
|
+
genericArguments: [{ type: "ref", ref: "T" }],
|
|
37
|
+
};
|
|
38
|
+
const generics = new Map<string, NodeType>([["T", { type: "number" }]]);
|
|
39
|
+
const result = fillInGenerics(refNode, generics);
|
|
40
|
+
expect(result).toMatchObject({
|
|
41
|
+
type: "ref",
|
|
42
|
+
ref: "Outer",
|
|
43
|
+
genericArguments: [{ type: "number" }],
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("ref not in map returns ref with filled genericArguments", () => {
|
|
48
|
+
const refNode: NodeType = {
|
|
49
|
+
type: "ref",
|
|
50
|
+
ref: "Outer",
|
|
51
|
+
genericArguments: [{ type: "ref", ref: "T" }],
|
|
52
|
+
};
|
|
53
|
+
const generics = new Map<string, NodeType>([["T", { type: "boolean" }]]);
|
|
54
|
+
const result = fillInGenerics(refNode, generics);
|
|
55
|
+
expect(result).toMatchObject({
|
|
56
|
+
type: "ref",
|
|
57
|
+
ref: "Outer",
|
|
58
|
+
genericArguments: [{ type: "boolean" }],
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("object properties are filled recursively", () => {
|
|
63
|
+
const obj: NodeType = {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
p: { required: true, node: { type: "ref", ref: "T" } },
|
|
67
|
+
},
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
};
|
|
70
|
+
const generics = new Map<string, NodeType>([["T", { type: "string" }]]);
|
|
71
|
+
const result = fillInGenerics(obj, generics);
|
|
72
|
+
expect(result).toMatchObject({
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
p: { required: true, node: { type: "string" } },
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("array elementType is filled", () => {
|
|
81
|
+
const arr: NodeType = {
|
|
82
|
+
type: "array",
|
|
83
|
+
elementType: { type: "ref", ref: "T" },
|
|
84
|
+
};
|
|
85
|
+
const generics = new Map<string, NodeType>([["T", { type: "number" }]]);
|
|
86
|
+
const result = fillInGenerics(arr, generics);
|
|
87
|
+
expect(result).toEqual({
|
|
88
|
+
type: "array",
|
|
89
|
+
elementType: { type: "number" },
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("or type members are filled", () => {
|
|
94
|
+
const orNode: NodeType = {
|
|
95
|
+
type: "or",
|
|
96
|
+
or: [{ type: "ref", ref: "T" }, { type: "string" }],
|
|
97
|
+
};
|
|
98
|
+
const generics = new Map<string, NodeType>([["T", { type: "number" }]]);
|
|
99
|
+
const result = fillInGenerics(orNode, generics);
|
|
100
|
+
expect(result).toMatchObject({
|
|
101
|
+
type: "or",
|
|
102
|
+
or: [{ type: "number" }, { type: "string" }],
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("and type members are filled", () => {
|
|
107
|
+
const andNode: NodeType = {
|
|
108
|
+
type: "and",
|
|
109
|
+
and: [
|
|
110
|
+
{ type: "object", properties: {}, additionalProperties: false },
|
|
111
|
+
{ type: "ref", ref: "T" },
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
const generics = new Map<string, NodeType>([["T", { type: "null" }]]);
|
|
115
|
+
const result = fillInGenerics(andNode, generics);
|
|
116
|
+
expect(result.type).toBe("and");
|
|
117
|
+
expect((result as { and: NodeType[] }).and).toHaveLength(2);
|
|
118
|
+
expect((result as { and: NodeType[] }).and[1]).toEqual({ type: "null" });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("record keyType and valueType are filled", () => {
|
|
122
|
+
const recordNode: NodeType = {
|
|
123
|
+
type: "record",
|
|
124
|
+
keyType: { type: "ref", ref: "K" },
|
|
125
|
+
valueType: { type: "ref", ref: "V" },
|
|
126
|
+
};
|
|
127
|
+
const generics = new Map<string, NodeType>([
|
|
128
|
+
["K", { type: "string" }],
|
|
129
|
+
["V", { type: "number" }],
|
|
130
|
+
]);
|
|
131
|
+
const result = fillInGenerics(recordNode, generics);
|
|
132
|
+
expect(result).toEqual({
|
|
133
|
+
type: "record",
|
|
134
|
+
keyType: { type: "string" },
|
|
135
|
+
valueType: { type: "number" },
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("generic node without map builds defaults from genericTokens", () => {
|
|
140
|
+
const genericObj: NodeTypeWithGenerics<ObjectNode> = {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {},
|
|
143
|
+
additionalProperties: false,
|
|
144
|
+
genericTokens: [
|
|
145
|
+
{
|
|
146
|
+
symbol: "T",
|
|
147
|
+
default: { type: "string" },
|
|
148
|
+
constraints: undefined,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
const result = fillInGenerics(genericObj);
|
|
153
|
+
expect(result).toMatchObject({ type: "object" });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("conditional type with both sides non-ref resolves via resolveConditional", () => {
|
|
157
|
+
const conditionalNode: NodeType = {
|
|
158
|
+
type: "conditional",
|
|
159
|
+
check: {
|
|
160
|
+
left: { type: "string" },
|
|
161
|
+
right: { type: "string" },
|
|
162
|
+
},
|
|
163
|
+
value: {
|
|
164
|
+
true: { type: "number" },
|
|
165
|
+
false: { type: "boolean" },
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
const result = fillInGenerics(conditionalNode);
|
|
169
|
+
expect(result).toEqual({ type: "number" });
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("conditional type with ref in check returns unresolved conditional", () => {
|
|
173
|
+
const conditionalNode: NodeType = {
|
|
174
|
+
type: "conditional",
|
|
175
|
+
check: {
|
|
176
|
+
left: { type: "ref", ref: "T" },
|
|
177
|
+
right: { type: "string" },
|
|
178
|
+
},
|
|
179
|
+
value: {
|
|
180
|
+
true: { type: "number" },
|
|
181
|
+
false: { type: "boolean" },
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
const result = fillInGenerics(conditionalNode);
|
|
185
|
+
expect(result).toMatchObject({
|
|
186
|
+
type: "conditional",
|
|
187
|
+
check: { left: { type: "ref", ref: "T" }, right: { type: "string" } },
|
|
188
|
+
value: { true: { type: "number" }, false: { type: "boolean" } },
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("object with genericTokens and extends fills constraints and extends", () => {
|
|
193
|
+
const objWithExtends: NodeTypeWithGenerics<ObjectNode> = {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: { p: { required: false, node: { type: "ref", ref: "T" } } },
|
|
196
|
+
additionalProperties: false,
|
|
197
|
+
genericTokens: [
|
|
198
|
+
{ symbol: "T", default: { type: "string" }, constraints: undefined },
|
|
199
|
+
],
|
|
200
|
+
extends: { type: "ref", ref: "Base" },
|
|
201
|
+
};
|
|
202
|
+
const generics = new Map<string, NodeType>([
|
|
203
|
+
["T", { type: "number" }],
|
|
204
|
+
["Base", { type: "object", properties: {}, additionalProperties: false }],
|
|
205
|
+
]);
|
|
206
|
+
const result = fillInGenerics(objWithExtends, generics);
|
|
207
|
+
expect(result).toMatchObject({
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: { p: { node: { type: "number" } } },
|
|
210
|
+
extends: { type: "object" },
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("applyPickOrOmitToNodeType", () => {
|
|
216
|
+
test("Omit - Doesn't filter property that doesn't exist", () => {
|
|
217
|
+
const baseObject: NodeType = {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: { foo: { node: { type: "string" }, required: false } },
|
|
220
|
+
additionalProperties: false,
|
|
221
|
+
};
|
|
222
|
+
const operation = "Omit";
|
|
223
|
+
const properties = new Set(["bar"]);
|
|
224
|
+
|
|
225
|
+
const result = applyPickOrOmitToNodeType(baseObject, operation, properties);
|
|
226
|
+
|
|
227
|
+
expect(result).toStrictEqual(baseObject);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("Omit - Filters property that do exist", () => {
|
|
231
|
+
const baseObject: NodeType = {
|
|
232
|
+
type: "object",
|
|
233
|
+
properties: {
|
|
234
|
+
foo: { node: { type: "string" }, required: false },
|
|
235
|
+
bar: { node: { type: "string" }, required: false },
|
|
236
|
+
},
|
|
237
|
+
additionalProperties: false,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const filteredObject: NodeType = {
|
|
241
|
+
type: "object",
|
|
242
|
+
properties: { foo: { node: { type: "string" }, required: false } },
|
|
243
|
+
additionalProperties: false,
|
|
244
|
+
};
|
|
245
|
+
const operation = "Omit";
|
|
246
|
+
const properties = new Set(["bar"]);
|
|
247
|
+
|
|
248
|
+
const result = applyPickOrOmitToNodeType(baseObject, operation, properties);
|
|
249
|
+
|
|
250
|
+
expect(result).toStrictEqual(filteredObject);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("Pick - Selects property that do exist", () => {
|
|
254
|
+
const baseObject: NodeType = {
|
|
255
|
+
type: "object",
|
|
256
|
+
properties: {
|
|
257
|
+
foo: { node: { type: "string" }, required: false },
|
|
258
|
+
bar: { node: { type: "string" }, required: false },
|
|
259
|
+
},
|
|
260
|
+
additionalProperties: false,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const filteredObject: NodeType = {
|
|
264
|
+
type: "object",
|
|
265
|
+
properties: { bar: { node: { type: "string" }, required: false } },
|
|
266
|
+
additionalProperties: false,
|
|
267
|
+
};
|
|
268
|
+
const operation = "Pick";
|
|
269
|
+
const properties = new Set(["bar"]);
|
|
270
|
+
|
|
271
|
+
const result = applyPickOrOmitToNodeType(baseObject, operation, properties);
|
|
272
|
+
|
|
273
|
+
expect(result).toStrictEqual(filteredObject);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("Pick - no matching properties returns undefined", () => {
|
|
277
|
+
const baseObject: NodeType = {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties: { foo: { node: { type: "string" }, required: false } },
|
|
280
|
+
additionalProperties: false,
|
|
281
|
+
};
|
|
282
|
+
const result = applyPickOrOmitToNodeType(
|
|
283
|
+
baseObject,
|
|
284
|
+
"Pick",
|
|
285
|
+
new Set(["bar"]),
|
|
286
|
+
);
|
|
287
|
+
expect(result).toBeUndefined();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("Omit - all properties with additionalProperties false returns undefined", () => {
|
|
291
|
+
const baseObject: NodeType = {
|
|
292
|
+
type: "object",
|
|
293
|
+
properties: { foo: { node: { type: "string" }, required: false } },
|
|
294
|
+
additionalProperties: false,
|
|
295
|
+
};
|
|
296
|
+
const result = applyPickOrOmitToNodeType(
|
|
297
|
+
baseObject,
|
|
298
|
+
"Omit",
|
|
299
|
+
new Set(["foo"]),
|
|
300
|
+
);
|
|
301
|
+
expect(result).toBeUndefined();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("and type - applies to each member and returns and", () => {
|
|
305
|
+
const baseObject: NodeType = {
|
|
306
|
+
type: "and",
|
|
307
|
+
and: [
|
|
308
|
+
{
|
|
309
|
+
type: "object",
|
|
310
|
+
properties: {
|
|
311
|
+
a: { node: { type: "string" }, required: false },
|
|
312
|
+
b: { node: { type: "string" }, required: false },
|
|
313
|
+
},
|
|
314
|
+
additionalProperties: false,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
type: "object",
|
|
318
|
+
properties: {
|
|
319
|
+
b: { node: { type: "string" }, required: false },
|
|
320
|
+
c: { node: { type: "string" }, required: false },
|
|
321
|
+
},
|
|
322
|
+
additionalProperties: false,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
};
|
|
326
|
+
const result = applyPickOrOmitToNodeType(
|
|
327
|
+
baseObject,
|
|
328
|
+
"Pick",
|
|
329
|
+
new Set(["b"]),
|
|
330
|
+
);
|
|
331
|
+
expect(result).toMatchObject({ type: "and" });
|
|
332
|
+
const and = (result as { and: NodeType[] }).and;
|
|
333
|
+
expect(and).toHaveLength(2);
|
|
334
|
+
expect(and[0]).toMatchObject({
|
|
335
|
+
type: "object",
|
|
336
|
+
properties: { b: { node: { type: "string" }, required: false } },
|
|
337
|
+
});
|
|
338
|
+
expect(and[1]).toMatchObject({
|
|
339
|
+
type: "object",
|
|
340
|
+
properties: { b: { node: { type: "string" }, required: false } },
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("or type - applies to each member and returns or", () => {
|
|
345
|
+
const baseObject: NodeType = {
|
|
346
|
+
type: "or",
|
|
347
|
+
or: [
|
|
348
|
+
{
|
|
349
|
+
type: "object",
|
|
350
|
+
properties: {
|
|
351
|
+
x: { node: { type: "string" }, required: false },
|
|
352
|
+
y: { node: { type: "string" }, required: false },
|
|
353
|
+
},
|
|
354
|
+
additionalProperties: false,
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
const result = applyPickOrOmitToNodeType(
|
|
359
|
+
baseObject,
|
|
360
|
+
"Pick",
|
|
361
|
+
new Set(["x"]),
|
|
362
|
+
);
|
|
363
|
+
expect(result).toMatchObject({
|
|
364
|
+
type: "object",
|
|
365
|
+
properties: { x: { node: { type: "string" }, required: false } },
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("or type with multiple members returns single or", () => {
|
|
370
|
+
const baseObject: NodeType = {
|
|
371
|
+
type: "or",
|
|
372
|
+
or: [
|
|
373
|
+
{
|
|
374
|
+
type: "object",
|
|
375
|
+
properties: { a: { node: { type: "string" }, required: false } },
|
|
376
|
+
additionalProperties: false,
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: { b: { node: { type: "string" }, required: false } },
|
|
381
|
+
additionalProperties: false,
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
};
|
|
385
|
+
const result = applyPickOrOmitToNodeType(
|
|
386
|
+
baseObject,
|
|
387
|
+
"Pick",
|
|
388
|
+
new Set(["a", "b"]),
|
|
389
|
+
);
|
|
390
|
+
expect(result).toMatchObject({ type: "or", or: expect.any(Array) });
|
|
391
|
+
expect((result as { or: NodeType[] }).or.length).toBe(2);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("throws when applying Pick/Omit to non-object non-union non-intersection", () => {
|
|
395
|
+
const baseObject: NodeType = { type: "string" };
|
|
396
|
+
expect(() =>
|
|
397
|
+
applyPickOrOmitToNodeType(baseObject, "Pick", new Set(["x"])),
|
|
398
|
+
).toThrow(/Can not apply Pick to type string/);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
describe("applyPartialOrRequiredToNodeType", () => {
|
|
403
|
+
test("Partial - Makes required properties optional", () => {
|
|
404
|
+
const baseObject: NodeType = {
|
|
405
|
+
type: "object",
|
|
406
|
+
properties: {
|
|
407
|
+
foo: { node: { type: "string" }, required: false },
|
|
408
|
+
bar: { node: { type: "string" }, required: true },
|
|
409
|
+
},
|
|
410
|
+
additionalProperties: false,
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const modifiedObject: NodeType = {
|
|
414
|
+
type: "object",
|
|
415
|
+
properties: {
|
|
416
|
+
foo: { node: { type: "string" }, required: false },
|
|
417
|
+
bar: { node: { type: "string" }, required: false },
|
|
418
|
+
},
|
|
419
|
+
additionalProperties: false,
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const result = applyPartialOrRequiredToNodeType(baseObject, false);
|
|
423
|
+
|
|
424
|
+
expect(result).toStrictEqual(modifiedObject);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("Required - Makes optional properties required", () => {
|
|
428
|
+
const baseObject: NodeType = {
|
|
429
|
+
type: "object",
|
|
430
|
+
properties: {
|
|
431
|
+
foo: { node: { type: "string" }, required: false },
|
|
432
|
+
bar: { node: { type: "string" }, required: true },
|
|
433
|
+
},
|
|
434
|
+
additionalProperties: false,
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const modifiedObject: NodeType = {
|
|
438
|
+
type: "object",
|
|
439
|
+
properties: {
|
|
440
|
+
foo: { node: { type: "string" }, required: true },
|
|
441
|
+
bar: { node: { type: "string" }, required: true },
|
|
442
|
+
},
|
|
443
|
+
additionalProperties: false,
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const result = applyPartialOrRequiredToNodeType(baseObject, true);
|
|
447
|
+
|
|
448
|
+
expect(result).toStrictEqual(modifiedObject);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test("and type - applies modifier to each member", () => {
|
|
452
|
+
const baseObject: NodeType = {
|
|
453
|
+
type: "and",
|
|
454
|
+
and: [
|
|
455
|
+
{
|
|
456
|
+
type: "object",
|
|
457
|
+
properties: { a: { node: { type: "string" }, required: false } },
|
|
458
|
+
additionalProperties: false,
|
|
459
|
+
},
|
|
460
|
+
],
|
|
461
|
+
};
|
|
462
|
+
const result = applyPartialOrRequiredToNodeType(baseObject, true);
|
|
463
|
+
expect(result).toMatchObject({ type: "and" });
|
|
464
|
+
expect((result as { and: NodeType[] }).and[0]).toMatchObject({
|
|
465
|
+
type: "object",
|
|
466
|
+
properties: { a: { required: true, node: { type: "string" } } },
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test("or type - applies modifier to each member", () => {
|
|
471
|
+
const baseObject: NodeType = {
|
|
472
|
+
type: "or",
|
|
473
|
+
or: [
|
|
474
|
+
{
|
|
475
|
+
type: "object",
|
|
476
|
+
properties: { a: { node: { type: "string" }, required: true } },
|
|
477
|
+
additionalProperties: false,
|
|
478
|
+
},
|
|
479
|
+
],
|
|
480
|
+
};
|
|
481
|
+
const result = applyPartialOrRequiredToNodeType(baseObject, false);
|
|
482
|
+
expect(result).toMatchObject({ type: "or" });
|
|
483
|
+
expect((result as { or: NodeType[] }).or[0]).toMatchObject({
|
|
484
|
+
type: "object",
|
|
485
|
+
properties: { a: { required: false, node: { type: "string" } } },
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("throws when applying Partial/Required to non-object non-union non-intersection", () => {
|
|
490
|
+
const baseObject: NodeType = { type: "number" };
|
|
491
|
+
expect(() => applyPartialOrRequiredToNodeType(baseObject, false)).toThrow(
|
|
492
|
+
/Can not apply Partial to type number/,
|
|
493
|
+
);
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
describe("applyExcludeToNodeType", () => {
|
|
498
|
+
test("excludes single type from union", () => {
|
|
499
|
+
const baseObject: OrType = {
|
|
500
|
+
type: "or",
|
|
501
|
+
or: [{ type: "string" }, { type: "number" }, { type: "boolean" }],
|
|
502
|
+
};
|
|
503
|
+
const result = applyExcludeToNodeType(baseObject, { type: "number" });
|
|
504
|
+
expect(result).toMatchObject({
|
|
505
|
+
type: "or",
|
|
506
|
+
or: [{ type: "string" }, { type: "boolean" }],
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("excludes with filter union (or)", () => {
|
|
511
|
+
const baseObject: OrType = {
|
|
512
|
+
type: "or",
|
|
513
|
+
or: [{ type: "string" }, { type: "number" }, { type: "boolean" }],
|
|
514
|
+
};
|
|
515
|
+
const filters: OrType = {
|
|
516
|
+
type: "or",
|
|
517
|
+
or: [{ type: "number" }, { type: "boolean" }],
|
|
518
|
+
};
|
|
519
|
+
const result = applyExcludeToNodeType(baseObject, filters);
|
|
520
|
+
expect(result).toEqual({ type: "string" });
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
test("single remaining member returns that member", () => {
|
|
524
|
+
const baseObject: OrType = {
|
|
525
|
+
type: "or",
|
|
526
|
+
or: [{ type: "string" }, { type: "number" }],
|
|
527
|
+
};
|
|
528
|
+
const result = applyExcludeToNodeType(baseObject, { type: "number" });
|
|
529
|
+
expect(result).toEqual({ type: "string" });
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("multiple remaining members returns or", () => {
|
|
533
|
+
const baseObject: OrType = {
|
|
534
|
+
type: "or",
|
|
535
|
+
or: [{ type: "string" }, { type: "number" }, { type: "boolean" }],
|
|
536
|
+
};
|
|
537
|
+
const result = applyExcludeToNodeType(baseObject, { type: "number" });
|
|
538
|
+
expect(result).toMatchObject({
|
|
539
|
+
type: "or",
|
|
540
|
+
or: [{ type: "string" }, { type: "boolean" }],
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
});
|
package/src/index.ts
CHANGED
package/src/type-checks.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
1
|
import type {
|
|
3
2
|
NamedType,
|
|
4
3
|
NamedTypeWithGenerics,
|
|
@@ -16,77 +15,6 @@ import type {
|
|
|
16
15
|
RecordType,
|
|
17
16
|
} from "@xlr-lib/xlr";
|
|
18
17
|
|
|
19
|
-
/**
|
|
20
|
-
* Returns if the Object Property is optional
|
|
21
|
-
*/
|
|
22
|
-
export function isOptionalProperty(node: ts.PropertySignature): boolean {
|
|
23
|
-
return node.questionToken?.kind === ts.SyntaxKind.QuestionToken;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Returns if the node is an Interface or Type with Generics
|
|
28
|
-
*/
|
|
29
|
-
export function isGenericInterfaceDeclaration(
|
|
30
|
-
node: ts.InterfaceDeclaration,
|
|
31
|
-
): boolean {
|
|
32
|
-
const length = node.typeParameters?.length;
|
|
33
|
-
return length ? length > 0 : false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Returns if the node is an Type Declaration with Generics
|
|
38
|
-
*/
|
|
39
|
-
export function isGenericTypeDeclaration(
|
|
40
|
-
node: ts.TypeAliasDeclaration,
|
|
41
|
-
): boolean {
|
|
42
|
-
const length = node.typeParameters?.length;
|
|
43
|
-
return length ? length > 0 : false;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Returns if the referenced type is a generic
|
|
48
|
-
*/
|
|
49
|
-
export function isTypeReferenceGeneric(
|
|
50
|
-
node: ts.TypeReferenceNode,
|
|
51
|
-
typeChecker: ts.TypeChecker,
|
|
52
|
-
): boolean {
|
|
53
|
-
const symbol = typeChecker.getSymbolAtLocation(node.typeName);
|
|
54
|
-
if (symbol && symbol.declarations) {
|
|
55
|
-
return symbol.declarations[0].kind === ts.SyntaxKind.TypeParameter;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export type TopLevelDeclaration =
|
|
62
|
-
| ts.InterfaceDeclaration
|
|
63
|
-
| ts.TypeAliasDeclaration;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Returns if the node is an interface or a type declaration
|
|
67
|
-
*/
|
|
68
|
-
export function isTopLevelDeclaration(
|
|
69
|
-
node: ts.Node,
|
|
70
|
-
): node is TopLevelDeclaration {
|
|
71
|
-
return (
|
|
72
|
-
node.kind === ts.SyntaxKind.InterfaceDeclaration ||
|
|
73
|
-
node.kind === ts.SyntaxKind.TypeAliasDeclaration
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export type TopLevelNode = TopLevelDeclaration | ts.VariableStatement;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Returns if the node is an interface or a type declaration
|
|
81
|
-
*/
|
|
82
|
-
export function isTopLevelNode(node: ts.Node): node is TopLevelNode {
|
|
83
|
-
return (
|
|
84
|
-
node.kind === ts.SyntaxKind.InterfaceDeclaration ||
|
|
85
|
-
node.kind === ts.SyntaxKind.TypeAliasDeclaration ||
|
|
86
|
-
node.kind === ts.SyntaxKind.VariableStatement
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
18
|
/**
|
|
91
19
|
* Returns if the NodeType has generic tokens
|
|
92
20
|
*/
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
RefNode,
|
|
7
7
|
} from "@xlr-lib/xlr";
|
|
8
8
|
import { isGenericNodeType, isPrimitiveTypeNode } from "./type-checks";
|
|
9
|
-
import { fillInGenerics } from "./
|
|
9
|
+
import { fillInGenerics } from "./xlr-helpers";
|
|
10
10
|
|
|
11
11
|
export interface PropertyNode {
|
|
12
12
|
/** Equivalent Property Name */
|
|
@@ -20,14 +20,8 @@ export interface PropertyNode {
|
|
|
20
20
|
* Takes a property node and returns the underlying Key/Value Pairs
|
|
21
21
|
*/
|
|
22
22
|
export function propertyToTuple(node: Node): PropertyNode {
|
|
23
|
-
let key = node.children?.[0].value as string;
|
|
24
|
-
if (key.includes("-")) {
|
|
25
|
-
// XLR outputs all escaped properties with single quotes
|
|
26
|
-
key = `'${key}'`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
23
|
return {
|
|
30
|
-
key,
|
|
24
|
+
key: node.children?.[0].value as string,
|
|
31
25
|
value: node.children?.[1] as Node,
|
|
32
26
|
};
|
|
33
27
|
}
|