cddl2ts 0.7.2 → 0.8.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 +11 -5
- package/build/cli.d.ts.map +1 -1
- package/build/cli.js +11 -1
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +63 -37
- package/cli-examples/remote.ts +2 -2
- package/package.json +1 -1
- package/src/cli.ts +13 -2
- package/src/index.ts +70 -37
- package/tests/mod.test.ts +17 -0
- package/tests/transform_edge_cases.test.ts +29 -0
package/README.md
CHANGED
|
@@ -23,6 +23,12 @@ This package exposes a CLI as well as a programmatic interface for transforming
|
|
|
23
23
|
npx cddl2ts ./path/to/interface.cddl &> ./path/to/interface.ts
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
Generated interface fields default to `camelCase`. Pass `--field-case snake` to emit `snake_case` fields while keeping exported interface and type names unchanged.
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npx cddl2ts ./path/to/interface.cddl --field-case snake &> ./path/to/interface.ts
|
|
30
|
+
```
|
|
31
|
+
|
|
26
32
|
### Programmatic Interface
|
|
27
33
|
|
|
28
34
|
The module exports a `transform` method that takes a CDDL AST object and returns a TypeScript definition as `string`, e.g.:
|
|
@@ -41,16 +47,16 @@ import { parse, transform } from 'cddl'
|
|
|
41
47
|
* };
|
|
42
48
|
*/
|
|
43
49
|
const ast = parse('./spec.cddl')
|
|
44
|
-
const ts = transform(ast)
|
|
50
|
+
const ts = transform(ast, { fieldCase: 'snake' })
|
|
45
51
|
console.log(ts)
|
|
46
52
|
/**
|
|
47
53
|
* outputs:
|
|
48
54
|
*
|
|
49
55
|
* interface SessionCapabilityRequest {
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
56
|
+
* accept_insecure_certs?: boolean,
|
|
57
|
+
* browser_name?: string,
|
|
58
|
+
* browser_version?: string,
|
|
59
|
+
* platform_name?: string,
|
|
54
60
|
* }
|
|
55
61
|
*/
|
|
56
62
|
```
|
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":"AAWA,wBAA8B,GAAG,CAAE,IAAI,WAAwB,sBAyC9D"}
|
package/build/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import yargs from 'yargs';
|
|
|
4
4
|
import { parse } from 'cddl';
|
|
5
5
|
import { transform } from './index.js';
|
|
6
6
|
import { pkg } from './constants.js';
|
|
7
|
+
const FIELD_CASE_CHOICES = ['camel', 'snake'];
|
|
7
8
|
export default async function cli(argv = process.argv.slice(2)) {
|
|
8
9
|
const parser = yargs(argv)
|
|
9
10
|
.usage(`${pkg.name}\n${pkg.description}\n\nUsage:\nrunme2ts ./path/to/spec.cddl &> ./path/to/interface.ts`)
|
|
@@ -14,6 +15,12 @@ export default async function cli(argv = process.argv.slice(2)) {
|
|
|
14
15
|
type: 'boolean',
|
|
15
16
|
description: 'Use unknown instead of any',
|
|
16
17
|
default: false
|
|
18
|
+
})
|
|
19
|
+
.option('field-case', {
|
|
20
|
+
choices: FIELD_CASE_CHOICES,
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Case for generated interface fields',
|
|
23
|
+
default: 'camel'
|
|
17
24
|
})
|
|
18
25
|
.help('help')
|
|
19
26
|
.alias('h', 'help')
|
|
@@ -30,5 +37,8 @@ export default async function cli(argv = process.argv.slice(2)) {
|
|
|
30
37
|
return process.exit(1);
|
|
31
38
|
}
|
|
32
39
|
const ast = parse(absoluteFilePath);
|
|
33
|
-
console.log(transform(ast, {
|
|
40
|
+
console.log(transform(ast, {
|
|
41
|
+
useUnknown: args.u,
|
|
42
|
+
fieldCase: args.fieldCase
|
|
43
|
+
}));
|
|
34
44
|
}
|
package/build/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Assignment } from 'cddl';
|
|
2
|
+
export type FieldCase = 'camel' | 'snake';
|
|
2
3
|
export interface TransformOptions {
|
|
3
4
|
useUnknown?: boolean;
|
|
5
|
+
fieldCase?: FieldCase;
|
|
4
6
|
}
|
|
5
7
|
export declare function transform(assignments: Assignment[], options?: TransformOptions): string;
|
|
6
8
|
//# 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,EAWH,KAAK,UAAU,EAMlB,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAWH,KAAK,UAAU,EAMlB,MAAM,MAAM,CAAA;AAwCb,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;AAIzC,MAAM,WAAW,gBAAgB;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,SAAS,CAAA;CACxB;AAED,wBAAgB,SAAS,CAAE,WAAW,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,gBAAgB,UA8B/E"}
|
package/build/index.js
CHANGED
|
@@ -36,8 +36,14 @@ const RECORD_KEY_TYPES = new Set([
|
|
|
36
36
|
'float', 'float16', 'float32', 'float64', 'float16-32', 'float32-64',
|
|
37
37
|
'str', 'text', 'tstr'
|
|
38
38
|
]);
|
|
39
|
+
const IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
39
40
|
export function transform(assignments, options) {
|
|
40
|
-
|
|
41
|
+
const transformOptions = {
|
|
42
|
+
useUnknown: false,
|
|
43
|
+
fieldCase: 'camel',
|
|
44
|
+
...options
|
|
45
|
+
};
|
|
46
|
+
if (transformOptions.useUnknown) {
|
|
41
47
|
NATIVE_TYPES.any = b.tsUnknownKeyword();
|
|
42
48
|
}
|
|
43
49
|
else {
|
|
@@ -49,7 +55,7 @@ export function transform(assignments, options) {
|
|
|
49
55
|
sourceRoot: process.cwd()
|
|
50
56
|
});
|
|
51
57
|
for (const assignment of assignments) {
|
|
52
|
-
const statement = parseAssignment(assignment);
|
|
58
|
+
const statement = parseAssignment(assignment, transformOptions);
|
|
53
59
|
if (!statement) {
|
|
54
60
|
continue;
|
|
55
61
|
}
|
|
@@ -72,7 +78,27 @@ function isExtensibleRecordProperty(prop) {
|
|
|
72
78
|
!prop.HasCut &&
|
|
73
79
|
RECORD_KEY_TYPES.has(prop.Name);
|
|
74
80
|
}
|
|
75
|
-
function
|
|
81
|
+
function toSnakeCase(name) {
|
|
82
|
+
return name
|
|
83
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
84
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')
|
|
85
|
+
.replace(/[^A-Za-z0-9_$]+/g, '_')
|
|
86
|
+
.replace(/_+/g, '_')
|
|
87
|
+
.replace(/^_+|_+$/g, '')
|
|
88
|
+
.toLowerCase();
|
|
89
|
+
}
|
|
90
|
+
function formatFieldName(name, fieldCase) {
|
|
91
|
+
return fieldCase === 'snake'
|
|
92
|
+
? toSnakeCase(name)
|
|
93
|
+
: camelcase(name);
|
|
94
|
+
}
|
|
95
|
+
function createPropertyKey(name, options) {
|
|
96
|
+
const fieldName = formatFieldName(name, options.fieldCase);
|
|
97
|
+
return IDENTIFIER_PATTERN.test(fieldName)
|
|
98
|
+
? b.identifier(fieldName)
|
|
99
|
+
: b.stringLiteral(fieldName);
|
|
100
|
+
}
|
|
101
|
+
function parseAssignment(assignment, options) {
|
|
76
102
|
if (isVariable(assignment)) {
|
|
77
103
|
const propType = Array.isArray(assignment.PropertyType)
|
|
78
104
|
? assignment.PropertyType
|
|
@@ -84,7 +110,7 @@ function parseAssignment(assignment) {
|
|
|
84
110
|
typeParameters = b.tsNumberKeyword();
|
|
85
111
|
}
|
|
86
112
|
else {
|
|
87
|
-
typeParameters = b.tsUnionType(propType.map(parseUnionType));
|
|
113
|
+
typeParameters = b.tsUnionType(propType.map((prop) => parseUnionType(prop, options)));
|
|
88
114
|
}
|
|
89
115
|
const expr = b.tsTypeAliasDeclaration(id, typeParameters);
|
|
90
116
|
expr.comments = getAssignmentComments(assignment);
|
|
@@ -114,7 +140,7 @@ function parseAssignment(assignment) {
|
|
|
114
140
|
choiceOptions.push(nextProp);
|
|
115
141
|
i++; // Skip next property
|
|
116
142
|
}
|
|
117
|
-
const
|
|
143
|
+
const choiceTypes = choiceOptions.map(p => {
|
|
118
144
|
// If p is a group reference (Name ''), it's a TypeReference
|
|
119
145
|
// e.g. SessionAutodetectProxyConfiguration // SessionDirectProxyConfiguration
|
|
120
146
|
// The parser sometimes wraps it in an array, sometimes not (if inside a choice)
|
|
@@ -125,12 +151,12 @@ function parseAssignment(assignment) {
|
|
|
125
151
|
if (isNamedGroupReference(typeVal)) {
|
|
126
152
|
return b.tsTypeReference(b.identifier(pascalCase(typeVal.Value || typeVal.Type)));
|
|
127
153
|
}
|
|
128
|
-
return parseUnionType(typeVal);
|
|
154
|
+
return parseUnionType(typeVal, options);
|
|
129
155
|
}
|
|
130
156
|
// Otherwise it is an object literal with this property
|
|
131
|
-
return b.tsTypeLiteral(parseObjectType([p]));
|
|
157
|
+
return b.tsTypeLiteral(parseObjectType([p], options));
|
|
132
158
|
});
|
|
133
|
-
intersections.push(b.tsUnionType(
|
|
159
|
+
intersections.push(b.tsUnionType(choiceTypes));
|
|
134
160
|
}
|
|
135
161
|
else {
|
|
136
162
|
staticProps.push(prop);
|
|
@@ -141,12 +167,12 @@ function parseAssignment(assignment) {
|
|
|
141
167
|
const mixins = staticProps.filter(isUnNamedProperty);
|
|
142
168
|
const ownProps = staticProps.filter(p => !isUnNamedProperty(p));
|
|
143
169
|
if (ownProps.length > 0) {
|
|
144
|
-
intersections.unshift(b.tsTypeLiteral(parseObjectType(ownProps)));
|
|
170
|
+
intersections.unshift(b.tsTypeLiteral(parseObjectType(ownProps, options)));
|
|
145
171
|
}
|
|
146
172
|
for (const mixin of mixins) {
|
|
147
173
|
if (Array.isArray(mixin.Type) && mixin.Type.length > 1) {
|
|
148
|
-
const
|
|
149
|
-
intersections.push(b.tsUnionType(
|
|
174
|
+
const choices = mixin.Type.map((type) => parseUnionType(type, options));
|
|
175
|
+
intersections.push(b.tsUnionType(choices));
|
|
150
176
|
}
|
|
151
177
|
else {
|
|
152
178
|
const typeVal = Array.isArray(mixin.Type) ? mixin.Type[0] : mixin.Type;
|
|
@@ -180,7 +206,7 @@ function parseAssignment(assignment) {
|
|
|
180
206
|
const prop = props[0];
|
|
181
207
|
const propType = Array.isArray(prop.Type) ? prop.Type : [prop.Type];
|
|
182
208
|
if (propType.length === 1 && RECORD_KEY_TYPES.has(prop.Name)) {
|
|
183
|
-
const value = parseUnionType(assignment);
|
|
209
|
+
const value = parseUnionType(assignment, options);
|
|
184
210
|
const expr = b.tsTypeAliasDeclaration(id, value);
|
|
185
211
|
expr.comments = getAssignmentComments(assignment);
|
|
186
212
|
return exportWithComments(expr);
|
|
@@ -225,10 +251,10 @@ function parseAssignment(assignment) {
|
|
|
225
251
|
const choices = [];
|
|
226
252
|
for (const prop of group.Properties) {
|
|
227
253
|
// Choices are wrapped in arrays in the properties
|
|
228
|
-
const
|
|
229
|
-
if (
|
|
254
|
+
const choiceProps = Array.isArray(prop) ? prop : [prop];
|
|
255
|
+
if (choiceProps.length > 1) { // It's a choice within the mixin group
|
|
230
256
|
const unionOptions = [];
|
|
231
|
-
for (const option of
|
|
257
|
+
for (const option of choiceProps) {
|
|
232
258
|
let refName;
|
|
233
259
|
const type = option.Type;
|
|
234
260
|
if (typeof type === 'string')
|
|
@@ -260,7 +286,7 @@ function parseAssignment(assignment) {
|
|
|
260
286
|
continue;
|
|
261
287
|
}
|
|
262
288
|
}
|
|
263
|
-
for (const option of
|
|
289
|
+
for (const option of choiceProps) {
|
|
264
290
|
let refName;
|
|
265
291
|
const type = option.Type;
|
|
266
292
|
if (typeof type === 'string') {
|
|
@@ -355,7 +381,7 @@ function parseAssignment(assignment) {
|
|
|
355
381
|
}
|
|
356
382
|
else if (type && typeof type === 'object') {
|
|
357
383
|
if (isGroup(type) && Array.isArray(type.Properties)) {
|
|
358
|
-
choices.push(b.tsTypeLiteral(parseObjectType(type.Properties)));
|
|
384
|
+
choices.push(b.tsTypeLiteral(parseObjectType(type.Properties, options)));
|
|
359
385
|
continue;
|
|
360
386
|
}
|
|
361
387
|
refName = isNamedGroupReference(type)
|
|
@@ -390,7 +416,7 @@ function parseAssignment(assignment) {
|
|
|
390
416
|
}
|
|
391
417
|
const ownProps = props.filter(p => !isUnNamedProperty(p));
|
|
392
418
|
if (ownProps.length > 0) {
|
|
393
|
-
intersections.push(b.tsTypeLiteral(parseObjectType(ownProps)));
|
|
419
|
+
intersections.push(b.tsTypeLiteral(parseObjectType(ownProps, options)));
|
|
394
420
|
}
|
|
395
421
|
let value;
|
|
396
422
|
if (intersections.length === 1) {
|
|
@@ -404,7 +430,7 @@ function parseAssignment(assignment) {
|
|
|
404
430
|
return exportWithComments(expr);
|
|
405
431
|
}
|
|
406
432
|
// Fallback to interface if no mixins (pure object)
|
|
407
|
-
const objectType = parseObjectType(props);
|
|
433
|
+
const objectType = parseObjectType(props, options);
|
|
408
434
|
const expr = b.tsInterfaceDeclaration(id, b.tsInterfaceBody(objectType));
|
|
409
435
|
expr.comments = getAssignmentComments(assignment);
|
|
410
436
|
return exportWithComments(expr);
|
|
@@ -419,7 +445,7 @@ function parseAssignment(assignment) {
|
|
|
419
445
|
// We need to parse each choice.
|
|
420
446
|
const obj = assignmentValues.map((prop) => {
|
|
421
447
|
const t = Array.isArray(prop.Type) ? prop.Type[0] : prop.Type;
|
|
422
|
-
return parseUnionType(t);
|
|
448
|
+
return parseUnionType(t, options);
|
|
423
449
|
});
|
|
424
450
|
const value = b.tsArrayType(b.tsParenthesizedType(b.tsUnionType(obj)));
|
|
425
451
|
const expr = b.tsTypeAliasDeclaration(id, value);
|
|
@@ -429,10 +455,10 @@ function parseAssignment(assignment) {
|
|
|
429
455
|
// Standard array
|
|
430
456
|
const firstType = assignmentValues.Type;
|
|
431
457
|
const obj = Array.isArray(firstType)
|
|
432
|
-
? firstType.map(parseUnionType)
|
|
458
|
+
? firstType.map((type) => parseUnionType(type, options))
|
|
433
459
|
: isCDDLArray(firstType)
|
|
434
|
-
? firstType.Values.map((val) => parseUnionType(Array.isArray(val.Type) ? val.Type[0] : val.Type))
|
|
435
|
-
: [parseUnionType(firstType)];
|
|
460
|
+
? firstType.Values.map((val) => parseUnionType(Array.isArray(val.Type) ? val.Type[0] : val.Type, options))
|
|
461
|
+
: [parseUnionType(firstType, options)];
|
|
436
462
|
const value = b.tsArrayType(obj.length === 1
|
|
437
463
|
? obj[0]
|
|
438
464
|
: b.tsParenthesizedType(b.tsUnionType(obj)));
|
|
@@ -442,7 +468,7 @@ function parseAssignment(assignment) {
|
|
|
442
468
|
}
|
|
443
469
|
throw new Error(`Unknown assignment type "${assignment.Type}"`);
|
|
444
470
|
}
|
|
445
|
-
function parseObjectType(props) {
|
|
471
|
+
function parseObjectType(props, options) {
|
|
446
472
|
const propItems = [];
|
|
447
473
|
for (const prop of props) {
|
|
448
474
|
/**
|
|
@@ -465,7 +491,7 @@ function parseObjectType(props) {
|
|
|
465
491
|
const keyIdentifier = b.identifier('key');
|
|
466
492
|
keyIdentifier.typeAnnotation = b.tsTypeAnnotation(NATIVE_TYPES[prop.Name]);
|
|
467
493
|
const indexSignature = b.tsIndexSignature([keyIdentifier], b.tsTypeAnnotation(b.tsUnionType([
|
|
468
|
-
...cddlType.map((t) => parseUnionType(t)),
|
|
494
|
+
...cddlType.map((t) => parseUnionType(t, options)),
|
|
469
495
|
b.tsUndefinedKeyword()
|
|
470
496
|
])));
|
|
471
497
|
indexSignature.comments = comments.length
|
|
@@ -474,14 +500,14 @@ function parseObjectType(props) {
|
|
|
474
500
|
propItems.push(indexSignature);
|
|
475
501
|
continue;
|
|
476
502
|
}
|
|
477
|
-
const id =
|
|
503
|
+
const id = createPropertyKey(prop.Name, options);
|
|
478
504
|
if (prop.Operator && prop.Operator.Type === 'default') {
|
|
479
505
|
const defaultValue = parseDefaultValue(prop.Operator);
|
|
480
506
|
defaultValue && comments.length && comments.push(''); // add empty line if we have previous comments
|
|
481
507
|
defaultValue && comments.push(` @default ${defaultValue}`);
|
|
482
508
|
}
|
|
483
509
|
const type = cddlType.map((t) => {
|
|
484
|
-
const unionType = parseUnionType(t);
|
|
510
|
+
const unionType = parseUnionType(t, options);
|
|
485
511
|
if (unionType) {
|
|
486
512
|
const defaultValue = parseDefaultValue(t.Operator);
|
|
487
513
|
defaultValue && comments.length && comments.push(''); // add empty line if we have previous comments
|
|
@@ -498,7 +524,7 @@ function parseObjectType(props) {
|
|
|
498
524
|
}
|
|
499
525
|
return propItems;
|
|
500
526
|
}
|
|
501
|
-
function parseUnionType(t) {
|
|
527
|
+
function parseUnionType(t, options) {
|
|
502
528
|
if (typeof t === 'string') {
|
|
503
529
|
if (!NATIVE_TYPES[t]) {
|
|
504
530
|
throw new Error(`Unknown native type: "${t}`);
|
|
@@ -524,31 +550,31 @@ function parseUnionType(t) {
|
|
|
524
550
|
* Check if we have choices in the group (arrays of Properties)
|
|
525
551
|
*/
|
|
526
552
|
if (prop.some(p => Array.isArray(p))) {
|
|
527
|
-
const
|
|
553
|
+
const choices = [];
|
|
528
554
|
for (const choice of prop) {
|
|
529
555
|
const subProps = Array.isArray(choice) ? choice : [choice];
|
|
530
556
|
if (subProps.length === 1 && isUnNamedProperty(subProps[0])) {
|
|
531
557
|
const first = subProps[0];
|
|
532
558
|
const subType = Array.isArray(first.Type) ? first.Type[0] : first.Type;
|
|
533
|
-
|
|
559
|
+
choices.push(parseUnionType(subType, options));
|
|
534
560
|
continue;
|
|
535
561
|
}
|
|
536
562
|
if (subProps.every(isUnNamedProperty)) {
|
|
537
563
|
const tupleItems = subProps.map((p) => {
|
|
538
564
|
const subType = Array.isArray(p.Type) ? p.Type[0] : p.Type;
|
|
539
|
-
return parseUnionType(subType);
|
|
565
|
+
return parseUnionType(subType, options);
|
|
540
566
|
});
|
|
541
|
-
|
|
567
|
+
choices.push(b.tsTupleType(tupleItems));
|
|
542
568
|
continue;
|
|
543
569
|
}
|
|
544
|
-
|
|
570
|
+
choices.push(b.tsTypeLiteral(parseObjectType(subProps, options)));
|
|
545
571
|
}
|
|
546
|
-
return b.tsUnionType(
|
|
572
|
+
return b.tsUnionType(choices);
|
|
547
573
|
}
|
|
548
574
|
if (prop.every(isUnNamedProperty)) {
|
|
549
575
|
const items = prop.map(p => {
|
|
550
576
|
const t = Array.isArray(p.Type) ? p.Type[0] : p.Type;
|
|
551
|
-
return parseUnionType(t);
|
|
577
|
+
return parseUnionType(t, options);
|
|
552
578
|
});
|
|
553
579
|
if (items.length === 1)
|
|
554
580
|
return items[0];
|
|
@@ -560,13 +586,13 @@ function parseUnionType(t) {
|
|
|
560
586
|
if (prop.length === 1 && RECORD_KEY_TYPES.has(prop[0].Name)) {
|
|
561
587
|
return b.tsTypeReference(b.identifier('Record'), b.tsTypeParameterInstantiation([
|
|
562
588
|
NATIVE_TYPES[prop[0].Name],
|
|
563
|
-
parseUnionType(prop[0].Type[0])
|
|
589
|
+
parseUnionType(prop[0].Type[0], options)
|
|
564
590
|
]));
|
|
565
591
|
}
|
|
566
592
|
/**
|
|
567
593
|
* e.g. ?attributes: {*foo => text},
|
|
568
594
|
*/
|
|
569
|
-
return b.tsTypeLiteral(parseObjectType(t.Properties));
|
|
595
|
+
return b.tsTypeLiteral(parseObjectType(t.Properties, options));
|
|
570
596
|
}
|
|
571
597
|
else if (isNamedGroupReference(t)) {
|
|
572
598
|
return b.tsTypeReference(b.identifier(pascalCase(t.Value)));
|
package/cli-examples/remote.ts
CHANGED
|
@@ -385,9 +385,9 @@ export interface BrowsingContextPrintParameters {
|
|
|
385
385
|
shrinkToFit?: boolean;
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
|
|
388
|
+
// Minimum size is 1pt x 1pt. Conversion follows from
|
|
389
389
|
// https://www.w3.org/TR/css3-values/#absolute-lengths
|
|
390
|
-
interface BrowsingContextPrintMarginParameters {
|
|
390
|
+
export interface BrowsingContextPrintMarginParameters {
|
|
391
391
|
/**
|
|
392
392
|
* @default 1
|
|
393
393
|
*/
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -4,9 +4,11 @@ import yargs from 'yargs'
|
|
|
4
4
|
|
|
5
5
|
import { parse } from 'cddl'
|
|
6
6
|
|
|
7
|
-
import { transform } from './index.js'
|
|
7
|
+
import { transform, type FieldCase } from './index.js'
|
|
8
8
|
import { pkg } from './constants.js'
|
|
9
9
|
|
|
10
|
+
const FIELD_CASE_CHOICES = ['camel', 'snake'] as const satisfies readonly FieldCase[]
|
|
11
|
+
|
|
10
12
|
export default async function cli (argv = process.argv.slice(2)) {
|
|
11
13
|
const parser = yargs(argv)
|
|
12
14
|
.usage(`${pkg.name}\n${pkg.description}\n\nUsage:\nrunme2ts ./path/to/spec.cddl &> ./path/to/interface.ts`)
|
|
@@ -18,6 +20,12 @@ export default async function cli (argv = process.argv.slice(2)) {
|
|
|
18
20
|
description: 'Use unknown instead of any',
|
|
19
21
|
default: false
|
|
20
22
|
})
|
|
23
|
+
.option('field-case', {
|
|
24
|
+
choices: FIELD_CASE_CHOICES,
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Case for generated interface fields',
|
|
27
|
+
default: 'camel'
|
|
28
|
+
})
|
|
21
29
|
.help('help')
|
|
22
30
|
.alias('h', 'help')
|
|
23
31
|
.alias('v', 'version')
|
|
@@ -38,5 +46,8 @@ export default async function cli (argv = process.argv.slice(2)) {
|
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
const ast = parse(absoluteFilePath)
|
|
41
|
-
console.log(transform(ast, {
|
|
49
|
+
console.log(transform(ast, {
|
|
50
|
+
useUnknown: args.u as boolean,
|
|
51
|
+
fieldCase: args.fieldCase as FieldCase
|
|
52
|
+
}))
|
|
42
53
|
}
|
package/src/index.ts
CHANGED
|
@@ -59,13 +59,23 @@ const RECORD_KEY_TYPES = new Set([
|
|
|
59
59
|
type ObjectEntry = types.namedTypes.TSCallSignatureDeclaration | types.namedTypes.TSConstructSignatureDeclaration | types.namedTypes.TSIndexSignature | types.namedTypes.TSMethodSignature | types.namedTypes.TSPropertySignature
|
|
60
60
|
type ObjectBody = ObjectEntry[]
|
|
61
61
|
type TSTypeKind = types.namedTypes.TSAsExpression['typeAnnotation']
|
|
62
|
+
export type FieldCase = 'camel' | 'snake'
|
|
63
|
+
type TransformSettings = Required<TransformOptions>
|
|
64
|
+
const IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/
|
|
62
65
|
|
|
63
66
|
export interface TransformOptions {
|
|
64
67
|
useUnknown?: boolean
|
|
68
|
+
fieldCase?: FieldCase
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
export function transform (assignments: Assignment[], options?: TransformOptions) {
|
|
68
|
-
|
|
72
|
+
const transformOptions: TransformSettings = {
|
|
73
|
+
useUnknown: false,
|
|
74
|
+
fieldCase: 'camel',
|
|
75
|
+
...options
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (transformOptions.useUnknown) {
|
|
69
79
|
NATIVE_TYPES.any = b.tsUnknownKeyword()
|
|
70
80
|
} else {
|
|
71
81
|
NATIVE_TYPES.any = b.tsAnyKeyword()
|
|
@@ -81,7 +91,7 @@ export function transform (assignments: Assignment[], options?: TransformOptions
|
|
|
81
91
|
) satisfies types.namedTypes.File
|
|
82
92
|
|
|
83
93
|
for (const assignment of assignments) {
|
|
84
|
-
const statement = parseAssignment(assignment)
|
|
94
|
+
const statement = parseAssignment(assignment, transformOptions)
|
|
85
95
|
if (!statement) {
|
|
86
96
|
continue
|
|
87
97
|
}
|
|
@@ -111,7 +121,30 @@ function isExtensibleRecordProperty (prop: Property) {
|
|
|
111
121
|
RECORD_KEY_TYPES.has(prop.Name)
|
|
112
122
|
}
|
|
113
123
|
|
|
114
|
-
function
|
|
124
|
+
function toSnakeCase (name: string) {
|
|
125
|
+
return name
|
|
126
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
127
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')
|
|
128
|
+
.replace(/[^A-Za-z0-9_$]+/g, '_')
|
|
129
|
+
.replace(/_+/g, '_')
|
|
130
|
+
.replace(/^_+|_+$/g, '')
|
|
131
|
+
.toLowerCase()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function formatFieldName (name: string, fieldCase: FieldCase) {
|
|
135
|
+
return fieldCase === 'snake'
|
|
136
|
+
? toSnakeCase(name)
|
|
137
|
+
: camelcase(name)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function createPropertyKey (name: string, options: TransformSettings) {
|
|
141
|
+
const fieldName = formatFieldName(name, options.fieldCase)
|
|
142
|
+
return IDENTIFIER_PATTERN.test(fieldName)
|
|
143
|
+
? b.identifier(fieldName)
|
|
144
|
+
: b.stringLiteral(fieldName)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parseAssignment (assignment: Assignment, options: TransformSettings) {
|
|
115
148
|
if (isVariable(assignment)) {
|
|
116
149
|
const propType = Array.isArray(assignment.PropertyType)
|
|
117
150
|
? assignment.PropertyType
|
|
@@ -124,7 +157,7 @@ function parseAssignment (assignment: Assignment) {
|
|
|
124
157
|
if (propType.length === 1 && propType[0].Type === 'range') {
|
|
125
158
|
typeParameters = b.tsNumberKeyword()
|
|
126
159
|
} else {
|
|
127
|
-
typeParameters = b.tsUnionType(propType.map(parseUnionType))
|
|
160
|
+
typeParameters = b.tsUnionType(propType.map((prop) => parseUnionType(prop, options)))
|
|
128
161
|
}
|
|
129
162
|
|
|
130
163
|
const expr = b.tsTypeAliasDeclaration(id, typeParameters)
|
|
@@ -161,7 +194,7 @@ function parseAssignment (assignment: Assignment) {
|
|
|
161
194
|
i++ // Skip next property
|
|
162
195
|
}
|
|
163
196
|
|
|
164
|
-
const
|
|
197
|
+
const choiceTypes = choiceOptions.map(p => {
|
|
165
198
|
// If p is a group reference (Name ''), it's a TypeReference
|
|
166
199
|
// e.g. SessionAutodetectProxyConfiguration // SessionDirectProxyConfiguration
|
|
167
200
|
// The parser sometimes wraps it in an array, sometimes not (if inside a choice)
|
|
@@ -175,12 +208,12 @@ function parseAssignment (assignment: Assignment) {
|
|
|
175
208
|
b.identifier(pascalCase(typeVal.Value || typeVal.Type))
|
|
176
209
|
)
|
|
177
210
|
}
|
|
178
|
-
return parseUnionType(typeVal)
|
|
211
|
+
return parseUnionType(typeVal, options)
|
|
179
212
|
}
|
|
180
213
|
// Otherwise it is an object literal with this property
|
|
181
|
-
return b.tsTypeLiteral(parseObjectType([p]))
|
|
214
|
+
return b.tsTypeLiteral(parseObjectType([p], options))
|
|
182
215
|
})
|
|
183
|
-
intersections.push(b.tsUnionType(
|
|
216
|
+
intersections.push(b.tsUnionType(choiceTypes))
|
|
184
217
|
} else {
|
|
185
218
|
staticProps.push(prop)
|
|
186
219
|
}
|
|
@@ -192,13 +225,13 @@ function parseAssignment (assignment: Assignment) {
|
|
|
192
225
|
const ownProps = staticProps.filter(p => !isUnNamedProperty(p))
|
|
193
226
|
|
|
194
227
|
if (ownProps.length > 0) {
|
|
195
|
-
intersections.unshift(b.tsTypeLiteral(parseObjectType(ownProps)))
|
|
228
|
+
intersections.unshift(b.tsTypeLiteral(parseObjectType(ownProps, options)))
|
|
196
229
|
}
|
|
197
230
|
|
|
198
231
|
for (const mixin of mixins) {
|
|
199
232
|
if (Array.isArray(mixin.Type) && mixin.Type.length > 1) {
|
|
200
|
-
const
|
|
201
|
-
intersections.push(b.tsUnionType(
|
|
233
|
+
const choices = mixin.Type.map((type) => parseUnionType(type, options))
|
|
234
|
+
intersections.push(b.tsUnionType(choices))
|
|
202
235
|
} else {
|
|
203
236
|
const typeVal = Array.isArray(mixin.Type) ? mixin.Type[0] : mixin.Type
|
|
204
237
|
if (isNamedGroupReference(typeVal)) {
|
|
@@ -235,7 +268,7 @@ function parseAssignment (assignment: Assignment) {
|
|
|
235
268
|
const prop = props[0]
|
|
236
269
|
const propType = Array.isArray(prop.Type) ? prop.Type : [prop.Type]
|
|
237
270
|
if (propType.length === 1 && RECORD_KEY_TYPES.has(prop.Name)) {
|
|
238
|
-
const value = parseUnionType(assignment)
|
|
271
|
+
const value = parseUnionType(assignment, options)
|
|
239
272
|
const expr = b.tsTypeAliasDeclaration(id, value)
|
|
240
273
|
expr.comments = getAssignmentComments(assignment)
|
|
241
274
|
return exportWithComments(expr)
|
|
@@ -284,10 +317,10 @@ function parseAssignment (assignment: Assignment) {
|
|
|
284
317
|
|
|
285
318
|
for (const prop of group.Properties) {
|
|
286
319
|
// Choices are wrapped in arrays in the properties
|
|
287
|
-
const
|
|
288
|
-
if (
|
|
320
|
+
const choiceProps = Array.isArray(prop) ? prop : [prop]
|
|
321
|
+
if (choiceProps.length > 1) { // It's a choice within the mixin group
|
|
289
322
|
const unionOptions: any[] = []
|
|
290
|
-
for (const option of
|
|
323
|
+
for (const option of choiceProps) {
|
|
291
324
|
let refName: string | undefined
|
|
292
325
|
const type = option.Type
|
|
293
326
|
if (typeof type === 'string') refName = type
|
|
@@ -314,7 +347,7 @@ function parseAssignment (assignment: Assignment) {
|
|
|
314
347
|
}
|
|
315
348
|
}
|
|
316
349
|
|
|
317
|
-
for (const option of
|
|
350
|
+
for (const option of choiceProps) {
|
|
318
351
|
let refName: string | undefined
|
|
319
352
|
const type = option.Type
|
|
320
353
|
|
|
@@ -395,7 +428,7 @@ function parseAssignment (assignment: Assignment) {
|
|
|
395
428
|
}
|
|
396
429
|
} else if (type && typeof type === 'object') {
|
|
397
430
|
if (isGroup(type) && Array.isArray(type.Properties)) {
|
|
398
|
-
choices.push(b.tsTypeLiteral(parseObjectType(type.Properties as Property[])))
|
|
431
|
+
choices.push(b.tsTypeLiteral(parseObjectType(type.Properties as Property[], options)))
|
|
399
432
|
continue
|
|
400
433
|
}
|
|
401
434
|
refName = isNamedGroupReference(type)
|
|
@@ -441,7 +474,7 @@ function parseAssignment (assignment: Assignment) {
|
|
|
441
474
|
|
|
442
475
|
const ownProps = props.filter(p => !isUnNamedProperty(p))
|
|
443
476
|
if (ownProps.length > 0) {
|
|
444
|
-
intersections.push(b.tsTypeLiteral(parseObjectType(ownProps)))
|
|
477
|
+
intersections.push(b.tsTypeLiteral(parseObjectType(ownProps, options)))
|
|
445
478
|
}
|
|
446
479
|
|
|
447
480
|
let value: any
|
|
@@ -457,7 +490,7 @@ function parseAssignment (assignment: Assignment) {
|
|
|
457
490
|
}
|
|
458
491
|
|
|
459
492
|
// Fallback to interface if no mixins (pure object)
|
|
460
|
-
const objectType = parseObjectType(props)
|
|
493
|
+
const objectType = parseObjectType(props, options)
|
|
461
494
|
|
|
462
495
|
const expr = b.tsInterfaceDeclaration(id, b.tsInterfaceBody(objectType))
|
|
463
496
|
expr.comments = getAssignmentComments(assignment)
|
|
@@ -476,7 +509,7 @@ function parseAssignment (assignment: Assignment) {
|
|
|
476
509
|
// We need to parse each choice.
|
|
477
510
|
const obj = assignmentValues.map((prop) => {
|
|
478
511
|
const t = Array.isArray(prop.Type) ? prop.Type[0] : prop.Type
|
|
479
|
-
return parseUnionType(t)
|
|
512
|
+
return parseUnionType(t, options)
|
|
480
513
|
})
|
|
481
514
|
const value = b.tsArrayType(b.tsParenthesizedType(b.tsUnionType(obj)))
|
|
482
515
|
const expr = b.tsTypeAliasDeclaration(id, value)
|
|
@@ -487,10 +520,10 @@ function parseAssignment (assignment: Assignment) {
|
|
|
487
520
|
// Standard array
|
|
488
521
|
const firstType = assignmentValues.Type
|
|
489
522
|
const obj = Array.isArray(firstType)
|
|
490
|
-
? firstType.map(parseUnionType)
|
|
523
|
+
? firstType.map((type) => parseUnionType(type, options))
|
|
491
524
|
: isCDDLArray(firstType)
|
|
492
|
-
? firstType.Values.map((val: any) => parseUnionType(Array.isArray(val.Type) ? val.Type[0] : val.Type))
|
|
493
|
-
: [parseUnionType(firstType)]
|
|
525
|
+
? firstType.Values.map((val: any) => parseUnionType(Array.isArray(val.Type) ? val.Type[0] : val.Type, options))
|
|
526
|
+
: [parseUnionType(firstType, options)]
|
|
494
527
|
|
|
495
528
|
const value = b.tsArrayType(
|
|
496
529
|
obj.length === 1
|
|
@@ -505,7 +538,7 @@ function parseAssignment (assignment: Assignment) {
|
|
|
505
538
|
throw new Error(`Unknown assignment type "${(assignment as any).Type}"`)
|
|
506
539
|
}
|
|
507
540
|
|
|
508
|
-
function parseObjectType (props: Property[]): ObjectBody {
|
|
541
|
+
function parseObjectType (props: Property[], options: TransformSettings): ObjectBody {
|
|
509
542
|
const propItems: ObjectBody = []
|
|
510
543
|
for (const prop of props) {
|
|
511
544
|
/**
|
|
@@ -534,7 +567,7 @@ function parseObjectType (props: Property[]): ObjectBody {
|
|
|
534
567
|
[keyIdentifier],
|
|
535
568
|
b.tsTypeAnnotation(
|
|
536
569
|
b.tsUnionType([
|
|
537
|
-
...cddlType.map((t) => parseUnionType(t)),
|
|
570
|
+
...cddlType.map((t) => parseUnionType(t, options)),
|
|
538
571
|
b.tsUndefinedKeyword()
|
|
539
572
|
])
|
|
540
573
|
)
|
|
@@ -546,7 +579,7 @@ function parseObjectType (props: Property[]): ObjectBody {
|
|
|
546
579
|
continue
|
|
547
580
|
}
|
|
548
581
|
|
|
549
|
-
const id =
|
|
582
|
+
const id = createPropertyKey(prop.Name, options)
|
|
550
583
|
|
|
551
584
|
if (prop.Operator && prop.Operator.Type === 'default') {
|
|
552
585
|
const defaultValue = parseDefaultValue(prop.Operator)
|
|
@@ -555,7 +588,7 @@ function parseObjectType (props: Property[]): ObjectBody {
|
|
|
555
588
|
}
|
|
556
589
|
|
|
557
590
|
const type = cddlType.map((t) => {
|
|
558
|
-
const unionType = parseUnionType(t)
|
|
591
|
+
const unionType = parseUnionType(t, options)
|
|
559
592
|
if (unionType) {
|
|
560
593
|
const defaultValue = parseDefaultValue((t as PropertyReference).Operator)
|
|
561
594
|
defaultValue && comments.length && comments.push('') // add empty line if we have previous comments
|
|
@@ -577,7 +610,7 @@ function parseObjectType (props: Property[]): ObjectBody {
|
|
|
577
610
|
return propItems
|
|
578
611
|
}
|
|
579
612
|
|
|
580
|
-
function parseUnionType (t: PropertyType | Assignment): TSTypeKind {
|
|
613
|
+
function parseUnionType (t: PropertyType | Assignment, options: TransformSettings): TSTypeKind {
|
|
581
614
|
if (typeof t === 'string') {
|
|
582
615
|
if (!NATIVE_TYPES[t]) {
|
|
583
616
|
throw new Error(`Unknown native type: "${t}`)
|
|
@@ -600,35 +633,35 @@ function parseUnionType (t: PropertyType | Assignment): TSTypeKind {
|
|
|
600
633
|
* Check if we have choices in the group (arrays of Properties)
|
|
601
634
|
*/
|
|
602
635
|
if (prop.some(p => Array.isArray(p))) {
|
|
603
|
-
const
|
|
636
|
+
const choices: TSTypeKind[] = []
|
|
604
637
|
for (const choice of prop) {
|
|
605
638
|
const subProps = Array.isArray(choice) ? choice : [choice]
|
|
606
639
|
|
|
607
640
|
if (subProps.length === 1 && isUnNamedProperty(subProps[0])) {
|
|
608
641
|
const first = subProps[0]
|
|
609
642
|
const subType = Array.isArray(first.Type) ? first.Type[0] : first.Type
|
|
610
|
-
|
|
643
|
+
choices.push(parseUnionType(subType as PropertyType, options))
|
|
611
644
|
continue
|
|
612
645
|
}
|
|
613
646
|
|
|
614
647
|
if (subProps.every(isUnNamedProperty)) {
|
|
615
648
|
const tupleItems = subProps.map((p) => {
|
|
616
649
|
const subType = Array.isArray(p.Type) ? p.Type[0] : p.Type
|
|
617
|
-
return parseUnionType(subType as PropertyType)
|
|
650
|
+
return parseUnionType(subType as PropertyType, options)
|
|
618
651
|
})
|
|
619
|
-
|
|
652
|
+
choices.push(b.tsTupleType(tupleItems))
|
|
620
653
|
continue
|
|
621
654
|
}
|
|
622
655
|
|
|
623
|
-
|
|
656
|
+
choices.push(b.tsTypeLiteral(parseObjectType(subProps, options)))
|
|
624
657
|
}
|
|
625
|
-
return b.tsUnionType(
|
|
658
|
+
return b.tsUnionType(choices)
|
|
626
659
|
}
|
|
627
660
|
|
|
628
661
|
if ((prop as Property[]).every(isUnNamedProperty)) {
|
|
629
662
|
const items = (prop as Property[]).map(p => {
|
|
630
663
|
const t = Array.isArray(p.Type) ? p.Type[0] : p.Type
|
|
631
|
-
return parseUnionType(t as PropertyType)
|
|
664
|
+
return parseUnionType(t as PropertyType, options)
|
|
632
665
|
})
|
|
633
666
|
|
|
634
667
|
if (items.length === 1) return items[0];
|
|
@@ -643,7 +676,7 @@ function parseUnionType (t: PropertyType | Assignment): TSTypeKind {
|
|
|
643
676
|
b.identifier('Record'),
|
|
644
677
|
b.tsTypeParameterInstantiation([
|
|
645
678
|
NATIVE_TYPES[(prop[0] as Property).Name],
|
|
646
|
-
parseUnionType(((prop[0] as Property).Type as PropertyType[])[0])
|
|
679
|
+
parseUnionType(((prop[0] as Property).Type as PropertyType[])[0], options)
|
|
647
680
|
])
|
|
648
681
|
)
|
|
649
682
|
}
|
|
@@ -651,7 +684,7 @@ function parseUnionType (t: PropertyType | Assignment): TSTypeKind {
|
|
|
651
684
|
/**
|
|
652
685
|
* e.g. ?attributes: {*foo => text},
|
|
653
686
|
*/
|
|
654
|
-
return b.tsTypeLiteral(parseObjectType(t.Properties as Property[]))
|
|
687
|
+
return b.tsTypeLiteral(parseObjectType(t.Properties as Property[], options))
|
|
655
688
|
} else if (isNamedGroupReference(t)) {
|
|
656
689
|
return b.tsTypeReference(
|
|
657
690
|
b.identifier(pascalCase(t.Value))
|
package/tests/mod.test.ts
CHANGED
|
@@ -66,6 +66,23 @@ describe('cddl2ts', () => {
|
|
|
66
66
|
expect(process.exit).toHaveBeenCalledTimes(0)
|
|
67
67
|
})
|
|
68
68
|
|
|
69
|
+
it('should allow configuring snake_case fields from the CLI', async () => {
|
|
70
|
+
await cli([
|
|
71
|
+
path.join(__dirname, '..', '..', '..', 'examples', 'commons', 'test.cddl'),
|
|
72
|
+
'--field-case',
|
|
73
|
+
'snake'
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
expect(console.log).toHaveBeenCalledTimes(1)
|
|
77
|
+
const output = vi.mocked(console.log).mock.calls[0]?.[0] as string
|
|
78
|
+
expect(output).toContain('export interface SessionCapabilitiesRequest {')
|
|
79
|
+
expect(output).toContain('always_match?: SessionCapabilityRequest;')
|
|
80
|
+
expect(output).toContain('first_match?: SessionCapabilityRequest[];')
|
|
81
|
+
expect(output).toContain('pointer_type?: InputPointerType;')
|
|
82
|
+
expect(output).not.toContain('alwaysMatch?: SessionCapabilityRequest;')
|
|
83
|
+
expect(process.exit).toHaveBeenCalledTimes(0)
|
|
84
|
+
})
|
|
85
|
+
|
|
69
86
|
afterEach(() => {
|
|
70
87
|
process.exit = exitOrig
|
|
71
88
|
console.log = logOrig
|
|
@@ -123,6 +123,35 @@ describe('transform edge cases', () => {
|
|
|
123
123
|
expect(output).toContain('export type MaybeValue = unknown;')
|
|
124
124
|
})
|
|
125
125
|
|
|
126
|
+
it('should keep camelCase fields by default', () => {
|
|
127
|
+
const output = transform([
|
|
128
|
+
group('session-capability-request', [
|
|
129
|
+
property('page_ranges', 'tstr')
|
|
130
|
+
])
|
|
131
|
+
])
|
|
132
|
+
|
|
133
|
+
expect(output).toContain('export interface SessionCapabilityRequest {')
|
|
134
|
+
expect(output).toContain('pageRanges: string;')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should support snake_case fields without changing exported type names', () => {
|
|
138
|
+
const output = transform([
|
|
139
|
+
group('session-capability-request', [
|
|
140
|
+
property('acceptInsecureCerts', 'bool'),
|
|
141
|
+
property('pageRanges', 'tstr'),
|
|
142
|
+
property('nestedConfig', group('', [
|
|
143
|
+
property('requestId', 'uint')
|
|
144
|
+
]))
|
|
145
|
+
])
|
|
146
|
+
], { fieldCase: 'snake' })
|
|
147
|
+
|
|
148
|
+
expect(output).toContain('export interface SessionCapabilityRequest {')
|
|
149
|
+
expect(output).toContain('accept_insecure_certs: boolean;')
|
|
150
|
+
expect(output).toContain('page_ranges: string;')
|
|
151
|
+
expect(output).toContain('nested_config: {')
|
|
152
|
+
expect(output).toContain('request_id: number;')
|
|
153
|
+
})
|
|
154
|
+
|
|
126
155
|
it('should generate intersections for choices with static props and mixins', () => {
|
|
127
156
|
const output = transform([
|
|
128
157
|
group('combined', [
|