@@ -59,13 +59,23 @@ const RECORD_KEY_TYPES = new Set([
5959type ObjectEntry = types . namedTypes . TSCallSignatureDeclaration | types . namedTypes . TSConstructSignatureDeclaration | types . namedTypes . TSIndexSignature | types . namedTypes . TSMethodSignature | types . namedTypes . TSPropertySignature
6060type ObjectBody = ObjectEntry [ ]
6161type TSTypeKind = types . namedTypes . TSAsExpression [ 'typeAnnotation' ]
62+ export type FieldCase = 'camel' | 'snake'
63+ type TransformSettings = Required < TransformOptions >
64+ const IDENTIFIER_PATTERN = / ^ [ A - Z a - z _ $ ] [ A - Z a - z 0 - 9 _ $ ] * $ /
6265
6366export interface TransformOptions {
6467 useUnknown ?: boolean
68+ fieldCase ?: FieldCase
6569}
6670
6771export function transform ( assignments : Assignment [ ] , options ?: TransformOptions ) {
68- if ( options ?. useUnknown ) {
72+ const transformOptions : TransformSettings = {
73+ useUnknown : false ,
74+ fieldCase : 'camel' ,
75+ ...options
76+ }
77+
78+ if ( transformOptions . useUnknown ) {
6979 NATIVE_TYPES . any = b . tsUnknownKeyword ( )
7080 } else {
7181 NATIVE_TYPES . any = b . tsAnyKeyword ( )
@@ -81,7 +91,7 @@ export function transform (assignments: Assignment[], options?: TransformOptions
8191 ) satisfies types . namedTypes . File
8292
8393 for ( const assignment of assignments ) {
84- const statement = parseAssignment ( assignment )
94+ const statement = parseAssignment ( assignment , transformOptions )
8595 if ( ! statement ) {
8696 continue
8797 }
@@ -111,7 +121,30 @@ function isExtensibleRecordProperty (prop: Property) {
111121 RECORD_KEY_TYPES . has ( prop . Name )
112122}
113123
114- function parseAssignment ( assignment : Assignment ) {
124+ function toSnakeCase ( name : string ) {
125+ return name
126+ . replace ( / ( [ a - z 0 - 9 ] ) ( [ A - Z ] ) / g, '$1_$2' )
127+ . replace ( / ( [ A - Z ] ) ( [ A - Z ] [ a - z ] ) / g, '$1_$2' )
128+ . replace ( / [ ^ A - Z a - z 0 - 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 ) {
115148 if ( isVariable ( assignment ) ) {
116149 const propType = Array . isArray ( assignment . PropertyType )
117150 ? assignment . PropertyType
@@ -124,7 +157,7 @@ function parseAssignment (assignment: Assignment) {
124157 if ( propType . length === 1 && propType [ 0 ] . Type === 'range' ) {
125158 typeParameters = b . tsNumberKeyword ( )
126159 } else {
127- typeParameters = b . tsUnionType ( propType . map ( parseUnionType ) )
160+ typeParameters = b . tsUnionType ( propType . map ( ( prop ) => parseUnionType ( prop , options ) ) )
128161 }
129162
130163 const expr = b . tsTypeAliasDeclaration ( id , typeParameters )
@@ -161,7 +194,7 @@ function parseAssignment (assignment: Assignment) {
161194 i ++ // Skip next property
162195 }
163196
164- const options = choiceOptions . map ( p => {
197+ const choiceTypes = choiceOptions . map ( p => {
165198 // If p is a group reference (Name ''), it's a TypeReference
166199 // e.g. SessionAutodetectProxyConfiguration // SessionDirectProxyConfiguration
167200 // The parser sometimes wraps it in an array, sometimes not (if inside a choice)
@@ -175,12 +208,12 @@ function parseAssignment (assignment: Assignment) {
175208 b . identifier ( pascalCase ( typeVal . Value || typeVal . Type ) )
176209 )
177210 }
178- return parseUnionType ( typeVal ) ;
211+ return parseUnionType ( typeVal , options )
179212 }
180213 // Otherwise it is an object literal with this property
181- return b . tsTypeLiteral ( parseObjectType ( [ p ] ) )
214+ return b . tsTypeLiteral ( parseObjectType ( [ p ] , options ) )
182215 } )
183- intersections . push ( b . tsUnionType ( options ) )
216+ intersections . push ( b . tsUnionType ( choiceTypes ) )
184217 } else {
185218 staticProps . push ( prop )
186219 }
@@ -192,13 +225,13 @@ function parseAssignment (assignment: Assignment) {
192225 const ownProps = staticProps . filter ( p => ! isUnNamedProperty ( p ) )
193226
194227 if ( ownProps . length > 0 ) {
195- intersections . unshift ( b . tsTypeLiteral ( parseObjectType ( ownProps ) ) )
228+ intersections . unshift ( b . tsTypeLiteral ( parseObjectType ( ownProps , options ) ) )
196229 }
197230
198231 for ( const mixin of mixins ) {
199232 if ( Array . isArray ( mixin . Type ) && mixin . Type . length > 1 ) {
200- const options = mixin . Type . map ( parseUnionType )
201- intersections . push ( b . tsUnionType ( options ) )
233+ const choices = mixin . Type . map ( ( type ) => parseUnionType ( type , options ) )
234+ intersections . push ( b . tsUnionType ( choices ) )
202235 } else {
203236 const typeVal = Array . isArray ( mixin . Type ) ? mixin . Type [ 0 ] : mixin . Type
204237 if ( isNamedGroupReference ( typeVal ) ) {
@@ -235,7 +268,7 @@ function parseAssignment (assignment: Assignment) {
235268 const prop = props [ 0 ]
236269 const propType = Array . isArray ( prop . Type ) ? prop . Type : [ prop . Type ]
237270 if ( propType . length === 1 && RECORD_KEY_TYPES . has ( prop . Name ) ) {
238- const value = parseUnionType ( assignment )
271+ const value = parseUnionType ( assignment , options )
239272 const expr = b . tsTypeAliasDeclaration ( id , value )
240273 expr . comments = getAssignmentComments ( assignment )
241274 return exportWithComments ( expr )
@@ -284,10 +317,10 @@ function parseAssignment (assignment: Assignment) {
284317
285318 for ( const prop of group . Properties ) {
286319 // Choices are wrapped in arrays in the properties
287- const options = Array . isArray ( prop ) ? prop : [ prop ]
288- if ( options . length > 1 ) { // It's a choice within the mixin group
320+ const choiceProps = Array . isArray ( prop ) ? prop : [ prop ]
321+ if ( choiceProps . length > 1 ) { // It's a choice within the mixin group
289322 const unionOptions : any [ ] = [ ]
290- for ( const option of options ) {
323+ for ( const option of choiceProps ) {
291324 let refName : string | undefined
292325 const type = option . Type
293326 if ( typeof type === 'string' ) refName = type
@@ -314,7 +347,7 @@ function parseAssignment (assignment: Assignment) {
314347 }
315348 }
316349
317- for ( const option of options ) {
350+ for ( const option of choiceProps ) {
318351 let refName : string | undefined
319352 const type = option . Type
320353
@@ -395,7 +428,7 @@ function parseAssignment (assignment: Assignment) {
395428 }
396429 } else if ( type && typeof type === 'object' ) {
397430 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 ) ) )
399432 continue
400433 }
401434 refName = isNamedGroupReference ( type )
@@ -441,7 +474,7 @@ function parseAssignment (assignment: Assignment) {
441474
442475 const ownProps = props . filter ( p => ! isUnNamedProperty ( p ) )
443476 if ( ownProps . length > 0 ) {
444- intersections . push ( b . tsTypeLiteral ( parseObjectType ( ownProps ) ) )
477+ intersections . push ( b . tsTypeLiteral ( parseObjectType ( ownProps , options ) ) )
445478 }
446479
447480 let value : any
@@ -457,7 +490,7 @@ function parseAssignment (assignment: Assignment) {
457490 }
458491
459492 // Fallback to interface if no mixins (pure object)
460- const objectType = parseObjectType ( props )
493+ const objectType = parseObjectType ( props , options )
461494
462495 const expr = b . tsInterfaceDeclaration ( id , b . tsInterfaceBody ( objectType ) )
463496 expr . comments = getAssignmentComments ( assignment )
@@ -476,7 +509,7 @@ function parseAssignment (assignment: Assignment) {
476509 // We need to parse each choice.
477510 const obj = assignmentValues . map ( ( prop ) => {
478511 const t = Array . isArray ( prop . Type ) ? prop . Type [ 0 ] : prop . Type
479- return parseUnionType ( t )
512+ return parseUnionType ( t , options )
480513 } )
481514 const value = b . tsArrayType ( b . tsParenthesizedType ( b . tsUnionType ( obj ) ) )
482515 const expr = b . tsTypeAliasDeclaration ( id , value )
@@ -487,10 +520,10 @@ function parseAssignment (assignment: Assignment) {
487520 // Standard array
488521 const firstType = assignmentValues . Type
489522 const obj = Array . isArray ( firstType )
490- ? firstType . map ( parseUnionType )
523+ ? firstType . map ( ( type ) => parseUnionType ( type , options ) )
491524 : 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 ) ]
494527
495528 const value = b . tsArrayType (
496529 obj . length === 1
@@ -505,7 +538,7 @@ function parseAssignment (assignment: Assignment) {
505538 throw new Error ( `Unknown assignment type "${ ( assignment as any ) . Type } "` )
506539}
507540
508- function parseObjectType ( props : Property [ ] ) : ObjectBody {
541+ function parseObjectType ( props : Property [ ] , options : TransformSettings ) : ObjectBody {
509542 const propItems : ObjectBody = [ ]
510543 for ( const prop of props ) {
511544 /**
@@ -534,7 +567,7 @@ function parseObjectType (props: Property[]): ObjectBody {
534567 [ keyIdentifier ] ,
535568 b . tsTypeAnnotation (
536569 b . tsUnionType ( [
537- ...cddlType . map ( ( t ) => parseUnionType ( t ) ) ,
570+ ...cddlType . map ( ( t ) => parseUnionType ( t , options ) ) ,
538571 b . tsUndefinedKeyword ( )
539572 ] )
540573 )
@@ -546,7 +579,7 @@ function parseObjectType (props: Property[]): ObjectBody {
546579 continue
547580 }
548581
549- const id = b . identifier ( camelcase ( prop . Name ) )
582+ const id = createPropertyKey ( prop . Name , options )
550583
551584 if ( prop . Operator && prop . Operator . Type === 'default' ) {
552585 const defaultValue = parseDefaultValue ( prop . Operator )
@@ -555,7 +588,7 @@ function parseObjectType (props: Property[]): ObjectBody {
555588 }
556589
557590 const type = cddlType . map ( ( t ) => {
558- const unionType = parseUnionType ( t )
591+ const unionType = parseUnionType ( t , options )
559592 if ( unionType ) {
560593 const defaultValue = parseDefaultValue ( ( t as PropertyReference ) . Operator )
561594 defaultValue && comments . length && comments . push ( '' ) // add empty line if we have previous comments
@@ -577,7 +610,7 @@ function parseObjectType (props: Property[]): ObjectBody {
577610 return propItems
578611}
579612
580- function parseUnionType ( t : PropertyType | Assignment ) : TSTypeKind {
613+ function parseUnionType ( t : PropertyType | Assignment , options : TransformSettings ) : TSTypeKind {
581614 if ( typeof t === 'string' ) {
582615 if ( ! NATIVE_TYPES [ t ] ) {
583616 throw new Error ( `Unknown native type: "${ t } ` )
@@ -600,35 +633,35 @@ function parseUnionType (t: PropertyType | Assignment): TSTypeKind {
600633 * Check if we have choices in the group (arrays of Properties)
601634 */
602635 if ( prop . some ( p => Array . isArray ( p ) ) ) {
603- const options : TSTypeKind [ ] = [ ]
636+ const choices : TSTypeKind [ ] = [ ]
604637 for ( const choice of prop ) {
605638 const subProps = Array . isArray ( choice ) ? choice : [ choice ]
606639
607640 if ( subProps . length === 1 && isUnNamedProperty ( subProps [ 0 ] ) ) {
608641 const first = subProps [ 0 ]
609642 const subType = Array . isArray ( first . Type ) ? first . Type [ 0 ] : first . Type
610- options . push ( parseUnionType ( subType as PropertyType ) )
643+ choices . push ( parseUnionType ( subType as PropertyType , options ) )
611644 continue
612645 }
613646
614647 if ( subProps . every ( isUnNamedProperty ) ) {
615648 const tupleItems = subProps . map ( ( p ) => {
616649 const subType = Array . isArray ( p . Type ) ? p . Type [ 0 ] : p . Type
617- return parseUnionType ( subType as PropertyType )
650+ return parseUnionType ( subType as PropertyType , options )
618651 } )
619- options . push ( b . tsTupleType ( tupleItems ) )
652+ choices . push ( b . tsTupleType ( tupleItems ) )
620653 continue
621654 }
622655
623- options . push ( b . tsTypeLiteral ( parseObjectType ( subProps ) ) )
656+ choices . push ( b . tsTypeLiteral ( parseObjectType ( subProps , options ) ) )
624657 }
625- return b . tsUnionType ( options )
658+ return b . tsUnionType ( choices )
626659 }
627660
628661 if ( ( prop as Property [ ] ) . every ( isUnNamedProperty ) ) {
629662 const items = ( prop as Property [ ] ) . map ( p => {
630663 const t = Array . isArray ( p . Type ) ? p . Type [ 0 ] : p . Type
631- return parseUnionType ( t as PropertyType )
664+ return parseUnionType ( t as PropertyType , options )
632665 } )
633666
634667 if ( items . length === 1 ) return items [ 0 ] ;
@@ -643,15 +676,15 @@ function parseUnionType (t: PropertyType | Assignment): TSTypeKind {
643676 b . identifier ( 'Record' ) ,
644677 b . tsTypeParameterInstantiation ( [
645678 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 )
647680 ] )
648681 )
649682 }
650683
651684 /**
652685 * e.g. ?attributes: {*foo => text},
653686 */
654- return b . tsTypeLiteral ( parseObjectType ( t . Properties as Property [ ] ) )
687+ return b . tsTypeLiteral ( parseObjectType ( t . Properties as Property [ ] , options ) )
655688 } else if ( isNamedGroupReference ( t ) ) {
656689 return b . tsTypeReference (
657690 b . identifier ( pascalCase ( t . Value ) )
0 commit comments