powershell/plugins/sdk-create-inline-properties.ts (546 lines of code) (raw):

/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { codeModelSchema, Property, CodeModel, ObjectSchema, ConstantSchema, GroupSchema, isObjectSchema, SchemaType, GroupProperty, ParameterLocation, Operation, Parameter, ImplementationLocation, OperationGroup, Request, SchemaContext, Protocol, Schemas, Schema } from '@autorest/codemodel'; import { getPascalIdentifier, removeSequentialDuplicates, pascalCase, fixLeadingNumber, deconstruct, selectName, EnglishPluralizationService, serialize, camelCase } from '@azure-tools/codegen'; import { length, values, } from '@azure-tools/linq'; import { AutorestExtensionHost as Host, Session, startSession } from '@autorest/extension-base'; import { CommandOperation } from '../utils/command-operation'; import { PwshModel } from '../utils/PwshModel'; import { ModelState } from '../utils/model-state'; import { VirtualParameter } from '../utils/command-operation'; import { VirtualProperty, getAllProperties, getAllPublicVirtualProperties, getMutability } from '../utils/schema'; import { resolveParameterNames } from '../utils/resolve-conflicts'; import { OperationType } from '../utils/command-operation'; import { Header, HeaderPropertyType } from '@azure-tools/codemodel-v3'; import { getEscapedReservedName, isReserved } from '../utils/code-namer'; import { Helper } from '../sdk/utility'; function getPluralizationService(): EnglishPluralizationService { const result = new EnglishPluralizationService(); result.addWord('Database', 'Databases'); result.addWord('database', 'databases'); result.addWord('Premise', 'Premises'); result.addWord('premise', 'premises'); return result; } type State = ModelState<PwshModel>; export function singularize(word: string): string { return getPluralizationService().singularize(word); } function getNameOptions(typeName: string, components: Array<string>) { const result = new Set<string>(); // add a variant for each incrementally inclusive parent naming scheme. for (let i = 0; i < length(components); i++) { const subset = pascalCase([...removeSequentialDuplicates(components.slice(-1 * i, length(components)))]); result.add(subset); } // add a second-to-last-ditch option as <typename>.<name> result.add(pascalCase([...removeSequentialDuplicates([...fixLeadingNumber(deconstruct(typeName)), ...deconstruct(components.last)])])); return [...result.values()]; } 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; } function parameterGroupName(operationGroup: OperationGroup, operation: Operation, groupExtension: any): string { if (groupExtension && groupExtension['name']) { return pascalCase(groupExtension['name']); } else if (groupExtension && groupExtension['postfix']) { return `${pascalCase(operationGroup.$key)}${pascalCase((<any>operation).operationId.split('_')[1] || '')}${pascalCase(groupExtension['postfix'])}`; } else { return `${pascalCase(operationGroup.$key)}${pascalCase((<any>operation).operationId.split('_')[1] || '')}Parameters`; } } async function implementGroupParameters(state: State) { const parameterGroup = new Map<string, ObjectSchema>(); const parameterGroupContainerHeader = new Map<string, boolean>(); const parameterGroupContainerOthers = new Map<string, boolean>(); const parameterAdded = new Map<string, Array<string>>(); for (const operationGroup of state.model.operationGroups) { for (const operation of operationGroup.operations) { const operationGroupParameters = new Array<Parameter>(); // value means if the parameter is required const addedOperationGroupParameters = new Map<string, boolean>(); for (const parameter of [...(operation.requests && operation.requests.length > 0 ? operation.requests[0].parameters || [] : []), ...(operation.parameters || [])]) { if (parameter.extensions && parameter.extensions['x-ms-parameter-grouping']) { const key = parameterGroupName(operationGroup, operation, parameter.extensions['x-ms-parameter-grouping']); if (parameter.protocol.http?.in === ParameterLocation.Header) { parameterGroupContainerHeader.set(key, true); } else { parameterGroupContainerOthers.set(key, true); } const groupObj = parameterGroup.get(key) || new ObjectSchema(key, ''); if (!parameterGroup.has(key)) { groupObj.extensions = {}; groupObj.extensions['x-ms-parameter-grouping'] = parameter.extensions['x-ms-parameter-grouping']; parameterGroup.set(key, groupObj); parameterAdded.set(key, []); state.model.schemas.objects = state.model.schemas.objects || []; state.model.schemas.objects.push(groupObj); } const prop = new Property(parameter.language.default.name, parameter.language.default.description, parameter.schema, { required: parameter.required, }); if (parameterAdded.has(key) && (parameterAdded.get(key) || []).indexOf(parameter.language.default.name) === -1) { groupObj.addProperty(prop); parameterAdded.set(key, [...(parameterAdded.get(key) || []), parameter.language.default.name]); } if (!addedOperationGroupParameters.has(key)) { addedOperationGroupParameters.set(key, !!(parameter.required)); const groupParameter = new Parameter(camelCase(key), '', groupObj); groupParameter.protocol.http = groupParameter.protocol.http || new Protocol(); groupParameter.protocol.http.in = 'complex'; operationGroupParameters.push(groupParameter); } else { if (!addedOperationGroupParameters.get(key)) { addedOperationGroupParameters.set(key, !!(parameter.required)); } } } else { continue; } } for (const groupParameter of operationGroupParameters) { groupParameter.required = addedOperationGroupParameters.get(pascalCase(groupParameter.language.default.name)) || false; } operation.parameters = [...operationGroupParameters, ...(operation.parameters || [])]; } } implementGroupParametersForPagination(state, parameterGroup, parameterGroupContainerHeader, parameterGroupContainerOthers); } async function implementGroupParametersForPagination(state: State, GroupObjects: Map<string, ObjectSchema>, parameterGroupContainHeader: Map<string, boolean>, parameterGroupContainOthers: Map<string, boolean>) { const parameterGroup = new Map<string, ObjectSchema>(); const parameterAdded = new Map<string, Array<string>>(); for (const operationGroup of state.model.operationGroups) { for (const operation of operationGroup.operations) { if (!(operation.extensions && operation.extensions['x-ms-pageable'])) { // skip none pageable operations continue; } const operationGroupParameters = new Array<Parameter>(); // value means if the parameter is required const addedOperationGroupParameters = new Map<string, boolean>(); for (const parameter of [...(operation.requests && operation.requests.length > 0 ? operation.requests[0].parameters || [] : []), ...(operation.parameters || [])]) { if (parameter.protocol.http?.in === ParameterLocation.Header && parameter.extensions && parameter.extensions['x-ms-parameter-grouping']) { const key = parameterGroupName(operationGroup, operation, parameter.extensions['x-ms-parameter-grouping']); if (parameterGroupContainHeader.get(key) && parameterGroupContainOthers.get(key)) { const keyWithModel = `${key}Model`; const groupObj = parameterGroup.get(keyWithModel) || new ObjectSchema(keyWithModel, ''); if (!parameterGroup.has(keyWithModel)) { groupObj.extensions = {}; groupObj.extensions['x-ms-parameter-grouping'] = parameter.extensions['x-ms-parameter-grouping']; parameterGroup.set(keyWithModel, groupObj); parameterAdded.set(keyWithModel, []); state.model.schemas.objects = state.model.schemas.objects || []; state.model.schemas.objects.push(groupObj); } const prop = new Property(parameter.language.default.name, parameter.language.default.description, parameter.schema, { required: parameter.required, }); if (parameterAdded.has(keyWithModel) && (parameterAdded.get(keyWithModel) || []).indexOf(parameter.language.default.name) === -1) { groupObj.addProperty(prop); parameterAdded.set(keyWithModel, [...(parameterAdded.get(keyWithModel) || []), parameter.language.default.name]); } if (!addedOperationGroupParameters.has(keyWithModel)) { addedOperationGroupParameters.set(keyWithModel, !!(parameter.required)); const groupParameter = new Parameter(camelCase(keyWithModel), '', groupObj); groupParameter.protocol.http = groupParameter.protocol.http || new Protocol(); groupParameter.protocol.http.in = 'complexHeader'; operationGroupParameters.push(groupParameter); } else { if (!addedOperationGroupParameters.get(keyWithModel)) { addedOperationGroupParameters.set(keyWithModel, !!(parameter.required)); } } } else if (parameterGroupContainHeader.get(key)) { if (!addedOperationGroupParameters.has(key)) { addedOperationGroupParameters.set(key, !!(parameter.required)); const groupParameter = new Parameter(camelCase(key), '', GroupObjects.get(key) || new ObjectSchema(key, '')); groupParameter.protocol.http = groupParameter.protocol.http || new Protocol(); groupParameter.protocol.http.in = 'complexHeader'; operationGroupParameters.push(groupParameter); } else { if (!addedOperationGroupParameters.get(key)) { addedOperationGroupParameters.set(key, !!(parameter.required)); } } } } else { continue; } } for (const groupParameter of operationGroupParameters) { groupParameter.required = addedOperationGroupParameters.get(pascalCase(groupParameter.language.default.name)) || false; } operation.parameters = [...operationGroupParameters, ...(operation.parameters || [])]; } } } async function implementOdata(state: State) { const knownOdataParameters = ['$filter', '$top', '$orderby', '$skip', '$expand']; for (const operationGroup of state.model.operationGroups) { for (const operation of operationGroup.operations) { if (operation.extensions && operation.extensions['x-ms-odata']) { const odata = operation.extensions['x-ms-odata']; operation.parameters = (operation.parameters || []).filter(p => !(p.protocol.http?.in === 'query' && knownOdataParameters.indexOf((p.language.default.serializedName).toLowerCase()) > -1)); const schemaName = odata.split('/').pop(); const odataSchema = (state.model.schemas.objects || []).find(s => pascalCase(s.language.default.name) === pascalCase(schemaName)) || new ObjectSchema(pascalCase(schemaName), ''); // add the odata parameter const odataParameter = new Parameter('odataQuery', '', odataSchema, { extensions: { 'x-ms-odata': true }, protocol: { http: { in: 'query' } } }); operation.parameters.unshift(odataParameter); } } } } async function fixModelAsString(state: State) { for (const operationGroup of state.model.operationGroups) { for (const operation of operationGroup.operations) { (operation.parameters || []).forEach(function (parameter) { // This is a workaround for parameter with the schema x-ms-enum // since modelAsString will be dropped in m4 if (parameter.extensions && parameter.extensions['x-ms-model-as-string'] !== undefined) { parameter.schema.extensions = (parameter.schema.extensions || {}); parameter.schema.extensions['x-ms-model-as-string'] = parameter.extensions['x-ms-model-as-string']; } }); } } } async function implementHeaderResponse(state: State) { const headerSchemaMap = new Map<string, ObjectSchema>(); const headerSchemaPropertiesMap = new Map<string, Array<string>>(); for (const operationGroup of state.model.operationGroups) { for (const operation of operationGroup.operations) { for (const response of values(operation.responses)) { if (response.protocol.http && response.protocol.http.headers) { const schemaName = pascalCase(operationGroup.$key + operation.language.default.name + 'Headers'); const headerSchema = headerSchemaMap.get(schemaName) || new ObjectSchema(schemaName, ''); headerSchemaMap.set(schemaName, headerSchema); for (const header of values(response.protocol.http.headers)) { if (headerSchemaPropertiesMap.has(schemaName) && (headerSchemaPropertiesMap.get(schemaName) || []).indexOf((<Property>header).language.default.name) !== -1) { continue; } else { headerSchemaPropertiesMap.set(schemaName, [...(headerSchemaPropertiesMap.get(schemaName) || []), (<Property>header).language.default.name]); const property = new Property(camelCase((<Property>header).language.default.name), '', (<Property>header).schema, { serializedName: (<any>header).header, required: (<Property>header).required, readOnly: (<Property>header).readOnly }); headerSchema.addProperty(property); } } } } } } headerSchemaMap.forEach((value, key) => { state.model.schemas.objects = state.model.schemas.objects || []; state.model.schemas.objects.push(value); }); } async function handlePayloadFlatteningThreshold(state: State): Promise<void> { const useDateTimeOffset = await state.getValue('useDateTimeOffset', false); const helper = new Helper(useDateTimeOffset); const payloadFlatteningThreshold = await state.getValue('payload-flattening-threshold', 0); for (const operationGroup of state.model.operationGroups) { for (const operation of operationGroup.operations) { if (operation.requests && operation.requests[0].parameters) { const parameters = operation.requests[0].parameters; const bodyParameter = parameters.find(p => p.protocol.http?.in === 'body'); if (bodyParameter && payloadFlatteningThreshold > 0) { const bodyParameterSchema = bodyParameter.schema; if (bodyParameterSchema.type === SchemaType.Object) { const virtualProperties = getAllPublicVirtualProperties(bodyParameterSchema.language.default.virtualProperties).filter(vp => !vp.readOnly && !(vp.required && (vp.property.schema.type === SchemaType.Constant || helper.IsConstantEnumProperty(vp)))); if (virtualProperties.length <= payloadFlatteningThreshold && !(bodyParameter.schema.type === SchemaType.Object && ((<ObjectSchema>bodyParameter.schema).discriminator || (<ObjectSchema>bodyParameter.schema).discriminatorValue))) { bodyParameter.extensions = bodyParameter.extensions || {}; bodyParameter.extensions['x-ms-client-flatten'] = true; } } } } } } } async function createVirtuals(state: State): Promise<PwshModel> { fixModelAsString(state); // add support for x-ms-odata implementOdata(state); // add support for x-ms-parameter-grouping await implementGroupParameters(state); // add support for header response await implementHeaderResponse(state); /* A model class should provide inlined properties for anything in a property called properties Classes that have $THRESHOLD number of properties should be inlined. Individual models can change the $THRESHOLD for generate */ const conflicts = new Array<string>(); // Move additionalProperties from parent to properties. We need to complete it for all objects before createVirtualProperties for (const schema of values(state.model.schemas.objects)) { moveAdditionalPropertiesFromParentToProperties(schema, state.model.schemas); } for (const schema of values(state.model.schemas.objects)) { // did we already inline this object if (schema.language.default.inlined) { continue; } // we have an object, let's process it. createVirtualProperties(schema, new Array<string>(), conflicts); } if (length(conflicts) > 0) { // dolauli need to figure out how inline-properties is used in README.md state.error('You have one or more circular references in your model, you must add configuration entries to specify which models won\'t be inlined.', ['inline-properties']); for (const each of conflicts) { state.error(each, ['circular reference']); } throw new Error('Circular references exists, must mark models as `no-inline`'); } // handle payload-flattening-threshold // when this value is set, we will flatten the body parameter if the number of non-constant & non-readonly virtual properties is less than or equal to the threshold await handlePayloadFlatteningThreshold(state); return state.model; } function addPropertyWithDuplicateNameReverse(properties: Array<Property>, duplicate: Property) { let count = 0; properties.filter(property => { if (property.language.default.name.startsWith(duplicate.language.default.name)) { const name = property.language.default.name.substring(duplicate.language.default.name.length); if (name === '' || typeof Number(name) === 'number') { return true; } } return false; }).sort((a, b) => { const keyA = a.language.default.name.substring(duplicate.language.default.name.length); const keyB = b.language.default.name.substring(duplicate.language.default.name.length); const numA = keyA === '' ? 0 : Number(keyA); const numB = keyB === '' ? 0 : Number(keyB); return numA - numB; }).forEach(property => { const key = property.language.default.name.substring(duplicate.language.default.name.length); const num = key === '' ? 0 : Number(key); if (num === count) { property.language.default.name = duplicate.language.default.name + (++count); } }); properties.unshift(duplicate); } function moveAdditionalPropertiesFromParentToProperties(obj: ObjectSchema, schemas: Schemas) { if (obj.parents) { let schema: Schema | undefined; if (Array.isArray(obj.parents.immediate)) { obj.parents.immediate = obj.parents.immediate.filter(parent => { if (parent.type === 'dictionary' && parent.language.default.name === obj.language.default.name && schemas.dictionaries?.find(dictionary => dictionary.language.default.name === parent.language.default.name)) { schema = parent; return false; } return true; }); } if (Array.isArray(obj.parents.all)) { obj.parents.all = obj.parents.all.filter(parent => { if (parent.type === 'dictionary' && parent.language.default.name === obj.language.default.name && schemas.dictionaries?.find(dictionary => dictionary.language.default.name === parent.language.default.name)) { schema = parent; return false; } return true; }); } if (schema) { schema.language.default.name = 'additionalProperties'; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (schema.language.csharp) { schema.language.csharp.name = 'additionalProperties'; } const additionalProperties = new Property('additionalProperties', schema.language.default.description, schema); additionalProperties.language.default = schema.language.default; if (!obj.properties) { obj.properties = []; } additionalProperties.language.default.flavor = 'additionalProperties'; addPropertyWithDuplicateNameReverse(obj.properties, additionalProperties); } } } export async function createSdkInlinedPropertiesPlugin(service: Host) { //const session = await startSession<PwshModel>(service, {}, codeModelSchema); //const result = tweakModelV2(session); const state = await new ModelState<PwshModel>(service).init(); await service.writeFile({ filename: 'sdk-code-model-v4-create-virtual-properties-v2.yaml', content: serialize(await createVirtuals(state)), sourceMap: undefined, artifactType: 'code-model-v4' }); //return processCodeModel(createVirtuals, service, 'create-virtual-properties-v2'); }