Skip to content

Commit 1480861

Browse files
committed
fix: add ZclType invalid type gen
1 parent 7c40255 commit 1480861

File tree

4 files changed

+382
-60
lines changed

4 files changed

+382
-60
lines changed

scripts/zap-update-types.ts

Lines changed: 169 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ interface ParsedXMLTypeType extends XMLTypeType {
3232

3333
interface 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+
145163
async 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+
491601
function 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

scripts/zap-xml-types.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ export interface XMLSequence {
1515
field: XMLSequenceField[];
1616
}
1717

18-
export interface XMLSequenceField extends XMLAttr<{name: string; type: string}> {}
18+
export interface XMLSequenceField
19+
extends XMLAttr<{
20+
name: string;
21+
type: string;
22+
presentIf?: string;
23+
}> {}
1924

2025
export interface XMLTypeType
2126
extends XMLAttr<{
@@ -34,6 +39,12 @@ export interface XMLLibrary {
3439
"type:type"?: XMLTypeType[];
3540
}
3641

42+
export interface XMLGlobal {
43+
"type:type"?: XMLTypeType[];
44+
attributes?: {attribute: XMLAttributeDefinition[]}[];
45+
commands?: {command: XMLCommandDefinition[]}[];
46+
}
47+
3748
export interface XMLRestriction {
3849
"type:length"?: XMLAttr<{value: string}>[];
3950
"type:minLength"?: XMLAttr<{value: string}>[];
@@ -42,13 +53,13 @@ export interface XMLRestriction {
4253
"type:minInclusive"?: XMLAttr<{value: string}>[];
4354
"type:maxExclusive"?: XMLAttr<{value: string}>[];
4455
"type:maxInclusive"?: XMLAttr<{value: string}>[];
45-
/** only used for `type:type` (data type non-value) */
46-
"type:invalid"?: XMLAttr<{value: string}>[];
4756
"type:minInclusiveRef"?: XMLAttr<{ref: string}>[];
4857
"type:minExclusiveRef"?: XMLAttr<{ref: string}>[];
4958
"type:maxInclusiveRef"?: XMLAttr<{ref: string}>[];
5059
"type:maxExclusiveRef"?: XMLAttr<{ref: string}>[];
5160
"type:special"?: XMLAttr<{name: string; value: string}>[];
61+
/** only used for `type:type` (data type non-value) */
62+
"type:invalid"?: XMLAttr<{value: string}>[];
5263
/** for types gen */
5364
"type:enumeration"?: XMLEnumeration[];
5465
"type:sequence"?: XMLSequence[];
@@ -131,4 +142,5 @@ export interface XMLRoot {
131142
"zcl:cluster"?: XMLCluster | XMLCluster[];
132143
"zcl:derivedCluster"?: XMLDerivedCluster | XMLDerivedCluster[];
133144
"zcl:library"?: XMLLibrary | XMLLibrary[];
145+
"zcl:global"?: XMLGlobal | XMLGlobal[];
134146
}

0 commit comments

Comments
 (0)