in apps/vs-code-designer/src/app/utils/unitTests.ts [780:876]
export function buildClassDefinition(className: string, node: any): ClassDefinition {
// If there's a top-level "description" for the object
let classDescription: string | null = node.description ? String(node.description) : null;
if (!classDescription) {
if (node.nestedTypeProperty === 'object') {
const skipKeys = ['nestedTypeProperty', 'title', 'description', 'format', 'headers', 'queries', 'tags', 'relativePathParameters'];
const propertyNames = Object.keys(node).filter((key) => !skipKeys.includes(key));
classDescription =
propertyNames.length > 0
? `Class for ${className} representing an object with properties.`
: `Class for ${className} representing an empty object.`;
} else {
classDescription = `Class for ${className} representing a ${node.nestedTypeProperty} value.`;
}
}
// We'll collect property info for the current class
const properties: PropertyDefinition[] = [];
// We'll collect child classes if we see nested objects (type: "object").
const children: ClassDefinition[] = [];
// If this node is an object, it may have sub-fields we need to parse as properties.
if (node.nestedTypeProperty === 'object') {
// Create a combined array of keys we need to skip
const skipKeys = ['nestedTypeProperty', 'title', 'description', 'format', 'headers', 'queries', 'tags', 'relativePathParameters'];
// For each subfield in node (like "id", "location", "properties", etc.)
for (const key of Object.keys(node)) {
// Skip known metadata fields and the newly added keys (headers, queries, relativePathParameters)
if (skipKeys.includes(key)) {
continue;
}
const subNode = node[key];
let propName = toPascalCase(key);
// Handle special characters
let jsonPropertyName = null;
if (/[~@]/.test(key)) {
jsonPropertyName = key.replace(/~1/g, '.');
propName = toPascalCase(subNode?.title.replace(/[^a-zA-Z0-9]/g, ''));
}
// Determine the child's C# type
let csharpType = mapJsonTypeToCSharp(subNode?.nestedTypeProperty, subNode?.format);
let isObject = false;
// If it's an object, we must generate a nested class.
// We'll do that recursively, then use the generated child's className for this property type.
if (subNode?.nestedTypeProperty === 'object') {
isObject = true;
const childClassName = className + propName; // e.g. "ActionOutputs" -> "ActionOutputsBody"
const childDef = buildClassDefinition(childClassName, subNode);
// If there are child properties then use the newly created object, otherwise use JObject
if (childDef.properties.length > 0) {
children.push(childDef);
// The property for this sub-node points to the newly created child's class name
csharpType = childDef.className;
}
}
// If it's an array, process the array items
if (subNode?.nestedTypeProperty === 'array') {
isObject = true;
const arrayItemNode = subNode['[*]'];
const arrayItemClassName = subNode?.description ? toPascalCase(subNode?.description.replace(/\s+/g, '')) : ''; // Remove spaces from description
const arrayItemDef = buildClassDefinition(arrayItemClassName, arrayItemNode);
// If there are child properties then use the newly created object, otherwise use JObject
if (arrayItemDef.properties.length > 0) {
children.push(arrayItemDef);
// The property for this sub-node points to the newly created child's class name
csharpType = `List<${arrayItemDef.className}>`;
}
}
// If it's an array, you might want to look at subNode.items.type to refine the list item type.
// Check if the subNode has a "description" to be used as a doc-comment on the property.
const subDescription = subNode?.description ? String(subNode.description) : null;
properties.push({
propertyName: propName,
propertyType: csharpType,
description: subDescription,
isObject,
jsonPropertyName,
});
}
}
// Build the ClassDefinition for the current node
return {
className,
description: classDescription,
properties,
children,
};
}