@@ -32,7 +32,7 @@ interface ParsedXMLTypeType extends XMLTypeType {
3232
3333interface ParsedType {
3434 name : string ;
35- node : ts . EnumDeclaration | ts . InterfaceDeclaration | ts . TypeAliasDeclaration ;
35+ node : ts . EnumDeclaration | ts . InterfaceDeclaration | ts . TypeAliasDeclaration | ts . VariableStatement ;
3636}
3737
3838// #endregion
@@ -142,11 +142,59 @@ function toHex(value: string, pad4 = false): string {
142142
143143// #region XML Parsing and Type Collection
144144
145+ const createSyntheticType = (
146+ clusterName : string ,
147+ name : string ,
148+ parent : XMLAttr < { type : string } > & { restriction ?: XMLRestriction [ ] ; bitmap ?: XMLBitmapDefinition [ ] } ,
149+ ) : ParsedXMLTypeType | undefined => {
150+ if ( parent . restriction || parent . bitmap ) {
151+ return {
152+ // biome-ignore lint/style/useNamingConvention: API
153+ $ : { id : "" , name : name , short : name , inheritsFrom : parent . $ . type } ,
154+ clusterName : clusterName ,
155+ restriction : parent . restriction ,
156+ bitmap : parent . bitmap ,
157+ } ;
158+ }
159+
160+ return undefined ;
161+ } ;
162+
145163async function collectAllTypes ( startFile : string ) : Promise < ParsedXMLTypeType [ ] > {
146164 const allTypes : ParsedXMLTypeType [ ] = [ ] ;
147165 const visitedFiles = new Set < string > ( ) ;
148166 const filesToProcess : string [ ] = [ startFile ] ;
149167
168+ const processSide = ( clusterName : string , side : XMLClusterSide | undefined ) => {
169+ if ( ! side ) {
170+ return ;
171+ }
172+
173+ if ( side . attributes ) {
174+ for ( const attr of side . attributes [ 0 ] . attribute ) {
175+ const synthetic = createSyntheticType ( clusterName , attr . $ . name , attr ) ;
176+
177+ if ( synthetic ) {
178+ allTypes . push ( synthetic ) ;
179+ }
180+ }
181+ }
182+
183+ if ( side . commands ) {
184+ for ( const cmd of side . commands [ 0 ] . command ) {
185+ if ( cmd . fields ) {
186+ for ( const field of cmd . fields [ 0 ] . field ) {
187+ const synthetic = createSyntheticType ( clusterName , field . $ . name , field ) ;
188+
189+ if ( synthetic ) {
190+ allTypes . push ( synthetic ) ;
191+ }
192+ }
193+ }
194+ }
195+ }
196+ } ;
197+
150198 while ( filesToProcess . length > 0 ) {
151199 const currentFile = filesToProcess . shift ( ) ;
152200
@@ -164,6 +212,7 @@ async function collectAllTypes(startFile: string): Promise<ParsedXMLTypeType[]>
164212 explicitRoot : true ,
165213 } ) ) as XMLRoot ;
166214 const libraries = parsed [ "zcl:library" ] ;
215+ const global = parsed [ "zcl:global" ] ;
167216 const clusters = parsed [ "zcl:cluster" ] ;
168217
169218 if ( libraries ) {
@@ -184,23 +233,13 @@ async function collectAllTypes(startFile: string): Promise<ParsedXMLTypeType[]>
184233 }
185234 }
186235
187- const createSyntheticType = (
188- clusterName : string ,
189- name : string ,
190- parent : XMLAttr < { type : string } > & { restriction ?: XMLRestriction [ ] ; bitmap ?: XMLBitmapDefinition [ ] } ,
191- ) : ParsedXMLTypeType | undefined => {
192- if ( parent . restriction || parent . bitmap ) {
193- return {
194- // biome-ignore lint/style/useNamingConvention: API
195- $ : { id : "" , name : name , short : name , inheritsFrom : parent . $ . type } ,
196- clusterName : clusterName ,
197- restriction : parent . restriction ,
198- bitmap : parent . bitmap ,
199- } ;
236+ if ( global ) {
237+ for ( const lib of Array . isArray ( global ) ? global : [ global ] ) {
238+ if ( lib [ "type:type" ] ) {
239+ allTypes . push ( ...lib [ "type:type" ] ) ;
240+ }
200241 }
201-
202- return undefined ;
203- } ;
242+ }
204243
205244 if ( clusters ) {
206245 for ( const cluster of Array . isArray ( clusters ) ? clusters : [ clusters ] ) {
@@ -210,42 +249,12 @@ async function collectAllTypes(startFile: string): Promise<ParsedXMLTypeType[]>
210249 }
211250 }
212251
213- const processSide = ( side : XMLClusterSide | undefined ) => {
214- if ( ! side ) {
215- return ;
216- }
217-
218- if ( side . attributes ) {
219- for ( const attr of side . attributes [ 0 ] . attribute ) {
220- const synthetic = createSyntheticType ( cluster . $ . name , attr . $ . name , attr ) ;
221-
222- if ( synthetic ) {
223- allTypes . push ( synthetic ) ;
224- }
225- }
226- }
227-
228- if ( side . commands ) {
229- for ( const cmd of side . commands [ 0 ] . command ) {
230- if ( cmd . fields ) {
231- for ( const field of cmd . fields [ 0 ] . field ) {
232- const synthetic = createSyntheticType ( cluster . $ . name , field . $ . name , field ) ;
233-
234- if ( synthetic ) {
235- allTypes . push ( synthetic ) ;
236- }
237- }
238- }
239- }
240- }
241- } ;
242-
243252 if ( cluster . server ) {
244- processSide ( cluster . server [ 0 ] ) ;
253+ processSide ( cluster . $ . name , cluster . server [ 0 ] ) ;
245254 }
246255
247256 if ( cluster . client ) {
248- processSide ( cluster . client [ 0 ] ) ;
257+ processSide ( cluster . $ . name , cluster . client [ 0 ] ) ;
249258 }
250259 }
251260 }
@@ -273,7 +282,7 @@ class TypeResolver {
273282 resolve ( short : string , factory : ts . NodeFactory ) : ts . TypeNode {
274283 const type = this . #typesByShort. get ( short . toLowerCase ( ) ) ;
275284
276- if ( type ?. $ . inheritsFrom ) {
285+ if ( type ?. $ . inheritsFrom && ! type . restriction ?. some ( ( r ) => r [ "type:sequence" ] ) ) {
277286 return this . resolve ( type . $ . inheritsFrom , factory ) ;
278287 }
279288
@@ -339,7 +348,7 @@ class TypeResolver {
339348 return factory . createTypeReferenceNode ( "Buffer" ) ;
340349
341350 default :
342- if ( type ) {
351+ if ( type && type . $ . short !== "unk" ) {
343352 return factory . createTypeReferenceNode ( simplePascalCase ( type . $ . short ) ) ;
344353 }
345354
@@ -472,7 +481,14 @@ function createInterfaceFromSequence(
472481
473482 const propertyType = resolver . resolve ( f . $ . type , factory ) ;
474483
475- members . push ( factory . createPropertySignature ( undefined , propertyName , undefined , propertyType ) ) ;
484+ members . push (
485+ factory . createPropertySignature (
486+ undefined ,
487+ propertyName ,
488+ f . $ . presentIf ? factory . createToken ( ts . SyntaxKind . QuestionToken ) : undefined ,
489+ propertyType ,
490+ ) ,
491+ ) ;
476492 }
477493
478494 const interfaceDeclaration = factory . createInterfaceDeclaration (
@@ -488,6 +504,100 @@ function createInterfaceFromSequence(
488504 return interfaceDeclaration ;
489505}
490506
507+ function createSafeNumericLiteral ( value : string | number , bigInt : boolean , factory : ts . NodeFactory ) : ts . Expression {
508+ if ( bigInt ) {
509+ const num = BigInt ( value ) ;
510+ const str = String ( value ) ;
511+
512+ return factory . createBigIntLiteral ( { base10Value : num < 0 ? str . slice ( 1 ) : str , negative : num < 0 } ) ;
513+ }
514+
515+ const num = Number ( value ) ;
516+
517+ if ( num < 0 ) {
518+ return factory . createPrefixUnaryExpression ( ts . SyntaxKind . MinusToken , factory . createNumericLiteral ( Math . abs ( num ) ) ) ;
519+ }
520+
521+ return factory . createNumericLiteral ( num ) ;
522+ }
523+
524+ function createZclTypeInvalidConstants (
525+ factory : ts . NodeFactory ,
526+ types : ParsedXMLTypeType [ ] ,
527+ ) : [ byName : ts . VariableStatement , byType : ts . VariableStatement ] | undefined {
528+ const zclType = types . find ( ( t ) => t . $ . short === "zclType" ) ;
529+
530+ if ( ! zclType ?. restriction ?. [ 0 ] ?. [ "type:enumeration" ] ) {
531+ return undefined ;
532+ }
533+
534+ const zclTypeEnumMap = new Map ( zclType . restriction [ 0 ] [ "type:enumeration" ] . map ( ( e ) => [ e . $ . name , e . $ . value ] ) ) ;
535+ const byNameProperties : ts . PropertyAssignment [ ] = [ ] ;
536+ const byTypeProperties : ts . PropertyAssignment [ ] = [ ] ;
537+
538+ for ( const type of types ) {
539+ const invalidValue = type . restriction ?. [ 0 ] ?. [ "type:invalid" ] ?. [ 0 ] ?. $ ?. value ;
540+ const typeId = zclTypeEnumMap . get ( type . $ . short ) ;
541+
542+ if ( invalidValue && typeId ) {
543+ const name = pascalCase ( type . $ . short ) ;
544+ const invalidValueLiteral = createSafeNumericLiteral ( invalidValue , name . endsWith ( "56" ) || name . endsWith ( "64" ) , factory ) ;
545+
546+ byNameProperties . push ( factory . createPropertyAssignment ( factory . createIdentifier ( name ) , invalidValueLiteral ) ) ;
547+
548+ const byTypeProp = factory . createPropertyAssignment ( factory . createNumericLiteral ( Number . parseInt ( typeId , 16 ) ) , invalidValueLiteral ) ;
549+ const commentText = `* ${ name } ` ;
550+
551+ ts . addSyntheticLeadingComment ( byTypeProp , ts . SyntaxKind . MultiLineCommentTrivia , commentText , true ) ;
552+ byTypeProperties . push ( byTypeProp ) ;
553+ }
554+ }
555+
556+ const invalidVal = factory . createUnionTypeNode ( [
557+ factory . createKeywordTypeNode ( ts . SyntaxKind . NumberKeyword ) ,
558+ factory . createKeywordTypeNode ( ts . SyntaxKind . BigIntKeyword ) ,
559+ ] ) ;
560+ const byNameCommentText = "* ZCL non-values by type name (key of `ZclType`). " ;
561+ const byNameConst = factory . createVariableStatement (
562+ [ factory . createToken ( ts . SyntaxKind . ExportKeyword ) ] ,
563+ factory . createVariableDeclarationList (
564+ [
565+ factory . createVariableDeclaration (
566+ "ZCL_TYPE_INVALID_BY_TYPE_NAME" ,
567+ undefined ,
568+ factory . createTypeReferenceNode ( "Readonly" , [
569+ factory . createTypeReferenceNode ( "Record" , [ factory . createKeywordTypeNode ( ts . SyntaxKind . StringKeyword ) , invalidVal ] ) ,
570+ ] ) ,
571+ factory . createObjectLiteralExpression ( byNameProperties , true ) ,
572+ ) ,
573+ ] ,
574+ ts . NodeFlags . Const ,
575+ ) ,
576+ ) ;
577+ const byTypeCommentText = "* ZCL non-values by type ID (value of `ZclType`). " ;
578+ const byTypeConst = factory . createVariableStatement (
579+ [ factory . createToken ( ts . SyntaxKind . ExportKeyword ) ] ,
580+ factory . createVariableDeclarationList (
581+ [
582+ factory . createVariableDeclaration (
583+ "ZCL_TYPE_INVALID_BY_TYPE" ,
584+ undefined ,
585+ factory . createTypeReferenceNode ( "Readonly" , [
586+ factory . createTypeReferenceNode ( "Record" , [ factory . createKeywordTypeNode ( ts . SyntaxKind . NumberKeyword ) , invalidVal ] ) ,
587+ ] ) ,
588+ factory . createObjectLiteralExpression ( byTypeProperties , true ) ,
589+ ) ,
590+ ] ,
591+ ts . NodeFlags . Const ,
592+ ) ,
593+ ) ;
594+
595+ ts . addSyntheticLeadingComment ( byNameConst , ts . SyntaxKind . MultiLineCommentTrivia , byNameCommentText , true ) ;
596+ ts . addSyntheticLeadingComment ( byTypeConst , ts . SyntaxKind . MultiLineCommentTrivia , byTypeCommentText , true ) ;
597+
598+ return [ byNameConst , byTypeConst ] ;
599+ }
600+
491601function processTypes ( factory : ts . NodeFactory , types : ParsedXMLTypeType [ ] , resolver : TypeResolver ) : ParsedType [ ] {
492602 const processed : ParsedType [ ] = [ ] ;
493603 const processedNames = new Set < string > ( ) ;
@@ -533,6 +643,13 @@ function processTypes(factory: ts.NodeFactory, types: ParsedXMLTypeType[], resol
533643 }
534644 }
535645
646+ const zclTypeInvalidConsts = createZclTypeInvalidConstants ( factory , types ) ;
647+
648+ if ( zclTypeInvalidConsts ) {
649+ processed . push ( { name : "ZCL_TYPE_INVALID_BY_TYPE_NAME" , node : zclTypeInvalidConsts [ 0 ] } ) ;
650+ processed . push ( { name : "ZCL_TYPE_INVALID_BY_TYPE" , node : zclTypeInvalidConsts [ 1 ] } ) ;
651+ }
652+
536653 return processed ;
537654}
538655
0 commit comments