cddl2ts 0.2.2 → 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/build/cli.d.ts +1 -1
- package/build/cli.d.ts.map +1 -1
- package/build/cli.js +20 -19
- package/build/index.d.ts +4 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +420 -57
- package/build/utils.d.ts +23 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +37 -0
- package/package.json +22 -14
package/build/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export default function cli(
|
|
1
|
+
export default function cli(argv?: string[]): Promise<undefined>;
|
|
2
2
|
//# sourceMappingURL=cli.d.ts.map
|
package/build/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
${pkg.description}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
|
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
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,
|
|
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(
|
|
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(
|
|
35
|
-
if (assignment
|
|
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(
|
|
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(
|
|
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
|
|
53
|
-
const id = b.identifier(
|
|
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 (
|
|
58
|
-
const
|
|
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
|
-
|
|
64
|
-
const
|
|
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
|
|
73
|
-
const id = b.identifier(
|
|
74
|
-
const
|
|
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
|
|
78
|
-
? firstType.Values.map((val) => parseUnionType(val.Type[0]))
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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
|
|
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
|
|
157
|
-
const value = t.Value;
|
|
471
|
+
else if (isGroup(t)) {
|
|
158
472
|
/**
|
|
159
473
|
* check if we have special groups
|
|
160
474
|
*/
|
|
161
|
-
if (!
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
183
|
-
|
|
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
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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 (
|
|
559
|
+
else if (isRange(t)) {
|
|
197
560
|
return b.tsNumberKeyword();
|
|
198
561
|
}
|
|
199
|
-
else if (
|
|
562
|
+
else if (isNativeTypeWithOperator(t) && isNamedGroupReference(t.Type)) {
|
|
200
563
|
/**
|
|
201
564
|
* e.g. ?pointerType: input.PointerType .default "mouse"
|
|
202
565
|
*/
|
|
203
|
-
const referenceValue =
|
|
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)}`);
|
package/build/utils.d.ts
ADDED
|
@@ -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.
|
|
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
|
-
"
|
|
34
|
-
"
|
|
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/
|
|
38
|
-
"@types/node": "^
|
|
39
|
-
"@vitest/coverage-v8": "^
|
|
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": "^
|
|
42
|
-
"typescript": "^5.
|
|
43
|
-
"vitest": "^
|
|
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.
|
|
47
|
-
"camelcase": "^
|
|
48
|
-
"cddl": "^0.
|
|
49
|
-
"recast": "^0.23.
|
|
50
|
-
"yargs": "^
|
|
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
|
}
|