cddl2ts 0.2.1 → 0.3.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/README.md CHANGED
@@ -31,6 +31,7 @@ npx cddl2ts ./path/to/interface.cddl &> ./path/to/interface.ts
31
31
  The module exports a `transform` method that takes an CDDL AST object and returns a TypeScript definition as `string`, e.g.:
32
32
 
33
33
  ```js
34
+ import { parse } from 'cddl'
34
35
  import { transform } from 'cddl2ts'
35
36
 
36
37
  /**
@@ -43,7 +44,8 @@ import { transform } from 'cddl2ts'
43
44
  * ?platformName: text,
44
45
  * };
45
46
  */
46
- const ts = transform('./spec.cddl')
47
+ const ast = parse('./spec.cddl')
48
+ const ts = transform(ast)
47
49
  console.log(ts)
48
50
  /**
49
51
  * outputs:
package/build/cli.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export default function cli(args?: string[]): Promise<undefined>;
1
+ export default function cli(argv?: string[]): Promise<undefined>;
2
2
  //# sourceMappingURL=cli.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAmBA,wBAA8B,GAAG,CAAE,IAAI,WAAwB,sBAmB9D"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AASA,wBAA8B,GAAG,CAAE,IAAI,WAAwB,sBAgC9D"}
package/build/cli.js CHANGED
@@ -1,33 +1,34 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import yargs from 'yargs';
3
4
  import { parse } from 'cddl';
4
5
  import { transform } from './index.js';
5
6
  import { pkg } from './constants.js';
6
- const HELP = `
7
- ${pkg.name}
8
- ${pkg.description}
9
-
10
- Usage:
11
- runme2ts ./path/to/spec.cddl &> ./path/to/interface.ts
12
-
13
- v${pkg.version}
14
- Copyright ${(new Date()).getFullYear()} ${pkg.author}
15
- `;
16
- export default async function cli(args = process.argv.slice(2)) {
17
- if (args.includes('--help') || args.length === 0) {
18
- console.log(HELP);
7
+ export default async function cli(argv = process.argv.slice(2)) {
8
+ const parser = yargs(argv)
9
+ .usage(`${pkg.name}\n${pkg.description}\n\nUsage:\nrunme2ts ./path/to/spec.cddl &> ./path/to/interface.ts`)
10
+ .epilog(`v${pkg.version}\nCopyright ${(new Date()).getFullYear()} ${pkg.author}`)
11
+ .version(pkg.version)
12
+ .option('u', {
13
+ alias: 'unknown-as-any',
14
+ type: 'boolean',
15
+ description: 'Use unknown instead of any',
16
+ default: false
17
+ })
18
+ .help('help')
19
+ .alias('h', 'help')
20
+ .alias('v', 'version');
21
+ const args = await parser.argv;
22
+ if (args._.length === 0) {
23
+ parser.showHelp();
19
24
  return process.exit(0);
20
25
  }
21
- if (args.includes('--version') || args.includes('-v')) {
22
- console.log(pkg.version);
23
- return process.exit(0);
24
- }
25
- const absoluteFilePath = path.resolve(process.cwd(), args[0]);
26
+ const absoluteFilePath = path.resolve(process.cwd(), args._[0]);
26
27
  const hasAccess = await fs.access(absoluteFilePath).then(() => true, () => false);
27
28
  if (!hasAccess) {
28
29
  console.error(`Couldn't find or access source CDDL file at "${absoluteFilePath}"`);
29
30
  return process.exit(1);
30
31
  }
31
32
  const ast = parse(absoluteFilePath);
32
- console.log(transform(ast));
33
+ console.log(transform(ast, { useUnknown: args.u }));
33
34
  }
package/build/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  import type { Assignment } from 'cddl';
2
- export declare function transform(assignments: Assignment[]): string;
2
+ export interface TransformOptions {
3
+ useUnknown?: boolean;
4
+ }
5
+ export declare function transform(assignments: Assignment[], options?: TransformOptions): string;
3
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAmG,MAAM,MAAM,CAAA;AAsBvI,wBAAgB,SAAS,CAAE,WAAW,EAAE,UAAU,EAAE,UAkBnD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAA8D,MAAM,MAAM,CAAA;AAoClG,MAAM,WAAW,gBAAgB;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,wBAAgB,SAAS,CAAE,WAAW,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,gBAAgB,UAwB/E"}
package/build/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import camelcase from 'camelcase';
2
2
  import { parse, print, types } from 'recast';
3
3
  import typescriptParser from 'recast/parsers/typescript.js';
4
+ import { isCDDLArray, isGroup, isNamedGroupReference, isLiteralWithValue, isNativeTypeWithOperator, isUnNamedProperty, isPropertyReference, isRange, isVariable, pascalCase } from './utils.js';
4
5
  import { pkg } from './constants.js';
5
6
  const b = types.builders;
6
7
  const NATIVE_TYPES = {
7
8
  any: b.tsAnyKeyword(),
8
9
  number: b.tsNumberKeyword(),
10
+ int: b.tsNumberKeyword(),
9
11
  float: b.tsNumberKeyword(),
10
12
  uint: b.tsNumberKeyword(),
11
13
  bool: b.tsBooleanKeyword(),
@@ -16,14 +18,20 @@ const NATIVE_TYPES = {
16
18
  nil: b.tsNullKeyword(),
17
19
  null: b.tsNullKeyword()
18
20
  };
19
- export function transform(assignments) {
21
+ export function transform(assignments, options) {
22
+ if (options?.useUnknown) {
23
+ NATIVE_TYPES.any = b.tsUnknownKeyword();
24
+ }
25
+ else {
26
+ NATIVE_TYPES.any = b.tsAnyKeyword();
27
+ }
20
28
  let ast = parse(`// compiled with https://www.npmjs.com/package/cddl2ts v${pkg.version}`, {
21
29
  parser: typescriptParser,
22
30
  sourceFileName: 'cddl2Ts.ts',
23
31
  sourceRoot: process.cwd()
24
32
  });
25
33
  for (const assignment of assignments) {
26
- const statement = parseAssignment(ast, assignment);
34
+ const statement = parseAssignment(assignment);
27
35
  if (!statement) {
28
36
  continue;
29
37
  }
@@ -31,72 +39,376 @@ export function transform(assignments) {
31
39
  }
32
40
  return print(ast).code;
33
41
  }
34
- function parseAssignment(ast, assignment) {
35
- if (assignment.Type === 'variable') {
42
+ function parseAssignment(assignment) {
43
+ if (isVariable(assignment)) {
36
44
  const propType = Array.isArray(assignment.PropertyType)
37
45
  ? assignment.PropertyType
38
46
  : [assignment.PropertyType];
39
- const id = b.identifier(camelcase(assignment.Name, { pascalCase: true }));
47
+ const id = b.identifier(pascalCase(assignment.Name));
40
48
  let typeParameters;
41
49
  // @ts-expect-error e.g. "js-int = -9007199254740991..9007199254740991"
42
50
  if (propType.length === 1 && propType[0].Type === 'range') {
43
51
  typeParameters = b.tsNumberKeyword();
44
52
  }
45
53
  else {
46
- typeParameters = b.tsUnionType(propType.map(parsePropertyType));
54
+ typeParameters = b.tsUnionType(propType.map(parseUnionType));
47
55
  }
48
56
  const expr = b.tsTypeAliasDeclaration(id, typeParameters);
49
57
  expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true));
50
58
  return b.exportDeclaration(false, expr);
51
59
  }
52
- if (assignment.Type === 'group') {
53
- const id = b.identifier(camelcase(assignment.Name, { pascalCase: true }));
60
+ if (isGroup(assignment)) {
61
+ const id = b.identifier(pascalCase(assignment.Name));
62
+ /**
63
+ * Check if we have choices in the group (arrays of Properties)
64
+ */
65
+ const properties = assignment.Properties;
66
+ const hasChoices = properties.some(p => Array.isArray(p));
67
+ if (hasChoices) {
68
+ // Flatten static properties and collect choices
69
+ const staticProps = [];
70
+ const intersections = [];
71
+ for (let i = 0; i < properties.length; i++) {
72
+ const prop = properties[i];
73
+ if (Array.isArray(prop)) {
74
+ // It's a choice (Union)
75
+ // prop is Property[] where each Property is an option
76
+ // CDDL parser appends the last choice element as a subsequent property
77
+ // so we need to grab it and merge it into the union
78
+ const choiceOptions = [...prop];
79
+ const nextProp = properties[i + 1];
80
+ if (nextProp && !Array.isArray(nextProp)) {
81
+ choiceOptions.push(nextProp);
82
+ i++; // Skip next property
83
+ }
84
+ const options = choiceOptions.map(p => {
85
+ // If p is a group reference (Name ''), it's a TypeReference
86
+ // e.g. SessionAutodetectProxyConfiguration // SessionDirectProxyConfiguration
87
+ // The parser sometimes wraps it in an array, sometimes not (if inside a choice)
88
+ const typeVal = Array.isArray(p.Type) ? p.Type[0] : p.Type;
89
+ if (isUnNamedProperty(p)) {
90
+ // Handle un-named properties (bare types) in choices.
91
+ // Native types / literals
92
+ if (isNamedGroupReference(typeVal)) {
93
+ return b.tsTypeReference(b.identifier(pascalCase(typeVal.Value || typeVal.Type)));
94
+ }
95
+ return parseUnionType(typeVal);
96
+ }
97
+ // Otherwise it is an object literal with this property
98
+ return b.tsTypeLiteral(parseObjectType([p]));
99
+ });
100
+ intersections.push(b.tsUnionType(options));
101
+ }
102
+ else {
103
+ staticProps.push(prop);
104
+ }
105
+ }
106
+ if (staticProps.length > 0) {
107
+ // Check if we have mixins in static props
108
+ const mixins = staticProps.filter(isUnNamedProperty);
109
+ const ownProps = staticProps.filter(p => !isUnNamedProperty(p));
110
+ if (ownProps.length > 0) {
111
+ intersections.unshift(b.tsTypeLiteral(parseObjectType(ownProps)));
112
+ }
113
+ for (const mixin of mixins) {
114
+ if (Array.isArray(mixin.Type) && mixin.Type.length > 1) {
115
+ const options = mixin.Type.map(parseUnionType);
116
+ intersections.push(b.tsUnionType(options));
117
+ }
118
+ else {
119
+ const typeVal = Array.isArray(mixin.Type) ? mixin.Type[0] : mixin.Type;
120
+ if (isNamedGroupReference(typeVal)) {
121
+ intersections.push(b.tsTypeReference(b.identifier(pascalCase(typeVal.Value))));
122
+ }
123
+ }
124
+ }
125
+ }
126
+ // If only one intersection element, return it directly
127
+ // If multiple, return Intersection
128
+ let value;
129
+ if (intersections.length === 0) {
130
+ value = b.tsAnyKeyword(); // Should not happen for valid CDDL?
131
+ }
132
+ else if (intersections.length === 1) {
133
+ value = intersections[0];
134
+ }
135
+ else {
136
+ value = b.tsIntersectionType(intersections);
137
+ }
138
+ const expr = b.tsTypeAliasDeclaration(id, value);
139
+ expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true));
140
+ return b.exportDeclaration(false, expr);
141
+ }
142
+ const props = properties;
54
143
  /**
55
144
  * transform CDDL groups like `Extensible = (*text => any)`
56
145
  */
57
- if (assignment.Properties.length === 1 && assignment.Properties[0].Type.length === 1 && Object.keys(NATIVE_TYPES).includes((assignment.Properties[0].Name))) {
58
- const value = parseUnionType(assignment);
146
+ if (props.length === 1) {
147
+ const prop = props[0];
148
+ const propType = Array.isArray(prop.Type) ? prop.Type : [prop.Type];
149
+ if (propType.length === 1 && Object.keys(NATIVE_TYPES).includes(prop.Name)) {
150
+ const value = parseUnionType(assignment);
151
+ const expr = b.tsTypeAliasDeclaration(id, value);
152
+ expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true));
153
+ return b.exportDeclaration(false, expr);
154
+ }
155
+ }
156
+ // Check if extended interfaces are likely unions or conflicting types
157
+ // In CDDL, including a group (Name '') that is defined elsewhere as a choice (union)
158
+ // is valid, but TypeScript interfaces cannot extend unions.
159
+ // We can't easily know if the referenced group is a union without a symbol table or 2-pass.
160
+ // However, if we simply use type intersection for ALL group inclusions, it is always safe.
161
+ // (Interface extending Interface is same as Type = Interface & Interface)
162
+ // Let's refactor to use Type Alias with Intersection if there are any mixins.
163
+ const mixins = props.filter(isUnNamedProperty);
164
+ if (mixins.length > 0) {
165
+ // It has mixins (extensions). Use type alias with intersection to be safe against unions.
166
+ // Type = (Mixin1 & Mixin2 & { OwnProps })
167
+ const intersections = [];
168
+ for (const mixin of mixins) {
169
+ // If mixin is a group choice (e.g. `(A // B)`), the parser returns a Group object
170
+ // with Properties containing the choices. We need to extract them.
171
+ if (Array.isArray(mixin.Type) && mixin.Type.length > 1) {
172
+ // Check if it's a choice of types
173
+ const unionOptions = [];
174
+ for (const t of mixin.Type) {
175
+ let refName;
176
+ if (typeof t === 'string')
177
+ refName = t;
178
+ else if (isNamedGroupReference(t))
179
+ refName = t.Value;
180
+ else
181
+ refName = t.Value || t.Type;
182
+ if (refName)
183
+ unionOptions.push(b.tsTypeReference(b.identifier(pascalCase(refName))));
184
+ }
185
+ if (unionOptions.length > 0) {
186
+ intersections.push(b.tsParenthesizedType(b.tsUnionType(unionOptions)));
187
+ continue;
188
+ }
189
+ }
190
+ if (isGroup(mixin.Type) && Array.isArray(mixin.Type.Properties)) {
191
+ const group = mixin.Type;
192
+ const choices = [];
193
+ for (const prop of group.Properties) {
194
+ // Choices are wrapped in arrays in the properties
195
+ const options = Array.isArray(prop) ? prop : [prop];
196
+ if (options.length > 1) { // It's a choice within the mixin group
197
+ const unionOptions = [];
198
+ for (const option of options) {
199
+ let refName;
200
+ const type = option.Type;
201
+ if (typeof type === 'string')
202
+ refName = type;
203
+ else if (isNamedGroupReference(type))
204
+ refName = type.Value;
205
+ else if (Array.isArray(type) && type[0]) {
206
+ const first = type[0];
207
+ if (isNamedGroupReference(first))
208
+ refName = first.Value;
209
+ else if (isUnNamedProperty(first)) {
210
+ if (isNamedGroupReference(first.Type))
211
+ refName = first.Type.Value;
212
+ else if (isGroup(first.Type) && first.Type.Properties && first.Type.Properties.length === 1) {
213
+ // Handle case where group reference is wrapped deeply
214
+ const subProp = first.Type.Properties[0];
215
+ if (isNamedGroupReference(subProp.Type))
216
+ refName = subProp.Type.Value;
217
+ }
218
+ }
219
+ if (!refName)
220
+ refName = first.Value || first.Type;
221
+ }
222
+ if (refName)
223
+ unionOptions.push(b.tsTypeReference(b.identifier(pascalCase(refName))));
224
+ }
225
+ if (unionOptions.length > 0) {
226
+ choices.push(b.tsParenthesizedType(b.tsUnionType(unionOptions)));
227
+ continue;
228
+ }
229
+ }
230
+ for (const option of options) {
231
+ let refName;
232
+ const type = option.Type;
233
+ if (typeof type === 'string') {
234
+ refName = type;
235
+ }
236
+ else if (Array.isArray(type)) {
237
+ if (type.length > 1) {
238
+ // console.log('DEBUG: Found array length > 1', JSON.stringify(type, null, 2))
239
+ const unionChoices = [];
240
+ for (const t of type) {
241
+ let name;
242
+ if (typeof t === 'string')
243
+ name = t;
244
+ else if (isNamedGroupReference(t))
245
+ name = t.Value;
246
+ else if (isUnNamedProperty(t)) {
247
+ if (isNamedGroupReference(t.Type))
248
+ name = t.Type.Value;
249
+ else if (isGroup(t.Type) && t.Type.Properties && t.Type.Properties.length === 1) {
250
+ const subProp = t.Type.Properties[0];
251
+ if (isNamedGroupReference(subProp.Type))
252
+ name = subProp.Type.Value;
253
+ }
254
+ }
255
+ if (!name)
256
+ name = t.Value || t.Type;
257
+ if (name)
258
+ unionChoices.push(b.tsTypeReference(b.identifier(pascalCase(name))));
259
+ }
260
+ if (unionChoices.length > 0) {
261
+ choices.push(b.tsParenthesizedType(b.tsUnionType(unionChoices)));
262
+ continue;
263
+ }
264
+ }
265
+ else if (type.length === 1 && Array.isArray(type[0])) {
266
+ // Handle nested union e.g. [ [ A, B ] ] which seems common in some parsers for choices
267
+ const nested = type[0];
268
+ if (nested.length > 1) {
269
+ const unionChoices = [];
270
+ for (const t of nested) {
271
+ let name;
272
+ if (typeof t === 'string')
273
+ name = t;
274
+ else if (isNamedGroupReference(t))
275
+ name = t.Value;
276
+ else if (isUnNamedProperty(t)) {
277
+ if (isNamedGroupReference(t.Type))
278
+ name = t.Type.Value;
279
+ else if (isGroup(t.Type) && t.Type.Properties && t.Type.Properties.length === 1) {
280
+ const subProp = t.Type.Properties[0];
281
+ if (isNamedGroupReference(subProp.Type))
282
+ name = subProp.Type.Value;
283
+ }
284
+ else {
285
+ // Fallback for wrapped primitive?
286
+ name = t.Type.Value || t.Type.Type;
287
+ }
288
+ }
289
+ if (!name)
290
+ name = t.Value || t.Type;
291
+ if (name)
292
+ unionChoices.push(b.tsTypeReference(b.identifier(pascalCase(name))));
293
+ }
294
+ if (unionChoices.length > 0) {
295
+ choices.push(b.tsParenthesizedType(b.tsUnionType(unionChoices)));
296
+ continue;
297
+ }
298
+ }
299
+ }
300
+ const first = type[0];
301
+ if (first) {
302
+ if (isNamedGroupReference(first)) {
303
+ refName = first.Value;
304
+ }
305
+ else if (!isGroup(first)) {
306
+ if (isUnNamedProperty(first)) {
307
+ if (isNamedGroupReference(first.Type)) {
308
+ refName = first.Type.Value;
309
+ }
310
+ else {
311
+ refName = first.Type.Value || first.Type.Type;
312
+ }
313
+ }
314
+ else {
315
+ refName = first.Value || first.Type;
316
+ }
317
+ }
318
+ else {
319
+ refName = first.Value || first.Type;
320
+ }
321
+ }
322
+ }
323
+ else if (type && typeof type === 'object') {
324
+ if (isGroup(type) && Array.isArray(type.Properties)) {
325
+ choices.push(b.tsTypeLiteral(parseObjectType(type.Properties)));
326
+ continue;
327
+ }
328
+ refName = isNamedGroupReference(type)
329
+ ? type.Value || type.Type
330
+ : type.Value || type.Type;
331
+ }
332
+ // If we found a refName, push it. Note that if this was a choice we handled above, we skip this
333
+ if (refName) {
334
+ choices.push(b.tsTypeReference(b.identifier(pascalCase(refName))));
335
+ }
336
+ }
337
+ }
338
+ if (choices.length > 0) {
339
+ // Unions inside intersections must be parenthesized to avoid ambiguity
340
+ // e.g. (A | B) & C vs A | (B & C)
341
+ const union = b.tsUnionType(choices);
342
+ intersections.push(b.tsParenthesizedType(union));
343
+ continue;
344
+ }
345
+ }
346
+ const propType = mixin.Type;
347
+ if (typeof propType === 'string' && NATIVE_TYPES[propType]) {
348
+ intersections.push(NATIVE_TYPES[propType]);
349
+ continue;
350
+ }
351
+ const groupRef = propType[0];
352
+ // Handle nested inline groups if any (though usually flat here if name is empty?)
353
+ const value = (groupRef?.Value || groupRef?.Type);
354
+ if (value) {
355
+ intersections.push(b.tsTypeReference(b.identifier(pascalCase(value))));
356
+ }
357
+ }
358
+ const ownProps = props.filter(p => !isUnNamedProperty(p));
359
+ if (ownProps.length > 0) {
360
+ intersections.push(b.tsTypeLiteral(parseObjectType(ownProps)));
361
+ }
362
+ let value;
363
+ if (intersections.length === 1) {
364
+ value = intersections[0];
365
+ }
366
+ else {
367
+ value = b.tsIntersectionType(intersections);
368
+ }
59
369
  const expr = b.tsTypeAliasDeclaration(id, value);
60
370
  expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true));
61
371
  return b.exportDeclaration(false, expr);
62
372
  }
63
- const objectType = parseObjectType(assignment.Properties);
64
- const extendInterfaces = assignment.Properties
65
- .filter((prop) => prop.Name === '')
66
- .map((prop) => b.tsExpressionWithTypeArguments(b.identifier(camelcase(prop.Type[0].Value, { pascalCase: true }))));
373
+ // Fallback to interface if no mixins (pure object)
374
+ const objectType = parseObjectType(props);
67
375
  const expr = b.tsInterfaceDeclaration(id, b.tsInterfaceBody(objectType));
68
- expr.extends = extendInterfaces;
69
376
  expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true));
70
377
  return b.exportDeclaration(false, expr);
71
378
  }
72
- if (assignment.Type === 'array') {
73
- const id = b.identifier(camelcase(assignment.Name, { pascalCase: true }));
74
- const firstType = assignment.Values[0].Type;
379
+ if (isCDDLArray(assignment)) {
380
+ const id = b.identifier(pascalCase(assignment.Name));
381
+ const assignmentValues = assignment.Values[0];
382
+ if (Array.isArray(assignmentValues)) {
383
+ // It's a choice/union in the array definition
384
+ // e.g. Foo = [ (A | B) ]
385
+ // assignment.Values[0] is Property[] (the choices)
386
+ // We need to parse each choice.
387
+ const obj = assignmentValues.map((prop) => {
388
+ const t = Array.isArray(prop.Type) ? prop.Type[0] : prop.Type;
389
+ return parseUnionType(t);
390
+ });
391
+ const value = b.tsArrayType(b.tsParenthesizedType(b.tsUnionType(obj)));
392
+ const expr = b.tsTypeAliasDeclaration(id, value);
393
+ expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true));
394
+ return b.exportDeclaration(false, expr);
395
+ }
396
+ // Standard array
397
+ const firstType = assignmentValues.Type;
75
398
  const obj = Array.isArray(firstType)
76
399
  ? firstType.map(parseUnionType)
77
- : firstType.Values
78
- ? firstType.Values.map((val) => parseUnionType(val.Type[0]))
79
- // ToDo(Christian): transpile this case correctly
80
- : [];
81
- const value = b.tsArrayType(b.tsParenthesizedType(b.tsUnionType(obj)));
400
+ : isCDDLArray(firstType)
401
+ ? firstType.Values.map((val) => parseUnionType(Array.isArray(val.Type) ? val.Type[0] : val.Type))
402
+ : [parseUnionType(firstType)];
403
+ const value = b.tsArrayType(obj.length === 1
404
+ ? obj[0]
405
+ : b.tsParenthesizedType(b.tsUnionType(obj)));
82
406
  const expr = b.tsTypeAliasDeclaration(id, value);
83
407
  expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true));
84
408
  return b.exportDeclaration(false, expr);
85
409
  }
86
410
  throw new Error(`Unknown assignment type "${assignment.Type}"`);
87
411
  }
88
- function parsePropertyType(propType) {
89
- if (typeof propType === 'string') {
90
- return b.tsStringKeyword();
91
- }
92
- if (propType.Type === 'group') {
93
- return b.tsTypeReference(b.identifier(camelcase(propType.Value.toString(), { pascalCase: true })));
94
- }
95
- if (propType.Type === 'literal') {
96
- return b.tsLiteralType(b.stringLiteral(propType.Value.toString()));
97
- }
98
- throw new Error(`Couldn't parse property type ${JSON.stringify(propType, null, 4)}`);
99
- }
100
412
  function parseObjectType(props) {
101
413
  const propItems = [];
102
414
  for (const prop of props) {
@@ -111,7 +423,7 @@ function parseObjectType(props) {
111
423
  * }
112
424
  * are ignored and later added as interface extensions
113
425
  */
114
- if (prop.Name === '') {
426
+ if (isUnNamedProperty(prop)) {
115
427
  continue;
116
428
  }
117
429
  const id = b.identifier(camelcase(prop.Name));
@@ -147,19 +459,55 @@ function parseUnionType(t) {
147
459
  }
148
460
  return NATIVE_TYPES[t];
149
461
  }
150
- else if (NATIVE_TYPES[t.Type]) {
462
+ else if (t.Type && typeof t.Type === 'string' && NATIVE_TYPES[t.Type]) {
151
463
  return NATIVE_TYPES[t.Type];
152
464
  }
153
- else if (t.Value === 'null') {
465
+ else if (isNativeTypeWithOperator(t) && NATIVE_TYPES[t.Type.Type]) {
466
+ return NATIVE_TYPES[t.Type.Type];
467
+ }
468
+ else if (isPropertyReference(t) && t.Value === 'null') {
154
469
  return b.tsNullKeyword();
155
470
  }
156
- else if (t.Type === 'group') {
157
- const value = t.Value;
471
+ else if (isGroup(t)) {
158
472
  /**
159
473
  * check if we have special groups
160
474
  */
161
- if (!value && t.Properties) {
475
+ if (isGroup(t) && !isNamedGroupReference(t) && t.Properties) {
162
476
  const prop = t.Properties;
477
+ /**
478
+ * Check if we have choices in the group (arrays of Properties)
479
+ */
480
+ if (prop.some(p => Array.isArray(p))) {
481
+ const options = [];
482
+ for (const choice of prop) {
483
+ const subProps = Array.isArray(choice) ? choice : [choice];
484
+ if (subProps.length === 1 && isUnNamedProperty(subProps[0])) {
485
+ const first = subProps[0];
486
+ const subType = Array.isArray(first.Type) ? first.Type[0] : first.Type;
487
+ options.push(parseUnionType(subType));
488
+ continue;
489
+ }
490
+ if (subProps.every(isUnNamedProperty)) {
491
+ const tupleItems = subProps.map((p) => {
492
+ const subType = Array.isArray(p.Type) ? p.Type[0] : p.Type;
493
+ return parseUnionType(subType);
494
+ });
495
+ options.push(b.tsTupleType(tupleItems));
496
+ continue;
497
+ }
498
+ options.push(b.tsTypeLiteral(parseObjectType(subProps)));
499
+ }
500
+ return b.tsUnionType(options);
501
+ }
502
+ if (prop.every(isUnNamedProperty)) {
503
+ const items = prop.map(p => {
504
+ const t = Array.isArray(p.Type) ? p.Type[0] : p.Type;
505
+ return parseUnionType(t);
506
+ });
507
+ if (items.length === 1)
508
+ return items[0];
509
+ return b.tsTupleType(items);
510
+ }
163
511
  /**
164
512
  * {*text => text} which will be transformed to `Record<string, string>`
165
513
  */
@@ -174,33 +522,48 @@ function parseUnionType(t) {
174
522
  */
175
523
  return b.tsTypeLiteral(parseObjectType(t.Properties));
176
524
  }
177
- return b.tsTypeReference(b.identifier(camelcase(value.toString(), { pascalCase: true })));
178
- }
179
- else if (t.Type === 'literal' && typeof t.Value === 'string') {
180
- return b.tsLiteralType(b.stringLiteral(t.Value));
525
+ else if (isNamedGroupReference(t)) {
526
+ return b.tsTypeReference(b.identifier(pascalCase(t.Value)));
527
+ }
528
+ throw new Error(`Unknown group type: ${JSON.stringify(t)}`);
181
529
  }
182
- else if (t.Type === 'literal' && typeof t.Value === 'number') {
183
- return b.tsLiteralType(b.numericLiteral(t.Value));
530
+ else if (isLiteralWithValue(t)) {
531
+ if (typeof t.Value === 'string')
532
+ return b.tsLiteralType(b.stringLiteral(t.Value));
533
+ if (typeof t.Value === 'number')
534
+ return b.tsLiteralType(b.numericLiteral(t.Value));
535
+ if (typeof t.Value === 'boolean')
536
+ return b.tsLiteralType(b.booleanLiteral(t.Value));
537
+ if (typeof t.Value === 'bigint')
538
+ return b.tsLiteralType(b.bigIntLiteral(t.Value.toString()));
539
+ if (t.Value === null)
540
+ return b.tsNullKeyword();
541
+ throw new Error(`Unsupported literal type: ${JSON.stringify(t)}`);
184
542
  }
185
- else if (t.Type === 'array') {
543
+ else if (isCDDLArray(t)) {
186
544
  const types = t.Values[0].Type;
187
545
  const typedTypes = (Array.isArray(types) ? types : [types]).map((val) => {
188
- return typeof val === 'string' && NATIVE_TYPES[val]
189
- ? NATIVE_TYPES[val]
190
- : b.tsTypeReference(b.identifier(camelcase(val.Value, { pascalCase: true })));
546
+ if (typeof val === 'string' && NATIVE_TYPES[val]) {
547
+ return NATIVE_TYPES[val];
548
+ }
549
+ return b.tsTypeReference(b.identifier(pascalCase(val.Value)));
191
550
  });
192
- return b.tsArrayType(typedTypes.length > 1
193
- ? b.tsParenthesizedType(b.tsUnionType(typedTypes))
194
- : b.tsUnionType(typedTypes));
551
+ if (typedTypes.length > 1) {
552
+ return b.tsArrayType(b.tsParenthesizedType(b.tsUnionType(typedTypes)));
553
+ }
554
+ if (!typedTypes[0]) {
555
+ console.log('typedTypes[0] is missing!', types, typedTypes);
556
+ }
557
+ return b.tsArrayType(typedTypes[0]);
195
558
  }
196
- else if (typeof t.Type === 'object' && t.Type.Type === 'range') {
559
+ else if (isRange(t)) {
197
560
  return b.tsNumberKeyword();
198
561
  }
199
- else if (typeof t.Type === 'object' && t.Type.Type === 'group') {
562
+ else if (isNativeTypeWithOperator(t) && isNamedGroupReference(t.Type)) {
200
563
  /**
201
564
  * e.g. ?pointerType: input.PointerType .default "mouse"
202
565
  */
203
- const referenceValue = camelcase(t.Type.Value, { pascalCase: true });
566
+ const referenceValue = pascalCase(t.Type.Value);
204
567
  return b.tsTypeReference(b.identifier(referenceValue));
205
568
  }
206
569
  throw new Error(`Unknown union type: ${JSON.stringify(t)}`);
@@ -0,0 +1,23 @@
1
+ import { Assignment, PropertyReference, Property, Array, NativeTypeWithOperator, Group, Variable } from 'cddl';
2
+ export declare function pascalCase(name: string): string;
3
+ export declare function isVariable(assignment: Assignment): assignment is Variable;
4
+ export declare function isGroup(t: any): t is Group;
5
+ export declare function isCDDLArray(t: any): t is Array;
6
+ export declare function isProperty(t: any): t is Property;
7
+ export declare function isUnNamedProperty(t: any): t is Property & {
8
+ Name: '';
9
+ };
10
+ export declare function isNamedGroupReference(t: any): t is PropertyReference & {
11
+ Value: string;
12
+ };
13
+ export declare function isPropertyReference(t: any): t is PropertyReference;
14
+ export declare function isNativeTypeWithOperator(t: any): t is NativeTypeWithOperator;
15
+ export declare function isRange(t: any): boolean;
16
+ export declare function isLiteralWithValue(t: any): t is {
17
+ Type: 'literal';
18
+ Value: unknown;
19
+ };
20
+ export declare function hasTypeProperty(t: any): t is {
21
+ Type: string;
22
+ };
23
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,UAAU,EACV,iBAAiB,EACjB,QAAQ,EACR,KAAK,EACL,sBAAsB,EACtB,KAAK,EACL,QAAQ,EACX,MAAM,MAAM,CAAA;AAGb,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,UAEtC;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,IAAI,QAAQ,CAEzE;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,KAAK,CAE1C;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,KAAK,CAE9C;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,QAAQ,CAEhD;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,QAAQ,GAAG;IAAE,IAAI,EAAE,EAAE,CAAA;CAAE,CAEtE;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,iBAAiB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAExF;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,iBAAiB,CAElE;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,sBAAsB,CAE5E;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,GAAG,OAAO,CAEvC;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI;IAC7C,IAAI,EAAE,SAAS,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;CACjB,CAEA;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAE7D"}
package/build/utils.js ADDED
@@ -0,0 +1,37 @@
1
+ import camelcase from 'camelcase';
2
+ export function pascalCase(name) {
3
+ return camelcase(name, { pascalCase: true });
4
+ }
5
+ export function isVariable(assignment) {
6
+ return assignment.Type === 'variable';
7
+ }
8
+ export function isGroup(t) {
9
+ return t && t.Type === 'group';
10
+ }
11
+ export function isCDDLArray(t) {
12
+ return t && t.Type === 'array';
13
+ }
14
+ export function isProperty(t) {
15
+ return t && typeof t.Name === 'string' && typeof t.HasCut === 'boolean';
16
+ }
17
+ export function isUnNamedProperty(t) {
18
+ return isProperty(t) && t.Name === '';
19
+ }
20
+ export function isNamedGroupReference(t) {
21
+ return isGroup(t) && isPropertyReference(t) && typeof t.Value === 'string';
22
+ }
23
+ export function isPropertyReference(t) {
24
+ return t && 'Value' in t;
25
+ }
26
+ export function isNativeTypeWithOperator(t) {
27
+ return t && typeof t.Type === 'object' && 'Operator' in t;
28
+ }
29
+ export function isRange(t) {
30
+ return t && typeof t.Type === 'object' && t.Type.Type === 'range';
31
+ }
32
+ export function isLiteralWithValue(t) {
33
+ return t && t.Type === 'literal' && 'Value' in t;
34
+ }
35
+ export function hasTypeProperty(t) {
36
+ return t && typeof t.Type === 'string';
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cddl2ts",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "A Node.js package that can generate a TypeScript definition based on a CDDL file",
5
5
  "author": "Christian Bromann <mail@bromann.dev>",
6
6
  "license": "MIT",
@@ -12,6 +12,10 @@
12
12
  "keywords": [
13
13
  "cddl"
14
14
  ],
15
+ "engines": {
16
+ "node": ">=20.0.0",
17
+ "npm": ">=9.0.0"
18
+ },
15
19
  "bugs": {
16
20
  "url": "https://github.com/christian-bromann/cddl2ts/issues"
17
21
  },
@@ -30,23 +34,27 @@
30
34
  "release:patch": "npm run release -- patch",
31
35
  "release:minor": "npm run release -- minor",
32
36
  "release:major": "npm run release -- major",
33
- "test": "vitest",
34
- "watch": "tsc --watch"
37
+ "generate:local": "npm run compile && node ./bin/cddl2ts.js ./examples/webdriver/local.cddl --unknown-as-any > ./examples/webdriver/local.ts && tsc -p ./examples/webdriver/tsconfig.json",
38
+ "generate:remote": "npm run compile && node ./bin/cddl2ts.js ./examples/webdriver/remote.cddl --unknown-as-any > ./examples/webdriver/remote.ts && tsc -p ./examples/webdriver/tsconfig.json",
39
+ "generate:examples": "npm run generate:local && npm run generate:remote",
40
+ "test": "vitest run",
41
+ "watch": "tsc --watch",
42
+ "checks:all": "npm run compile && npm run test && npm run generate:examples"
35
43
  },
36
44
  "devDependencies": {
37
- "@types/jest": "^29.5.1",
38
- "@types/node": "^20.2.3",
39
- "@vitest/coverage-c8": "^0.31.1",
45
+ "@types/yargs": "^17.0.35",
46
+ "@types/node": "^24.12.0",
47
+ "@vitest/coverage-v8": "^4.1.0",
40
48
  "npm-run-all": "^4.1.5",
41
- "release-it": "^15.10.3",
42
- "typescript": "^5.0.4",
43
- "vitest": "^0.31.1"
49
+ "release-it": "^19.2.4",
50
+ "typescript": "^5.9.3",
51
+ "vitest": "^4.1.0"
44
52
  },
45
53
  "dependencies": {
46
- "@babel/parser": "^7.21.9",
47
- "camelcase": "^7.0.1",
48
- "cddl": "^0.8.4",
49
- "recast": "^0.23.2",
50
- "yargs": "^17.7.2"
54
+ "@babel/parser": "^7.29.0",
55
+ "camelcase": "^9.0.0",
56
+ "cddl": "^0.13.0",
57
+ "recast": "^0.23.11",
58
+ "yargs": "^18.0.0"
51
59
  }
52
60
  }