in powershell/plugins/sdk-create-inline-properties.ts [50:344]
function createVirtualProperties(schema: ObjectSchema, stack: Array<string>, conflicts: Array<string>) {
// Some properties should be removed are wrongly kept as null and need to clean them
if (schema.properties) {
schema.properties = schema.properties.filter(each => each);
}
// dolauli
// owned: all properties(obj & nonobj) in the schema,
// inherited: Properties from parents,
// inlined: for obj properties, flatten them to children,
// did we already inline this object
if (schema.language.default.inline === 'yes') {
return true;
}
if (schema.language.default.inline === 'no') {
return false;
}
// this is bad. This would happen when we have a circular reference in the tree.
// dolauli curious in which case this will happen, got it to use no-inline to skip inline and avoid circular reference
if (schema.language.default.inline === 'inprogress') {
let text = (`Note: during processing of '${schema.language.default.name}' a circular reference has been discovered.`);
text += '\n In order to proceed, you must add a directive to indicate which model you want to not inline.\n';
text += '\ndirective:';
text += '\n- no-inline: # choose ONE of these models to disable inlining';
for (const each of stack) {
text += (`\n - ${each} `);
}
text += '\n';
conflicts.push(text);
/* `directive:
- no-inline:
- MyModel
- YourModel
- HerModel
` */
// `, and we're skipping inlining.\n ${stack.join(' => ')}`);
// mark it as 'not-inlining'
schema.language.default.inline = 'no';
return false;
}
// ok, set to in progress now.
schema.language.default.inline = 'inprogress';
// virutual property set.
const virtualProperties = schema.language.default.virtualProperties = {
owned: new Array<VirtualProperty>(),
inherited: new Array<VirtualProperty>(),
inlined: new Array<VirtualProperty>(),
};
// First we should run thru the properties in parent classes and create inliners for each property they have.
// dolauli handle properties in parents
for (const parentSchema of values(schema.parents?.immediate)) {
// make sure that the parent is done.
// Guess parent should always be an object.
if (!isObjectSchema(parentSchema))
continue;
createVirtualProperties(parentSchema, [...stack, `${schema.language.default.name}`], conflicts);
const parentProperties = parentSchema.language.default.virtualProperties || {
owned: [],
inherited: [],
inlined: [],
};
// now we go thru the parent's virutal properties and create our own copies
for (const virtualProperty of [...parentProperties.inherited, ...parentProperties.inlined, ...parentProperties.owned]) {
// make sure that we have a list of shared owners of this property.
virtualProperty.sharedWith = virtualProperty.sharedWith || [virtualProperty];
// we are just copying over theirs to ours.
const inheritedProperty = {
name: getEscapedReservedName(virtualProperty.name, 'Property'),
property: virtualProperty.property,
private: virtualProperty.private,
nameComponents: virtualProperty.nameComponents,
nameOptions: virtualProperty.nameOptions,
accessViaProperty: virtualProperty,
accessViaMember: virtualProperty,
accessViaSchema: parentSchema,
originalContainingSchema: virtualProperty.originalContainingSchema,
description: virtualProperty.description,
alias: [],
create: virtualProperty.create,
update: virtualProperty.update,
read: virtualProperty.read,
readOnly: virtualProperty.readOnly,
required: virtualProperty.required,
sharedWith: virtualProperty.sharedWith,
serializedName: virtualProperty.serializedName
};
// add it to the list of virtual properties that share this property.
virtualProperty.sharedWith.push(inheritedProperty);
// add it to this class.
virtualProperties.inherited.push(inheritedProperty);
}
}
// dolauli figure out object properties and non object properties in this class
const [objectProperties, nonObjectProperties] = values(schema.properties).bifurcate(each =>
!schema.language.default['skip-inline'] && // if this schema is marked skip-inline, none can be inlined, treat them all as straight properties.
!each.schema.language.default['skip-inline'] && // if the property schema is marked skip-inline, then it should not be processed either.
each.extensions && each.extensions['x-ms-client-flatten'] && // only flatten when x-ms-client-flatten is set
each.schema.type === SchemaType.Object && // is it an object
getAllProperties(each.schema).length > 0 // does it have properties (or inherit properties)
);
// run thru the properties in this class.
// dolauli handle properties in this class
for (const property of objectProperties) {
if (isReserved(property.language.default.name)) {
property.language.default.name = camelCase(getEscapedReservedName(property.language.default.name, 'Property'));
}
const mutability = getMutability(property);
const propertyName = property.language.default.name;
// for each object member, make sure that it's inlined it's children that it can.
createVirtualProperties(<ObjectSchema>property.schema, [...stack, `${schema.language.default.name}`], conflicts);
// this happens if there is a circular reference.
// this means that this class should not attempt any inlining of that property at all .
// dolauli pay attention to the condition check
const isDict = property.schema.type === SchemaType.Dictionary || (<ObjectSchema>property.schema).parents?.immediate?.find((s) => s.type === SchemaType.Dictionary);
const canInline =
(!property.schema.language.default['skip-inline']) &&
(!<ObjectSchema>property.schema.language.default.byReference) &&
(!isDict) &&
property.extensions &&
property.extensions['x-ms-client-flatten'] &&
(<ObjectSchema>property.schema).language.default.inline === 'yes';
// the target has properties that we can inline
const virtualChildProperties = property.schema.language.default.virtualProperties || {
owned: [],
inherited: [],
inlined: [],
};
const allNotRequired = values(getAllPublicVirtualProperties()).all(each => !each.property.language.default.required);
if (canInline && (property.language.default.required || allNotRequired)) {
// if the child property is low enough (or it's 'properties'), let's create virtual properties for each one.
// create a private property for the inlined ones to use.
const privateProperty = {
name: getEscapedReservedName(getPascalIdentifier(propertyName), 'Property'),
propertySchema: schema,
property,
nameComponents: [getPascalIdentifier(propertyName)],
nameOptions: getNameOptions(schema.language.default.name, [propertyName]),
private: true,
description: property.summary || '',
originalContainingSchema: schema,
alias: [],
required: property.required || property.language.default.required,
serializedName: property.serializedName
};
virtualProperties.owned.push(privateProperty);
for (const inlinedProperty of [...virtualChildProperties.inherited, ...virtualChildProperties.owned]) {
// child properties are be inlined without prefixing the name with the property name
// unless there is a collision, in which case, we have to resolve
// (scan back from the far right)
// deeper child properties should be inlined with their parent's name
// ie, this.[properties].owner.name should be this.ownerName
// const proposedName = getPascalIdentifier(`${propertyName === 'properties' || /*objectProperties.length === 1*/ propertyName === 'error' ? '' : pascalCase(fixLeadingNumber(deconstruct(propertyName)).map(each => singularize(each)))} ${inlinedProperty.name}`);
const proposedName = getPascalIdentifier(inlinedProperty.name);
const components = [...removeSequentialDuplicates([propertyName, ...inlinedProperty.nameComponents])];
let readonly = inlinedProperty.readOnly || property.readOnly;
const create = mutability.create && inlinedProperty.create && !readonly;
const update = mutability.update && inlinedProperty.update && !readonly;
const read = mutability.read && inlinedProperty.read;
readonly = readonly || (read && !update && !create);
virtualProperties.inlined.push({
name: getEscapedReservedName(proposedName, 'Property'),
property: inlinedProperty.property,
private: inlinedProperty.private,
nameComponents: components,
nameOptions: getNameOptions(inlinedProperty.property.schema.language.default.name, components),
accessViaProperty: privateProperty,
accessViaMember: inlinedProperty,
accessViaSchema: schema,
originalContainingSchema: schema,
description: inlinedProperty.description,
alias: [],
create: create,
update: update,
read: read,
readOnly: readonly,
required: inlinedProperty.required && privateProperty.required,
serializedName: `${property.serializedName}.${inlinedProperty.serializedName}`
});
}
for (const inlinedProperty of [...virtualChildProperties.inlined]) {
// child properties are be inlined without prefixing the name with the property name
// unless there is a collision, in which case, we have to resolve
// (scan back from the far right)
// deeper child properties should be inlined with their parent's name
// ie, this.[properties].owner.name should be this.ownerName
const proposedName = getPascalIdentifier(inlinedProperty.name);
let readonly = inlinedProperty.readOnly || property.readOnly;
const create = mutability.create && inlinedProperty.create && !readonly;
const update = mutability.update && inlinedProperty.update && !readonly;
const read = mutability.read && inlinedProperty.read;
readonly = readonly || (read && !update && !create);
const components = [...removeSequentialDuplicates([propertyName, ...inlinedProperty.nameComponents])];
virtualProperties.inlined.push({
name: getEscapedReservedName(proposedName, 'Property'),
property: inlinedProperty.property,
private: inlinedProperty.private,
nameComponents: components,
nameOptions: getNameOptions(inlinedProperty.property.schema.language.default.name, components),
accessViaProperty: privateProperty,
accessViaMember: inlinedProperty,
accessViaSchema: schema,
originalContainingSchema: schema,
description: inlinedProperty.description,
alias: [],
create: create,
update: update,
read: read,
readOnly: readonly,
required: inlinedProperty.required && privateProperty.required,
serializedName: `${property.serializedName}.${inlinedProperty.serializedName}`
});
}
} else {
// otherwise, we're not below the threshold, and we should treat this as a non-inlined property
nonObjectProperties.push(property);
}
}
for (const property of nonObjectProperties) {
if (isReserved(property.language.default.name)) {
property.language.default.name = camelCase(getEscapedReservedName(property.language.default.name, 'Property'));
}
const name = getEscapedReservedName(getPascalIdentifier(<string>property.language.default.name), 'Property');
// this is not something that has properties,
// so we don't need to do any inlining
// however, we can add it to our list of virtual properties
// so that our consumers can get it.
const mutability = getMutability(property);
virtualProperties.owned.push({
name,
property,
nameComponents: [name],
nameOptions: [name],
description: property.summary || '',
originalContainingSchema: schema,
alias: [],
create: mutability.create && !property.readOnly,
update: mutability.update && !property.readOnly,
read: mutability.read,
readOnly: property.readOnly || (mutability.read && !mutability.create && !mutability.update),
required: property.required || property.language.default.required,
serializedName: `${property.serializedName}`
});
}
// resolve name collisions.
const allProps = [...virtualProperties.owned, ...virtualProperties.inherited, ...virtualProperties.inlined];
const inlined = new Map<string, number>();
for (const each of allProps) {
// track number of instances of a given name.
inlined.set(each.name, (inlined.get(each.name) || 0) + 1);
}
const usedNames = new Set(inlined.keys());
for (const each of virtualProperties.inlined.sort((a, b) => length(a.nameOptions) - length(b.nameOptions))) {
const ct = inlined.get(each.name);
if (ct && ct > 1) {
// console.error(`Fixing collision on name ${each.name} #${ct} `);
each.name = selectName(each.nameOptions, usedNames);
}
}
schema.language.default.inline = 'yes';
return true;
}