cddl2ts 0.3.0 → 0.4.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/.release-it.ts +14 -0
- package/LICENSE +1 -1
- package/README.md +5 -9
- package/changelogithub.config.ts +6 -0
- package/cli-examples/local.ts +1013 -0
- package/cli-examples/remote.ts +1520 -0
- package/cli-examples/tsconfig.json +12 -0
- package/package.json +18 -34
- package/src/cli.ts +42 -0
- package/src/constants.ts +6 -0
- package/src/index.ts +652 -0
- package/src/utils.ts +61 -0
- package/tests/__snapshots__/group_choice.test.ts.snap +72 -0
- package/tests/__snapshots__/literals.test.ts.snap +8 -0
- package/tests/__snapshots__/mixin_union.test.ts.snap +19 -0
- package/tests/__snapshots__/mod.test.ts.snap +102 -0
- package/tests/__snapshots__/repro_proxy.test.ts.snap +14 -0
- package/tests/__snapshots__/union_extension.test.ts.snap +18 -0
- package/tests/__snapshots__/webdriver_local.test.ts.snap +1017 -0
- package/tests/__snapshots__/webdriver_remote.test.ts.snap +1524 -0
- package/tests/complex_types.test.ts +54 -0
- package/tests/group_choice.test.ts +53 -0
- package/tests/literals.test.ts +45 -0
- package/tests/mixin_union.test.ts +56 -0
- package/tests/mod.test.ts +74 -0
- package/tests/named_group_choice.test.ts +47 -0
- package/tests/transform.test.ts +21 -0
- package/tests/unknown.test.ts +51 -0
- package/tests/webdriver_local.test.ts +43 -0
- package/tests/webdriver_remote.test.ts +43 -0
- package/tsconfig.json +11 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
import camelcase from 'camelcase'
|
|
2
|
+
import { parse, print, types } from 'recast'
|
|
3
|
+
import typescriptParser from 'recast/parsers/typescript.js'
|
|
4
|
+
|
|
5
|
+
import type { Assignment, PropertyType, PropertyReference, Property, Array, Operator } from 'cddl'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
isCDDLArray,
|
|
9
|
+
isGroup,
|
|
10
|
+
isNamedGroupReference,
|
|
11
|
+
isLiteralWithValue,
|
|
12
|
+
isNativeTypeWithOperator,
|
|
13
|
+
isUnNamedProperty,
|
|
14
|
+
isPropertyReference,
|
|
15
|
+
isRange,
|
|
16
|
+
isVariable,
|
|
17
|
+
pascalCase
|
|
18
|
+
} from './utils.js'
|
|
19
|
+
|
|
20
|
+
import { pkg } from './constants.js'
|
|
21
|
+
|
|
22
|
+
const b = types.builders
|
|
23
|
+
const NATIVE_TYPES: Record<string, any> = {
|
|
24
|
+
any: b.tsAnyKeyword(),
|
|
25
|
+
number: b.tsNumberKeyword(),
|
|
26
|
+
int: b.tsNumberKeyword(),
|
|
27
|
+
float: b.tsNumberKeyword(),
|
|
28
|
+
uint: b.tsNumberKeyword(),
|
|
29
|
+
bool: b.tsBooleanKeyword(),
|
|
30
|
+
str: b.tsStringKeyword(),
|
|
31
|
+
text: b.tsStringKeyword(),
|
|
32
|
+
tstr: b.tsStringKeyword(),
|
|
33
|
+
range: b.tsNumberKeyword(),
|
|
34
|
+
nil: b.tsNullKeyword(),
|
|
35
|
+
null: b.tsNullKeyword()
|
|
36
|
+
}
|
|
37
|
+
type ObjectEntry = types.namedTypes.TSCallSignatureDeclaration | types.namedTypes.TSConstructSignatureDeclaration | types.namedTypes.TSIndexSignature | types.namedTypes.TSMethodSignature | types.namedTypes.TSPropertySignature
|
|
38
|
+
type ObjectBody = ObjectEntry[]
|
|
39
|
+
type TSTypeKind = types.namedTypes.TSAsExpression['typeAnnotation']
|
|
40
|
+
|
|
41
|
+
export interface TransformOptions {
|
|
42
|
+
useUnknown?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function transform (assignments: Assignment[], options?: TransformOptions) {
|
|
46
|
+
if (options?.useUnknown) {
|
|
47
|
+
NATIVE_TYPES.any = b.tsUnknownKeyword()
|
|
48
|
+
} else {
|
|
49
|
+
NATIVE_TYPES.any = b.tsAnyKeyword()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let ast = parse(
|
|
53
|
+
`// compiled with https://www.npmjs.com/package/cddl2ts v${pkg.version}`,
|
|
54
|
+
{
|
|
55
|
+
parser: typescriptParser,
|
|
56
|
+
sourceFileName: 'cddl2Ts.ts',
|
|
57
|
+
sourceRoot: process.cwd()
|
|
58
|
+
}
|
|
59
|
+
) satisfies types.namedTypes.File
|
|
60
|
+
|
|
61
|
+
for (const assignment of assignments) {
|
|
62
|
+
const statement = parseAssignment(assignment)
|
|
63
|
+
if (!statement) {
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
ast.program.body.push(statement)
|
|
67
|
+
}
|
|
68
|
+
return print(ast).code
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseAssignment (assignment: Assignment) {
|
|
72
|
+
if (isVariable(assignment)) {
|
|
73
|
+
const propType = Array.isArray(assignment.PropertyType)
|
|
74
|
+
? assignment.PropertyType
|
|
75
|
+
: [assignment.PropertyType]
|
|
76
|
+
|
|
77
|
+
const id = b.identifier(pascalCase(assignment.Name))
|
|
78
|
+
|
|
79
|
+
let typeParameters: any
|
|
80
|
+
// @ts-expect-error e.g. "js-int = -9007199254740991..9007199254740991"
|
|
81
|
+
if (propType.length === 1 && propType[0].Type === 'range') {
|
|
82
|
+
typeParameters = b.tsNumberKeyword()
|
|
83
|
+
} else {
|
|
84
|
+
typeParameters = b.tsUnionType(propType.map(parseUnionType))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const expr = b.tsTypeAliasDeclaration(id, typeParameters)
|
|
88
|
+
expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true))
|
|
89
|
+
return b.exportDeclaration(false, expr)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isGroup(assignment)) {
|
|
93
|
+
const id = b.identifier(pascalCase(assignment.Name))
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if we have choices in the group (arrays of Properties)
|
|
97
|
+
*/
|
|
98
|
+
const properties = assignment.Properties
|
|
99
|
+
const hasChoices = properties.some(p => Array.isArray(p))
|
|
100
|
+
|
|
101
|
+
if (hasChoices) {
|
|
102
|
+
// Flatten static properties and collect choices
|
|
103
|
+
const staticProps: Property[] = []
|
|
104
|
+
const intersections: any[] = []
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < properties.length; i++) {
|
|
107
|
+
const prop = properties[i]
|
|
108
|
+
if (Array.isArray(prop)) {
|
|
109
|
+
// It's a choice (Union)
|
|
110
|
+
// prop is Property[] where each Property is an option
|
|
111
|
+
// CDDL parser appends the last choice element as a subsequent property
|
|
112
|
+
// so we need to grab it and merge it into the union
|
|
113
|
+
const choiceOptions = [...prop]
|
|
114
|
+
const nextProp = properties[i + 1]
|
|
115
|
+
|
|
116
|
+
if (nextProp && !Array.isArray(nextProp)) {
|
|
117
|
+
choiceOptions.push(nextProp)
|
|
118
|
+
i++ // Skip next property
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const options = choiceOptions.map(p => {
|
|
122
|
+
// If p is a group reference (Name ''), it's a TypeReference
|
|
123
|
+
// e.g. SessionAutodetectProxyConfiguration // SessionDirectProxyConfiguration
|
|
124
|
+
// The parser sometimes wraps it in an array, sometimes not (if inside a choice)
|
|
125
|
+
const typeVal = Array.isArray(p.Type) ? p.Type[0] : p.Type
|
|
126
|
+
|
|
127
|
+
if (isUnNamedProperty(p)) {
|
|
128
|
+
// Handle un-named properties (bare types) in choices.
|
|
129
|
+
// Native types / literals
|
|
130
|
+
if (isNamedGroupReference(typeVal)) {
|
|
131
|
+
return b.tsTypeReference(
|
|
132
|
+
b.identifier(pascalCase(typeVal.Value || typeVal.Type))
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
return parseUnionType(typeVal);
|
|
136
|
+
}
|
|
137
|
+
// Otherwise it is an object literal with this property
|
|
138
|
+
return b.tsTypeLiteral(parseObjectType([p]))
|
|
139
|
+
})
|
|
140
|
+
intersections.push(b.tsUnionType(options))
|
|
141
|
+
} else {
|
|
142
|
+
staticProps.push(prop)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (staticProps.length > 0) {
|
|
147
|
+
// Check if we have mixins in static props
|
|
148
|
+
const mixins = staticProps.filter(isUnNamedProperty)
|
|
149
|
+
const ownProps = staticProps.filter(p => !isUnNamedProperty(p))
|
|
150
|
+
|
|
151
|
+
if (ownProps.length > 0) {
|
|
152
|
+
intersections.unshift(b.tsTypeLiteral(parseObjectType(ownProps)))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const mixin of mixins) {
|
|
156
|
+
if (Array.isArray(mixin.Type) && mixin.Type.length > 1) {
|
|
157
|
+
const options = mixin.Type.map(parseUnionType)
|
|
158
|
+
intersections.push(b.tsUnionType(options))
|
|
159
|
+
} else {
|
|
160
|
+
const typeVal = Array.isArray(mixin.Type) ? mixin.Type[0] : mixin.Type
|
|
161
|
+
if (isNamedGroupReference(typeVal)) {
|
|
162
|
+
intersections.push(b.tsTypeReference(
|
|
163
|
+
b.identifier(pascalCase(typeVal.Value))
|
|
164
|
+
))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If only one intersection element, return it directly
|
|
171
|
+
// If multiple, return Intersection
|
|
172
|
+
let value: any
|
|
173
|
+
if (intersections.length === 0) {
|
|
174
|
+
value = b.tsAnyKeyword() // Should not happen for valid CDDL?
|
|
175
|
+
} else if (intersections.length === 1) {
|
|
176
|
+
value = intersections[0]
|
|
177
|
+
} else {
|
|
178
|
+
value = b.tsIntersectionType(intersections)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const expr = b.tsTypeAliasDeclaration(id, value)
|
|
182
|
+
expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true))
|
|
183
|
+
return b.exportDeclaration(false, expr)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const props = properties as Property[]
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* transform CDDL groups like `Extensible = (*text => any)`
|
|
190
|
+
*/
|
|
191
|
+
if (props.length === 1) {
|
|
192
|
+
const prop = props[0]
|
|
193
|
+
const propType = Array.isArray(prop.Type) ? prop.Type : [prop.Type]
|
|
194
|
+
if (propType.length === 1 && Object.keys(NATIVE_TYPES).includes(prop.Name)) {
|
|
195
|
+
const value = parseUnionType(assignment)
|
|
196
|
+
const expr = b.tsTypeAliasDeclaration(id, value)
|
|
197
|
+
expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true))
|
|
198
|
+
return b.exportDeclaration(false, expr)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Check if extended interfaces are likely unions or conflicting types
|
|
203
|
+
// In CDDL, including a group (Name '') that is defined elsewhere as a choice (union)
|
|
204
|
+
// is valid, but TypeScript interfaces cannot extend unions.
|
|
205
|
+
// We can't easily know if the referenced group is a union without a symbol table or 2-pass.
|
|
206
|
+
// However, if we simply use type intersection for ALL group inclusions, it is always safe.
|
|
207
|
+
// (Interface extending Interface is same as Type = Interface & Interface)
|
|
208
|
+
// Let's refactor to use Type Alias with Intersection if there are any mixins.
|
|
209
|
+
|
|
210
|
+
const mixins = props.filter(isUnNamedProperty)
|
|
211
|
+
|
|
212
|
+
if (mixins.length > 0) {
|
|
213
|
+
// It has mixins (extensions). Use type alias with intersection to be safe against unions.
|
|
214
|
+
// Type = (Mixin1 & Mixin2 & { OwnProps })
|
|
215
|
+
|
|
216
|
+
const intersections: any[] = []
|
|
217
|
+
|
|
218
|
+
for (const mixin of mixins) {
|
|
219
|
+
// If mixin is a group choice (e.g. `(A // B)`), the parser returns a Group object
|
|
220
|
+
// with Properties containing the choices. We need to extract them.
|
|
221
|
+
if (Array.isArray(mixin.Type) && mixin.Type.length > 1) {
|
|
222
|
+
// Check if it's a choice of types
|
|
223
|
+
const unionOptions: any[] = []
|
|
224
|
+
for (const t of mixin.Type) {
|
|
225
|
+
let refName: string | undefined
|
|
226
|
+
if (typeof t === 'string') refName = t
|
|
227
|
+
else if (isNamedGroupReference(t)) refName = t.Value
|
|
228
|
+
else refName = (t as any).Value || (t as any).Type
|
|
229
|
+
|
|
230
|
+
if (refName) unionOptions.push(b.tsTypeReference(b.identifier(pascalCase(refName))))
|
|
231
|
+
}
|
|
232
|
+
if (unionOptions.length > 0) {
|
|
233
|
+
intersections.push(b.tsParenthesizedType(b.tsUnionType(unionOptions)))
|
|
234
|
+
continue
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (isGroup(mixin.Type) && Array.isArray(mixin.Type.Properties)) {
|
|
239
|
+
const group = mixin.Type
|
|
240
|
+
const choices: any[] = []
|
|
241
|
+
|
|
242
|
+
for (const prop of group.Properties) {
|
|
243
|
+
// Choices are wrapped in arrays in the properties
|
|
244
|
+
const options = Array.isArray(prop) ? prop : [prop]
|
|
245
|
+
if (options.length > 1) { // It's a choice within the mixin group
|
|
246
|
+
const unionOptions: any[] = []
|
|
247
|
+
for (const option of options) {
|
|
248
|
+
let refName: string | undefined
|
|
249
|
+
const type = option.Type
|
|
250
|
+
if (typeof type === 'string') refName = type
|
|
251
|
+
else if (isNamedGroupReference(type)) refName = type.Value
|
|
252
|
+
else if (Array.isArray(type) && type[0]) {
|
|
253
|
+
const first = type[0]
|
|
254
|
+
if (isNamedGroupReference(first)) refName = first.Value
|
|
255
|
+
else if (isUnNamedProperty(first)) {
|
|
256
|
+
if (isNamedGroupReference(first.Type)) refName = first.Type.Value
|
|
257
|
+
else if (isGroup(first.Type) && first.Type.Properties && first.Type.Properties.length === 1) {
|
|
258
|
+
// Handle case where group reference is wrapped deeply
|
|
259
|
+
const subProp = first.Type.Properties[0] as Property
|
|
260
|
+
if (isNamedGroupReference(subProp.Type)) refName = subProp.Type.Value
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (!refName) refName = (first as any).Value || (first as any).Type
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (refName) unionOptions.push(b.tsTypeReference(b.identifier(pascalCase(refName))))
|
|
267
|
+
}
|
|
268
|
+
if (unionOptions.length > 0) {
|
|
269
|
+
choices.push(b.tsParenthesizedType(b.tsUnionType(unionOptions)))
|
|
270
|
+
continue
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for (const option of options) {
|
|
275
|
+
let refName: string | undefined
|
|
276
|
+
const type = option.Type
|
|
277
|
+
|
|
278
|
+
if (typeof type === 'string') {
|
|
279
|
+
refName = type
|
|
280
|
+
} else if (Array.isArray(type)) {
|
|
281
|
+
if (type.length > 1) {
|
|
282
|
+
// console.log('DEBUG: Found array length > 1', JSON.stringify(type, null, 2))
|
|
283
|
+
const unionChoices: any[] = []
|
|
284
|
+
for (const t of type) {
|
|
285
|
+
let name: string | undefined
|
|
286
|
+
if (typeof t === 'string') name = t
|
|
287
|
+
else if (isNamedGroupReference(t)) name = t.Value
|
|
288
|
+
else if (isUnNamedProperty(t)) {
|
|
289
|
+
if (isNamedGroupReference(t.Type)) name = t.Type.Value
|
|
290
|
+
else if (isGroup(t.Type) && t.Type.Properties && t.Type.Properties.length === 1) {
|
|
291
|
+
const subProp = t.Type.Properties[0] as Property
|
|
292
|
+
if (isNamedGroupReference(subProp.Type)) name = subProp.Type.Value
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (!name) name = (t as any).Value || (t as any).Type
|
|
296
|
+
|
|
297
|
+
if (name) unionChoices.push(b.tsTypeReference(b.identifier(pascalCase(name))))
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (unionChoices.length > 0) {
|
|
301
|
+
choices.push(b.tsParenthesizedType(b.tsUnionType(unionChoices)))
|
|
302
|
+
continue
|
|
303
|
+
}
|
|
304
|
+
} else if (type.length === 1 && Array.isArray(type[0])) {
|
|
305
|
+
// Handle nested union e.g. [ [ A, B ] ] which seems common in some parsers for choices
|
|
306
|
+
const nested = type[0]
|
|
307
|
+
if (nested.length > 1) {
|
|
308
|
+
const unionChoices: any[] = []
|
|
309
|
+
for (const t of nested) {
|
|
310
|
+
let name: string | undefined
|
|
311
|
+
if (typeof t === 'string') name = t
|
|
312
|
+
else if (isNamedGroupReference(t)) name = t.Value
|
|
313
|
+
else if (isUnNamedProperty(t)) {
|
|
314
|
+
if (isNamedGroupReference(t.Type)) name = t.Type.Value
|
|
315
|
+
else if (isGroup(t.Type) && t.Type.Properties && t.Type.Properties.length === 1) {
|
|
316
|
+
const subProp = t.Type.Properties[0] as Property
|
|
317
|
+
if (isNamedGroupReference(subProp.Type)) name = subProp.Type.Value
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
// Fallback for wrapped primitive?
|
|
321
|
+
name = (t.Type as any).Value || (t.Type as any).Type
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (!name) name = (t as any).Value || (t as any).Type
|
|
325
|
+
|
|
326
|
+
if (name) unionChoices.push(b.tsTypeReference(b.identifier(pascalCase(name))))
|
|
327
|
+
}
|
|
328
|
+
if (unionChoices.length > 0) {
|
|
329
|
+
choices.push(b.tsParenthesizedType(b.tsUnionType(unionChoices)))
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const first = type[0]
|
|
336
|
+
if (first) {
|
|
337
|
+
if (isNamedGroupReference(first)) {
|
|
338
|
+
refName = first.Value
|
|
339
|
+
} else if (!isGroup(first)) {
|
|
340
|
+
if (isUnNamedProperty(first)) {
|
|
341
|
+
if (isNamedGroupReference(first.Type)) {
|
|
342
|
+
refName = first.Type.Value
|
|
343
|
+
} else {
|
|
344
|
+
refName = (first.Type as any).Value || (first.Type as any).Type
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
refName = (first as any).Value || (first as any).Type
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
refName = (first as any).Value || (first as any).Type
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} else if (type && typeof type === 'object') {
|
|
354
|
+
if (isGroup(type) && Array.isArray(type.Properties)) {
|
|
355
|
+
choices.push(b.tsTypeLiteral(parseObjectType(type.Properties as Property[])))
|
|
356
|
+
continue
|
|
357
|
+
}
|
|
358
|
+
refName = isNamedGroupReference(type)
|
|
359
|
+
? type.Value || type.Type
|
|
360
|
+
: (type as any).Value || (type as any).Type
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// If we found a refName, push it. Note that if this was a choice we handled above, we skip this
|
|
364
|
+
if (refName) {
|
|
365
|
+
choices.push(
|
|
366
|
+
b.tsTypeReference(b.identifier(pascalCase(refName)))
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (choices.length > 0) {
|
|
373
|
+
// Unions inside intersections must be parenthesized to avoid ambiguity
|
|
374
|
+
// e.g. (A | B) & C vs A | (B & C)
|
|
375
|
+
const union = b.tsUnionType(choices)
|
|
376
|
+
intersections.push(b.tsParenthesizedType(union))
|
|
377
|
+
continue
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const propType = mixin.Type as PropertyType[]
|
|
382
|
+
|
|
383
|
+
if (typeof propType === 'string' && NATIVE_TYPES[propType]) {
|
|
384
|
+
intersections.push(NATIVE_TYPES[propType])
|
|
385
|
+
continue
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const groupRef = propType[0] as PropertyReference
|
|
389
|
+
|
|
390
|
+
// Handle nested inline groups if any (though usually flat here if name is empty?)
|
|
391
|
+
const value = (groupRef?.Value || (groupRef as any)?.Type) as string
|
|
392
|
+
if (value) {
|
|
393
|
+
intersections.push(
|
|
394
|
+
b.tsTypeReference(b.identifier(pascalCase(value)))
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const ownProps = props.filter(p => !isUnNamedProperty(p))
|
|
400
|
+
if (ownProps.length > 0) {
|
|
401
|
+
intersections.push(b.tsTypeLiteral(parseObjectType(ownProps)))
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let value: any
|
|
405
|
+
if (intersections.length === 1) {
|
|
406
|
+
value = intersections[0]
|
|
407
|
+
} else {
|
|
408
|
+
value = b.tsIntersectionType(intersections)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const expr = b.tsTypeAliasDeclaration(id, value)
|
|
412
|
+
expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true))
|
|
413
|
+
return b.exportDeclaration(false, expr)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Fallback to interface if no mixins (pure object)
|
|
417
|
+
const objectType = parseObjectType(props)
|
|
418
|
+
|
|
419
|
+
const expr = b.tsInterfaceDeclaration(id, b.tsInterfaceBody(objectType))
|
|
420
|
+
expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true))
|
|
421
|
+
return b.exportDeclaration(false, expr)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (isCDDLArray(assignment)) {
|
|
425
|
+
const id = b.identifier(pascalCase(assignment.Name))
|
|
426
|
+
|
|
427
|
+
const assignmentValues = assignment.Values[0]
|
|
428
|
+
|
|
429
|
+
if (Array.isArray(assignmentValues)) {
|
|
430
|
+
// It's a choice/union in the array definition
|
|
431
|
+
// e.g. Foo = [ (A | B) ]
|
|
432
|
+
// assignment.Values[0] is Property[] (the choices)
|
|
433
|
+
// We need to parse each choice.
|
|
434
|
+
const obj = assignmentValues.map((prop) => {
|
|
435
|
+
const t = Array.isArray(prop.Type) ? prop.Type[0] : prop.Type
|
|
436
|
+
return parseUnionType(t)
|
|
437
|
+
})
|
|
438
|
+
const value = b.tsArrayType(b.tsParenthesizedType(b.tsUnionType(obj)))
|
|
439
|
+
const expr = b.tsTypeAliasDeclaration(id, value)
|
|
440
|
+
expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true))
|
|
441
|
+
return b.exportDeclaration(false, expr)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Standard array
|
|
445
|
+
const firstType = assignmentValues.Type
|
|
446
|
+
const obj = Array.isArray(firstType)
|
|
447
|
+
? firstType.map(parseUnionType)
|
|
448
|
+
: isCDDLArray(firstType)
|
|
449
|
+
? firstType.Values.map((val: any) => parseUnionType(Array.isArray(val.Type) ? val.Type[0] : val.Type))
|
|
450
|
+
: [parseUnionType(firstType)]
|
|
451
|
+
|
|
452
|
+
const value = b.tsArrayType(
|
|
453
|
+
obj.length === 1
|
|
454
|
+
? obj[0]
|
|
455
|
+
: b.tsParenthesizedType(b.tsUnionType(obj))
|
|
456
|
+
)
|
|
457
|
+
const expr = b.tsTypeAliasDeclaration(id, value)
|
|
458
|
+
expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true))
|
|
459
|
+
return b.exportDeclaration(false, expr)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
throw new Error(`Unknown assignment type "${(assignment as any).Type}"`)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function parseObjectType (props: Property[]): ObjectBody {
|
|
466
|
+
const propItems: ObjectBody = []
|
|
467
|
+
for (const prop of props) {
|
|
468
|
+
/**
|
|
469
|
+
* Empty groups like
|
|
470
|
+
* {
|
|
471
|
+
* HasCut: false,
|
|
472
|
+
* Occurrence: { n: 1, m: 1 },
|
|
473
|
+
* Name: '',
|
|
474
|
+
* Type: [ { Type: 'group', Value: 'Extensible', Unwrapped: false } ],
|
|
475
|
+
* Comment: ''
|
|
476
|
+
* }
|
|
477
|
+
* are ignored and later added as interface extensions
|
|
478
|
+
*/
|
|
479
|
+
if (isUnNamedProperty(prop)) {
|
|
480
|
+
continue
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const id = b.identifier(camelcase(prop.Name))
|
|
484
|
+
const cddlType: PropertyType[] = Array.isArray(prop.Type) ? prop.Type : [prop.Type]
|
|
485
|
+
const comments: string[] = prop.Comments.map((c) => ` ${c.Content}`)
|
|
486
|
+
|
|
487
|
+
if (prop.Operator && prop.Operator.Type === 'default') {
|
|
488
|
+
const defaultValue = parseDefaultValue(prop.Operator)
|
|
489
|
+
defaultValue && comments.length && comments.push('') // add empty line if we have previous comments
|
|
490
|
+
defaultValue && comments.push(` @default ${defaultValue}`)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const type = cddlType.map((t) => {
|
|
494
|
+
const unionType = parseUnionType(t)
|
|
495
|
+
if (unionType) {
|
|
496
|
+
const defaultValue = parseDefaultValue((t as PropertyReference).Operator)
|
|
497
|
+
defaultValue && comments.length && comments.push('') // add empty line if we have previous comments
|
|
498
|
+
defaultValue && comments.push(` @default ${defaultValue}`)
|
|
499
|
+
return unionType
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
throw new Error(`Couldn't parse property ${JSON.stringify(t)}`)
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
const typeAnnotation = b.tsTypeAnnotation(b.tsUnionType(type))
|
|
507
|
+
const isOptional = prop.Occurrence.n === 0
|
|
508
|
+
const propSignature = b.tsPropertySignature(id, typeAnnotation, isOptional)
|
|
509
|
+
propSignature.comments = comments.length ? [b.commentBlock(`*\n *${comments.join('\n *')}\n `)] : []
|
|
510
|
+
propItems.push(propSignature)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return propItems
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function parseUnionType (t: PropertyType | Assignment): TSTypeKind {
|
|
517
|
+
if (typeof t === 'string') {
|
|
518
|
+
if (!NATIVE_TYPES[t]) {
|
|
519
|
+
throw new Error(`Unknown native type: "${t}`)
|
|
520
|
+
}
|
|
521
|
+
return NATIVE_TYPES[t]
|
|
522
|
+
} else if ((t as any).Type && typeof (t as any).Type === 'string' && NATIVE_TYPES[(t as any).Type]) {
|
|
523
|
+
return NATIVE_TYPES[(t as any).Type]
|
|
524
|
+
} else if (isNativeTypeWithOperator(t) && NATIVE_TYPES[(t.Type as any).Type]) {
|
|
525
|
+
return NATIVE_TYPES[(t.Type as any).Type]
|
|
526
|
+
} else if (isPropertyReference(t) && t.Value === 'null') {
|
|
527
|
+
return b.tsNullKeyword()
|
|
528
|
+
} else if (isGroup(t)) {
|
|
529
|
+
/**
|
|
530
|
+
* check if we have special groups
|
|
531
|
+
*/
|
|
532
|
+
if (isGroup(t) && !isNamedGroupReference(t) && t.Properties) {
|
|
533
|
+
const prop = t.Properties
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Check if we have choices in the group (arrays of Properties)
|
|
537
|
+
*/
|
|
538
|
+
if (prop.some(p => Array.isArray(p))) {
|
|
539
|
+
const options: TSTypeKind[] = []
|
|
540
|
+
for (const choice of prop) {
|
|
541
|
+
const subProps = Array.isArray(choice) ? choice : [choice]
|
|
542
|
+
|
|
543
|
+
if (subProps.length === 1 && isUnNamedProperty(subProps[0])) {
|
|
544
|
+
const first = subProps[0]
|
|
545
|
+
const subType = Array.isArray(first.Type) ? first.Type[0] : first.Type
|
|
546
|
+
options.push(parseUnionType(subType as PropertyType))
|
|
547
|
+
continue
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (subProps.every(isUnNamedProperty)) {
|
|
551
|
+
const tupleItems = subProps.map((p) => {
|
|
552
|
+
const subType = Array.isArray(p.Type) ? p.Type[0] : p.Type
|
|
553
|
+
return parseUnionType(subType as PropertyType)
|
|
554
|
+
})
|
|
555
|
+
options.push(b.tsTupleType(tupleItems))
|
|
556
|
+
continue
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
options.push(b.tsTypeLiteral(parseObjectType(subProps)))
|
|
560
|
+
}
|
|
561
|
+
return b.tsUnionType(options)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if ((prop as Property[]).every(isUnNamedProperty)) {
|
|
565
|
+
const items = (prop as Property[]).map(p => {
|
|
566
|
+
const t = Array.isArray(p.Type) ? p.Type[0] : p.Type
|
|
567
|
+
return parseUnionType(t as PropertyType)
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
if (items.length === 1) return items[0];
|
|
571
|
+
return b.tsTupleType(items);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* {*text => text} which will be transformed to `Record<string, string>`
|
|
576
|
+
*/
|
|
577
|
+
if (prop.length === 1 && Object.keys(NATIVE_TYPES).includes((prop[0] as Property).Name)) {
|
|
578
|
+
return b.tsTypeReference(
|
|
579
|
+
b.identifier('Record'),
|
|
580
|
+
b.tsTypeParameterInstantiation([
|
|
581
|
+
NATIVE_TYPES[(prop[0] as Property).Name],
|
|
582
|
+
parseUnionType(((prop[0] as Property).Type as PropertyType[])[0])
|
|
583
|
+
])
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* e.g. ?attributes: {*foo => text},
|
|
589
|
+
*/
|
|
590
|
+
return b.tsTypeLiteral(parseObjectType(t.Properties as Property[]))
|
|
591
|
+
} else if (isNamedGroupReference(t)) {
|
|
592
|
+
return b.tsTypeReference(
|
|
593
|
+
b.identifier(pascalCase(t.Value))
|
|
594
|
+
)
|
|
595
|
+
}
|
|
596
|
+
throw new Error(`Unknown group type: ${JSON.stringify(t)}`)
|
|
597
|
+
} else if (isLiteralWithValue(t)) {
|
|
598
|
+
if (typeof t.Value === 'string') return b.tsLiteralType(b.stringLiteral(t.Value))
|
|
599
|
+
if (typeof t.Value === 'number') return b.tsLiteralType(b.numericLiteral(t.Value))
|
|
600
|
+
if (typeof t.Value === 'boolean') return b.tsLiteralType(b.booleanLiteral(t.Value))
|
|
601
|
+
if (typeof t.Value === 'bigint') return b.tsLiteralType(b.bigIntLiteral(t.Value.toString()))
|
|
602
|
+
if (t.Value === null) return b.tsNullKeyword()
|
|
603
|
+
throw new Error(`Unsupported literal type: ${JSON.stringify(t)}`)
|
|
604
|
+
} else if (isCDDLArray(t)) {
|
|
605
|
+
const types = ((t as Array).Values[0] as Property).Type as PropertyType[]
|
|
606
|
+
const typedTypes = (Array.isArray(types) ? types : [types]).map((val) => {
|
|
607
|
+
if (typeof val === 'string' && NATIVE_TYPES[val]) {
|
|
608
|
+
return NATIVE_TYPES[val]
|
|
609
|
+
}
|
|
610
|
+
return b.tsTypeReference(
|
|
611
|
+
b.identifier(pascalCase((val as any).Value as string))
|
|
612
|
+
)
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
if (typedTypes.length > 1) {
|
|
616
|
+
return b.tsArrayType(b.tsParenthesizedType(b.tsUnionType(typedTypes)))
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (!typedTypes[0]) {
|
|
620
|
+
console.log('typedTypes[0] is missing!', types, typedTypes);
|
|
621
|
+
}
|
|
622
|
+
return b.tsArrayType(typedTypes[0])
|
|
623
|
+
} else if (isRange(t)) {
|
|
624
|
+
return b.tsNumberKeyword()
|
|
625
|
+
} else if (isNativeTypeWithOperator(t) && isNamedGroupReference(t.Type)) {
|
|
626
|
+
/**
|
|
627
|
+
* e.g. ?pointerType: input.PointerType .default "mouse"
|
|
628
|
+
*/
|
|
629
|
+
const referenceValue = pascalCase(t.Type.Value)
|
|
630
|
+
return b.tsTypeReference(b.identifier(referenceValue))
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
throw new Error(`Unknown union type: ${JSON.stringify(t)}`)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function parseDefaultValue (operator?: Operator) {
|
|
637
|
+
if (!operator || operator.Type !== 'default') {
|
|
638
|
+
return
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const operatorValue = operator.Value as PropertyReference
|
|
642
|
+
if (operator.Value === 'null') {
|
|
643
|
+
return operator.Value
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (operatorValue.Type !== 'literal') {
|
|
647
|
+
throw new Error(`Can't parse operator default value of ${JSON.stringify(operator)}`)
|
|
648
|
+
}
|
|
649
|
+
return typeof operatorValue.Value === 'string'
|
|
650
|
+
? `'${operatorValue.Value}'`
|
|
651
|
+
: operatorValue.Value as unknown as string
|
|
652
|
+
}
|