@player-tools/xlr-sdk 0.2.1-next.4 → 0.2.1
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/index.cjs.js +236 -162
- package/dist/index.d.ts +66 -6
- package/dist/index.esm.js +237 -145
- package/package.json +4 -4
- package/src/registry/basic-registry.ts +0 -1
- package/src/sdk.ts +152 -27
- package/src/utils.ts +104 -43
- package/src/validator.ts +53 -76
package/src/sdk.ts
CHANGED
|
@@ -2,10 +2,15 @@ import type {
|
|
|
2
2
|
Manifest,
|
|
3
3
|
NamedType,
|
|
4
4
|
NodeType,
|
|
5
|
+
ObjectType,
|
|
5
6
|
TransformFunction,
|
|
6
7
|
TSManifest,
|
|
7
8
|
} from '@player-tools/xlr';
|
|
8
9
|
import type { TopLevelDeclaration } from '@player-tools/xlr-utils';
|
|
10
|
+
import {
|
|
11
|
+
computeEffectiveObject,
|
|
12
|
+
resolveReferenceNode,
|
|
13
|
+
} from '@player-tools/xlr-utils';
|
|
9
14
|
import { fillInGenerics } from '@player-tools/xlr-utils';
|
|
10
15
|
import type { Node } from 'jsonc-parser';
|
|
11
16
|
import { TSWriter } from '@player-tools/xlr-converters';
|
|
@@ -17,6 +22,12 @@ import type { XLRRegistry, Filters } from './registry';
|
|
|
17
22
|
import { BasicXLRRegistry } from './registry';
|
|
18
23
|
import type { ExportTypes } from './types';
|
|
19
24
|
import { XLRValidator } from './validator';
|
|
25
|
+
import { simpleTransformGenerator } from './utils';
|
|
26
|
+
|
|
27
|
+
export interface GetTypeOptions {
|
|
28
|
+
/** Resolves `extends` fields in objects */
|
|
29
|
+
getRawType?: boolean;
|
|
30
|
+
}
|
|
20
31
|
|
|
21
32
|
/**
|
|
22
33
|
* Abstraction for interfacing with XLRs making it more approachable to use without understanding the inner workings of the types and how they are packaged
|
|
@@ -28,10 +39,17 @@ export class XLRSDK {
|
|
|
28
39
|
|
|
29
40
|
constructor(customRegistry?: XLRRegistry) {
|
|
30
41
|
this.registry = customRegistry ?? new BasicXLRRegistry();
|
|
31
|
-
this.validator = new XLRValidator(this.
|
|
42
|
+
this.validator = new XLRValidator(this.getType.bind(this));
|
|
32
43
|
this.tsWriter = new TSWriter();
|
|
33
44
|
}
|
|
34
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Loads definitions from a path on the filesystem
|
|
48
|
+
*
|
|
49
|
+
* @param inputPath - path to the directory to load (above the xlr folder)
|
|
50
|
+
* @param filters - Any filters to apply when loading the types (a positive match will omit)
|
|
51
|
+
* @param transforms - any transforms to apply to the types being loaded
|
|
52
|
+
*/
|
|
35
53
|
public loadDefinitionsFromDisk(
|
|
36
54
|
inputPath: string,
|
|
37
55
|
filters?: Omit<Filters, 'pluginFilter'>,
|
|
@@ -66,24 +84,34 @@ export class XLRSDK {
|
|
|
66
84
|
)
|
|
67
85
|
.toString()
|
|
68
86
|
);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
const effectiveType =
|
|
88
|
+
transforms?.reduce(
|
|
89
|
+
(typeAccumulator: NamedType<NodeType>, transformFn) =>
|
|
90
|
+
transformFn(
|
|
91
|
+
typeAccumulator,
|
|
92
|
+
capabilityName
|
|
93
|
+
) as NamedType<NodeType>,
|
|
94
|
+
cType
|
|
95
|
+
) ?? cType;
|
|
96
|
+
|
|
97
|
+
this.registry.add(effectiveType, manifest.pluginName, capabilityName);
|
|
72
98
|
}
|
|
73
99
|
});
|
|
74
100
|
});
|
|
75
101
|
}
|
|
76
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Load definitions from a js/ts file in memory
|
|
105
|
+
*
|
|
106
|
+
* @param manifest - The imported XLR manifest module
|
|
107
|
+
* @param filters - Any filters to apply when loading the types (a positive match will omit)
|
|
108
|
+
* @param transforms - any transforms to apply to the types being loaded
|
|
109
|
+
*/
|
|
77
110
|
public async loadDefinitionsFromModule(
|
|
78
|
-
|
|
111
|
+
manifest: TSManifest,
|
|
79
112
|
filters?: Omit<Filters, 'pluginFilter'>,
|
|
80
113
|
transforms?: Array<TransformFunction>
|
|
81
114
|
) {
|
|
82
|
-
const importManifest = await import(
|
|
83
|
-
path.join(inputPath, 'xlr', 'manifest.js')
|
|
84
|
-
);
|
|
85
|
-
const manifest = importManifest.default as TSManifest;
|
|
86
|
-
|
|
87
115
|
Object.keys(manifest.capabilities)?.forEach((capabilityName) => {
|
|
88
116
|
if (
|
|
89
117
|
filters?.capabilityFilter &&
|
|
@@ -96,30 +124,82 @@ export class XLRSDK {
|
|
|
96
124
|
!filters?.typeFilter ||
|
|
97
125
|
!extension.name.match(filters?.typeFilter)
|
|
98
126
|
) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
127
|
+
const effectiveType =
|
|
128
|
+
transforms?.reduce(
|
|
129
|
+
(typeAccumulator: NamedType<NodeType>, transformFn) =>
|
|
130
|
+
transformFn(
|
|
131
|
+
typeAccumulator,
|
|
132
|
+
capabilityName
|
|
133
|
+
) as NamedType<NodeType>,
|
|
134
|
+
extension
|
|
135
|
+
) ?? extension;
|
|
136
|
+
|
|
137
|
+
this.registry.add(effectiveType, manifest.pluginName, capabilityName);
|
|
104
138
|
}
|
|
105
139
|
});
|
|
106
140
|
});
|
|
107
141
|
}
|
|
108
142
|
|
|
109
|
-
|
|
110
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Returns a Type that has been previously loaded
|
|
145
|
+
*
|
|
146
|
+
* @param id - Type to retrieve
|
|
147
|
+
* @param options - `GetTypeOptions`
|
|
148
|
+
* @returns `NamedType<NodeType>` | `undefined`
|
|
149
|
+
*/
|
|
150
|
+
public getType(
|
|
151
|
+
id: string,
|
|
152
|
+
options?: GetTypeOptions
|
|
153
|
+
): NamedType<NodeType> | undefined {
|
|
154
|
+
let type = this.registry.get(id);
|
|
155
|
+
if (options?.getRawType === true || !type) {
|
|
156
|
+
return type;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type = this.resolveType(type);
|
|
160
|
+
|
|
161
|
+
return fillInGenerics(type) as NamedType;
|
|
111
162
|
}
|
|
112
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Returns if a Type with `id` has been loaded into the DSK
|
|
166
|
+
*
|
|
167
|
+
* @param id - Type to retrieve
|
|
168
|
+
* @returns `boolean`
|
|
169
|
+
*/
|
|
113
170
|
public hasType(id: string) {
|
|
114
171
|
return this.registry.has(id);
|
|
115
172
|
}
|
|
116
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Lists types that have been loaded into the SDK
|
|
176
|
+
*
|
|
177
|
+
* @param filters - Any filters to apply to the types returned (a positive match will omit)
|
|
178
|
+
* @returns `Array<NamedTypes>`
|
|
179
|
+
*/
|
|
117
180
|
public listTypes(filters?: Filters) {
|
|
118
181
|
return this.registry.list(filters);
|
|
119
182
|
}
|
|
120
183
|
|
|
121
|
-
|
|
122
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Returns meta information around a registered type
|
|
186
|
+
*
|
|
187
|
+
* @param id - Name of Type to retrieve
|
|
188
|
+
* @returns `TypeMetaData` | `undefined`
|
|
189
|
+
*/
|
|
190
|
+
public getTypeInfo(id: string) {
|
|
191
|
+
return this.registry.info(id);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Validates if a JSONC Node follows the XLR Type registered under the `typeName` specified
|
|
196
|
+
*
|
|
197
|
+
* @param typeName - Registered XLR Type to use for validation
|
|
198
|
+
* @param rootNode - Node to validate
|
|
199
|
+
* @returns `Array<ValidationErrors>`
|
|
200
|
+
*/
|
|
201
|
+
public validateByName(typeName: string, rootNode: Node) {
|
|
202
|
+
const xlr = this.getType(typeName, { getRawType: true });
|
|
123
203
|
if (!xlr) {
|
|
124
204
|
throw new Error(
|
|
125
205
|
`Type ${typeName} does not exist in registry, can't validate`
|
|
@@ -129,6 +209,17 @@ export class XLRSDK {
|
|
|
129
209
|
return this.validator.validateType(rootNode, xlr);
|
|
130
210
|
}
|
|
131
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Validates if a JSONC Node follows the supplied XLR Type
|
|
214
|
+
*
|
|
215
|
+
* @param type - Type to validate against
|
|
216
|
+
* @param rootNode - Node to validate
|
|
217
|
+
* @returns `Array<ValidationErrors>`
|
|
218
|
+
*/
|
|
219
|
+
public validateByType(type: NodeType, rootNode: Node) {
|
|
220
|
+
return this.validator.validateType(rootNode, type);
|
|
221
|
+
}
|
|
222
|
+
|
|
132
223
|
/**
|
|
133
224
|
* Exports the types loaded into the registry to the specified format
|
|
134
225
|
*
|
|
@@ -145,13 +236,18 @@ export class XLRSDK {
|
|
|
145
236
|
transforms?: Array<TransformFunction>
|
|
146
237
|
): [string, string][] {
|
|
147
238
|
const typesToExport = this.registry.list(filters).map((type) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
239
|
+
const resolvedType = this.resolveType(type);
|
|
240
|
+
const effectiveType =
|
|
241
|
+
transforms?.reduce(
|
|
242
|
+
(typeAccumulator: NamedType<NodeType>, transformFn) =>
|
|
243
|
+
transformFn(
|
|
244
|
+
typeAccumulator,
|
|
245
|
+
this.registry.info(type.name)?.capability as string
|
|
246
|
+
) as NamedType<NodeType>,
|
|
247
|
+
resolvedType
|
|
248
|
+
) ?? resolvedType;
|
|
249
|
+
|
|
250
|
+
return effectiveType;
|
|
155
251
|
});
|
|
156
252
|
|
|
157
253
|
if (exportType === 'TypeScript') {
|
|
@@ -162,6 +258,36 @@ export class XLRSDK {
|
|
|
162
258
|
throw new Error(`Unknown export format ${exportType}`);
|
|
163
259
|
}
|
|
164
260
|
|
|
261
|
+
private resolveType(type: NodeType) {
|
|
262
|
+
return simpleTransformGenerator('object', 'any', (objectNode) => {
|
|
263
|
+
if (objectNode.extends) {
|
|
264
|
+
const refName = objectNode.extends.ref.split('<')[0];
|
|
265
|
+
let extendedType = this.getType(refName, { getRawType: true });
|
|
266
|
+
if (!extendedType) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Error resolving ${objectNode.name}: can't find extended type ${refName}`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
extendedType = resolveReferenceNode(
|
|
273
|
+
objectNode.extends,
|
|
274
|
+
extendedType as NamedType<ObjectType>
|
|
275
|
+
) as NamedType;
|
|
276
|
+
return {
|
|
277
|
+
...computeEffectiveObject(
|
|
278
|
+
extendedType as ObjectType,
|
|
279
|
+
objectNode as ObjectType,
|
|
280
|
+
false
|
|
281
|
+
),
|
|
282
|
+
name: objectNode.name,
|
|
283
|
+
description: objectNode.description,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return objectNode;
|
|
288
|
+
})(type, 'any') as NamedType;
|
|
289
|
+
}
|
|
290
|
+
|
|
165
291
|
private exportToTypeScript(
|
|
166
292
|
typesToExport: NamedType[],
|
|
167
293
|
importMap: Map<string, string[]>
|
|
@@ -202,7 +328,6 @@ export class XLRSDK {
|
|
|
202
328
|
const applicableImports = imports.filter((i) => referencedImports.has(i));
|
|
203
329
|
resultFile = ts.factory.updateSourceFile(resultFile, [
|
|
204
330
|
ts.factory.createImportDeclaration(
|
|
205
|
-
/* decorators */ undefined,
|
|
206
331
|
/* modifiers */ undefined,
|
|
207
332
|
ts.factory.createImportClause(
|
|
208
333
|
false,
|
package/src/utils.ts
CHANGED
|
@@ -6,6 +6,8 @@ import type {
|
|
|
6
6
|
NodeTypeMap,
|
|
7
7
|
TransformFunction,
|
|
8
8
|
NodeType,
|
|
9
|
+
ObjectProperty,
|
|
10
|
+
RefNode,
|
|
9
11
|
} from '@player-tools/xlr';
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -17,68 +19,127 @@ export function simpleTransformGenerator<
|
|
|
17
19
|
>(
|
|
18
20
|
typeToTransform: T,
|
|
19
21
|
capabilityToTransform: string,
|
|
20
|
-
functionToRun: (input: NodeTypeMap[T]) =>
|
|
22
|
+
functionToRun: (input: NodeTypeMap[T]) => NodeTypeMap[T]
|
|
21
23
|
): TransformFunction {
|
|
22
24
|
/** walker for an XLR tree to touch every node */
|
|
23
25
|
const walker: TransformFunction = (
|
|
24
|
-
|
|
26
|
+
n: NamedType | NodeType,
|
|
25
27
|
capability: string
|
|
26
28
|
) => {
|
|
27
29
|
// Run transform on base node before running on children
|
|
28
30
|
if (capability === capabilityToTransform) {
|
|
31
|
+
let node = { ...n };
|
|
29
32
|
if (node.type === typeToTransform) {
|
|
30
|
-
functionToRun(node as unknown as NodeTypeMap[T]);
|
|
33
|
+
node = functionToRun(node as unknown as NodeTypeMap[T]);
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
if (node.type === 'object') {
|
|
34
|
-
|
|
35
|
-
walker(node, capability);
|
|
36
|
-
}
|
|
37
|
+
const newObjectProperties: Record<string, ObjectProperty> = {};
|
|
37
38
|
|
|
38
39
|
for (const key in node.properties) {
|
|
39
40
|
const value = node.properties[key];
|
|
40
|
-
|
|
41
|
+
newObjectProperties[key] = {
|
|
42
|
+
required: value.required,
|
|
43
|
+
node: walker(value.node, capability),
|
|
44
|
+
};
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
walker(element, capability)
|
|
55
|
-
);
|
|
56
|
-
} else if (node.type === 'tuple') {
|
|
57
|
-
if (node.additionalItems) {
|
|
58
|
-
walker(node.additionalItems, capability);
|
|
59
|
-
}
|
|
47
|
+
return {
|
|
48
|
+
...node,
|
|
49
|
+
properties: { ...newObjectProperties },
|
|
50
|
+
extends: node.extends
|
|
51
|
+
? (walker(node.extends, capability) as RefNode)
|
|
52
|
+
: undefined,
|
|
53
|
+
additionalProperties: node.additionalProperties
|
|
54
|
+
? walker(node.additionalProperties, capability)
|
|
55
|
+
: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
walker(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
if (node.type === 'array') {
|
|
60
|
+
return {
|
|
61
|
+
...node,
|
|
62
|
+
elementType: walker(node.elementType, capability),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (node.type === 'and') {
|
|
67
|
+
return {
|
|
68
|
+
...node,
|
|
69
|
+
and: node.and.map((element) => walker(element, capability)),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (node.type === 'or') {
|
|
74
|
+
return {
|
|
75
|
+
...node,
|
|
76
|
+
or: node.or.map((element) => walker(element, capability)),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (node.type === 'ref') {
|
|
81
|
+
return {
|
|
82
|
+
...node,
|
|
83
|
+
genericArguments: node.genericArguments?.map((arg) =>
|
|
84
|
+
walker(arg, capability)
|
|
85
|
+
),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (node.type === 'tuple') {
|
|
90
|
+
return {
|
|
91
|
+
...node,
|
|
92
|
+
elementTypes: node.elementTypes.map((type) =>
|
|
93
|
+
walker(type, capability)
|
|
94
|
+
),
|
|
95
|
+
additionalItems: node.additionalItems
|
|
96
|
+
? walker(node.additionalItems, capability)
|
|
97
|
+
: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (node.type === 'function') {
|
|
102
|
+
return {
|
|
103
|
+
...node,
|
|
104
|
+
parameters: node.parameters.map((param) => {
|
|
105
|
+
return {
|
|
106
|
+
...param,
|
|
107
|
+
type: walker(param.type, capability),
|
|
108
|
+
default: param.default
|
|
109
|
+
? walker(param.default, capability)
|
|
110
|
+
: undefined,
|
|
111
|
+
};
|
|
112
|
+
}),
|
|
113
|
+
returnType: node.returnType
|
|
114
|
+
? walker(node.returnType, capability)
|
|
115
|
+
: undefined,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (node.type === 'record') {
|
|
120
|
+
return {
|
|
121
|
+
...node,
|
|
122
|
+
keyType: walker(node.keyType, capability),
|
|
123
|
+
valueType: walker(node.valueType, capability),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (node.type === 'conditional') {
|
|
128
|
+
return {
|
|
129
|
+
...node,
|
|
130
|
+
check: {
|
|
131
|
+
left: walker(node.check.left, capability),
|
|
132
|
+
right: walker(node.check.left, capability),
|
|
133
|
+
},
|
|
134
|
+
value: {
|
|
135
|
+
true: walker(node.value.true, capability),
|
|
136
|
+
false: walker(node.value.false, capability),
|
|
137
|
+
},
|
|
138
|
+
};
|
|
80
139
|
}
|
|
81
140
|
}
|
|
141
|
+
|
|
142
|
+
return n;
|
|
82
143
|
};
|
|
83
144
|
|
|
84
145
|
return walker;
|
package/src/validator.ts
CHANGED
|
@@ -13,21 +13,20 @@ import {
|
|
|
13
13
|
makePropertyMap,
|
|
14
14
|
resolveConditional,
|
|
15
15
|
isPrimitiveTypeNode,
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
resolveReferenceNode,
|
|
17
|
+
computeEffectiveObject,
|
|
18
18
|
} from '@player-tools/xlr-utils';
|
|
19
19
|
import type { ValidationError } from './types';
|
|
20
|
-
import type { XLRRegistry } from './registry';
|
|
21
20
|
|
|
22
21
|
/**
|
|
23
22
|
* Validator for XLRs on JSON Nodes
|
|
24
23
|
*/
|
|
25
24
|
export class XLRValidator {
|
|
26
|
-
private
|
|
25
|
+
private resolveType: (id: string) => NamedType<NodeType> | undefined;
|
|
27
26
|
private regexCache: Map<string, RegExp>;
|
|
28
27
|
|
|
29
|
-
constructor(
|
|
30
|
-
this.
|
|
28
|
+
constructor(resolveType: (id: string) => NamedType<NodeType> | undefined) {
|
|
29
|
+
this.resolveType = resolveType;
|
|
31
30
|
this.regexCache = new Map();
|
|
32
31
|
}
|
|
33
32
|
|
|
@@ -74,7 +73,10 @@ export class XLRValidator {
|
|
|
74
73
|
message: `Does not match any of the expected types for type: '${xlrNode.name}'`,
|
|
75
74
|
});
|
|
76
75
|
} else if (xlrNode.type === 'and') {
|
|
77
|
-
const effectiveType =
|
|
76
|
+
const effectiveType = {
|
|
77
|
+
...this.computeIntersectionType(xlrNode.and),
|
|
78
|
+
...(xlrNode.name ? { name: xlrNode.name } : {}),
|
|
79
|
+
};
|
|
78
80
|
validationIssues.push(...this.validateType(rootNode, effectiveType));
|
|
79
81
|
} else if (xlrNode.type === 'record') {
|
|
80
82
|
rootNode.children?.forEach((child) => {
|
|
@@ -120,14 +122,35 @@ export class XLRValidator {
|
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
} else if (xlrNode.type === 'conditional') {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
// Resolve RefNodes in check conditions if needed
|
|
126
|
+
let { right, left } = xlrNode.check;
|
|
127
|
+
|
|
128
|
+
if (right.type === 'ref') {
|
|
129
|
+
right = this.getRefType(right);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (left.type === 'ref') {
|
|
133
|
+
left = this.getRefType(left);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const resolvedXLRNode = {
|
|
137
|
+
...xlrNode,
|
|
138
|
+
check: {
|
|
139
|
+
left,
|
|
140
|
+
right,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const resolvedConditional = resolveConditional(resolvedXLRNode);
|
|
145
|
+
if (resolvedConditional === resolvedXLRNode) {
|
|
125
146
|
throw Error(
|
|
126
147
|
`Unable to resolve conditional type at runtime: ${xlrNode.name}`
|
|
127
148
|
);
|
|
128
149
|
}
|
|
129
150
|
|
|
130
|
-
validationIssues.push(
|
|
151
|
+
validationIssues.push(
|
|
152
|
+
...this.validateType(rootNode, resolvedConditional)
|
|
153
|
+
);
|
|
131
154
|
} else {
|
|
132
155
|
throw Error(`Unknown type ${xlrNode.type}`);
|
|
133
156
|
}
|
|
@@ -174,7 +197,7 @@ export class XLRValidator {
|
|
|
174
197
|
|
|
175
198
|
if (xlrNode.extends) {
|
|
176
199
|
const extendedNode = this.getRefType(xlrNode.extends) as ObjectType;
|
|
177
|
-
effectiveXLRNode =
|
|
200
|
+
effectiveXLRNode = computeEffectiveObject(extendedNode, xlrNode);
|
|
178
201
|
}
|
|
179
202
|
|
|
180
203
|
// eslint-disable-next-line guard-for-in, no-restricted-syntax
|
|
@@ -233,27 +256,22 @@ export class XLRValidator {
|
|
|
233
256
|
}
|
|
234
257
|
|
|
235
258
|
return typeof literalType.value === 'boolean';
|
|
236
|
-
break;
|
|
237
259
|
case 'number':
|
|
238
260
|
if (expectedType.const) {
|
|
239
261
|
return expectedType.const === literalType.value;
|
|
240
262
|
}
|
|
241
263
|
|
|
242
264
|
return typeof literalType.value === 'number';
|
|
243
|
-
break;
|
|
244
265
|
case 'string':
|
|
245
266
|
if (expectedType.const) {
|
|
246
267
|
return expectedType.const === literalType.value;
|
|
247
268
|
}
|
|
248
269
|
|
|
249
270
|
return typeof literalType.value === 'string';
|
|
250
|
-
break;
|
|
251
271
|
case 'null':
|
|
252
272
|
return literalType.value === 'null';
|
|
253
|
-
break;
|
|
254
273
|
case 'never':
|
|
255
274
|
return literalType === undefined;
|
|
256
|
-
break;
|
|
257
275
|
case 'any':
|
|
258
276
|
return literalType !== undefined;
|
|
259
277
|
case 'unknown':
|
|
@@ -267,27 +285,16 @@ export class XLRValidator {
|
|
|
267
285
|
|
|
268
286
|
private getRefType(ref: RefType): NodeType {
|
|
269
287
|
let refName = ref.ref;
|
|
270
|
-
const { genericArguments } = ref;
|
|
271
|
-
|
|
272
288
|
if (refName.indexOf('<') > 0) {
|
|
273
289
|
[refName] = refName.split('<');
|
|
274
290
|
}
|
|
275
291
|
|
|
276
|
-
const actualType = this.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
// Compose first level generics here since `fillInGenerics` won't process them if a map is passed in
|
|
280
|
-
if (genericArguments && isGenericNodeType(actualType)) {
|
|
281
|
-
actualType.genericTokens.forEach((token, index) => {
|
|
282
|
-
genericMap.set(
|
|
283
|
-
token.symbol,
|
|
284
|
-
genericArguments[index] ?? token.default ?? token.constraints
|
|
285
|
-
);
|
|
286
|
-
});
|
|
292
|
+
const actualType = this.resolveType(refName);
|
|
293
|
+
if (!actualType) {
|
|
294
|
+
throw new Error(`Error: can't resolve type reference ${refName}`);
|
|
287
295
|
}
|
|
288
296
|
|
|
289
|
-
|
|
290
|
-
return fillInGenerics(actualType, genericMap);
|
|
297
|
+
return resolveReferenceNode(ref, actualType);
|
|
291
298
|
}
|
|
292
299
|
|
|
293
300
|
private getRegex(expString: string): RegExp {
|
|
@@ -310,6 +317,12 @@ export class XLRValidator {
|
|
|
310
317
|
|
|
311
318
|
if (firstElement.type === 'and') {
|
|
312
319
|
effectiveType = this.computeIntersectionType(firstElement.and);
|
|
320
|
+
} else if (firstElement.type === 'record') {
|
|
321
|
+
effectiveType = {
|
|
322
|
+
type: 'object',
|
|
323
|
+
properties: {},
|
|
324
|
+
additionalProperties: firstElement.valueType,
|
|
325
|
+
};
|
|
313
326
|
} else if (firstElement.type !== 'or' && firstElement.type !== 'object') {
|
|
314
327
|
throw new Error(
|
|
315
328
|
`Can't compute a union with a non-object type ${firstElement.type} (${firstElement.name})`
|
|
@@ -321,6 +334,14 @@ export class XLRValidator {
|
|
|
321
334
|
types.slice(1).forEach((type) => {
|
|
322
335
|
let typeToApply = type;
|
|
323
336
|
|
|
337
|
+
if (typeToApply.type === 'record') {
|
|
338
|
+
typeToApply = {
|
|
339
|
+
type: 'object',
|
|
340
|
+
properties: {},
|
|
341
|
+
additionalProperties: typeToApply.valueType,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
324
345
|
if (type.type === 'ref') {
|
|
325
346
|
typeToApply = this.getRefType(type);
|
|
326
347
|
}
|
|
@@ -331,10 +352,7 @@ export class XLRValidator {
|
|
|
331
352
|
|
|
332
353
|
if (typeToApply.type === 'object') {
|
|
333
354
|
if (effectiveType.type === 'object') {
|
|
334
|
-
effectiveType =
|
|
335
|
-
effectiveType,
|
|
336
|
-
typeToApply
|
|
337
|
-
);
|
|
355
|
+
effectiveType = computeEffectiveObject(effectiveType, typeToApply);
|
|
338
356
|
} else {
|
|
339
357
|
effectiveType = {
|
|
340
358
|
...effectiveType,
|
|
@@ -363,45 +381,4 @@ export class XLRValidator {
|
|
|
363
381
|
|
|
364
382
|
return effectiveType;
|
|
365
383
|
}
|
|
366
|
-
|
|
367
|
-
private computeEffectiveObject(
|
|
368
|
-
base: ObjectType,
|
|
369
|
-
operand: ObjectType,
|
|
370
|
-
errorOnOverlap = true
|
|
371
|
-
): ObjectType {
|
|
372
|
-
const baseObjectName = base.name ?? 'object literal';
|
|
373
|
-
const operandObjectName = operand.name ?? 'object literal';
|
|
374
|
-
const newObject = {
|
|
375
|
-
...base,
|
|
376
|
-
name: `${baseObjectName} & ${operandObjectName}`,
|
|
377
|
-
description: `Effective type combining ${baseObjectName} and ${operandObjectName}`,
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
|
381
|
-
for (const property in operand.properties) {
|
|
382
|
-
if (
|
|
383
|
-
newObject.properties[property] !== undefined &&
|
|
384
|
-
newObject.properties[property].node.type !==
|
|
385
|
-
operand.properties[property].node.type &&
|
|
386
|
-
errorOnOverlap
|
|
387
|
-
) {
|
|
388
|
-
throw new Error(
|
|
389
|
-
`Can't compute effective type for ${baseObjectName} and ${operandObjectName} because of conflicting properties ${property}`
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
newObject.properties[property] = operand.properties[property];
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (newObject.additionalProperties && operand.additionalProperties) {
|
|
397
|
-
newObject.additionalProperties = {
|
|
398
|
-
type: 'and',
|
|
399
|
-
and: [newObject.additionalProperties, operand.additionalProperties],
|
|
400
|
-
};
|
|
401
|
-
} else if (operand.additionalProperties) {
|
|
402
|
-
newObject.additionalProperties = operand.additionalProperties;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return newObject;
|
|
406
|
-
}
|
|
407
384
|
}
|