UNPKG

@autorest/powershell

Version:
415 lines (413 loc) 23.9 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.createInlinedPropertiesPlugin = exports.createVirtuals = exports.singularize = void 0; const codemodel_1 = require("@autorest/codemodel"); const codegen_1 = require("@azure-tools/codegen"); const linq_1 = require("@azure-tools/linq"); const command_operation_1 = require("../utils/command-operation"); const model_state_1 = require("../utils/model-state"); const schema_1 = require("../utils/schema"); const resolve_conflicts_1 = require("../utils/resolve-conflicts"); const command_operation_2 = require("../utils/command-operation"); function getPluralizationService() { const result = new codegen_1.EnglishPluralizationService(); result.addWord('Database', 'Databases'); result.addWord('database', 'databases'); result.addWord('Premise', 'Premises'); result.addWord('premise', 'premises'); return result; } function singularize(word) { return getPluralizationService().singularize(word); } exports.singularize = singularize; function getCombinedDescription(rawDescription, externalDocsUrl) { let description = rawDescription !== null && rawDescription !== void 0 ? rawDescription : ''; if (!!externalDocsUrl && !!externalDocsUrl.trim()) { description = description.concat(` Please visit external url ${externalDocsUrl} to get more information.`); } return description; } function getNameOptions(typeName, components) { const result = new Set(); // add a variant for each incrementally inclusive parent naming scheme. for (let i = 0; i < (0, linq_1.length)(components); i++) { const subset = (0, codegen_1.pascalCase)([...(0, codegen_1.removeSequentialDuplicates)(components.slice(-1 * i, (0, linq_1.length)(components)))]); result.add(subset); } // add a second-to-last-ditch option as <typename>.<name> result.add((0, codegen_1.pascalCase)([...(0, codegen_1.removeSequentialDuplicates)([...(0, codegen_1.fixLeadingNumber)((0, codegen_1.deconstruct)(typeName)), ...(0, codegen_1.deconstruct)(components.last)])])); return [...result.values()]; } function createVirtualProperties(schema, stack, threshold, conflicts) { var _a, _b, _c, _d, _e, _f, _g; // 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(), inherited: new Array(), inlined: new Array(), }; // 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 (0, linq_1.values)((_a = schema.parents) === null || _a === void 0 ? void 0 : _a.immediate)) { // make sure that the parent is done. // Guess parent should always be an object. if (!(0, codemodel_1.isObjectSchema)(parentSchema)) continue; createVirtualProperties(parentSchema, [...stack, `${schema.language.default.name}`], threshold, 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: virtualProperty.name, 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, }; // 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] = (0, linq_1.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.schema.type === codemodel_1.SchemaType.Object && // is it an object (0, schema_1.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) { const mutability = (0, schema_1.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(property.schema, [...stack, `${schema.language.default.name}`], threshold, 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 === codemodel_1.SchemaType.Dictionary || ((_c = (_b = property.schema.parents) === null || _b === void 0 ? void 0 : _b.immediate) === null || _c === void 0 ? void 0 : _c.find((s) => s.type === codemodel_1.SchemaType.Dictionary)); const canInline = (!property.schema.language.default['skip-inline']) && (!property.schema.language.default.byReference) && (!isDict) && 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 = (0, linq_1.values)((0, schema_1.getAllPublicVirtualProperties)()).all(each => !each.property.language.default.required); const childCount = (0, linq_1.length)(virtualChildProperties.owned) + (0, linq_1.length)(virtualChildProperties.inherited) + (0, linq_1.length)(virtualChildProperties.inlined); if (canInline && (property.language.default.required || allNotRequired) && (childCount < threshold || propertyName === 'properties')) { // 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 combinedDescription = getCombinedDescription(property.language.default.description, (_e = (_d = property.schema) === null || _d === void 0 ? void 0 : _d.externalDocs) === null || _e === void 0 ? void 0 : _e.url); property.language.default.description = combinedDescription; const privateProperty = { name: (0, codegen_1.getPascalIdentifier)(propertyName), propertySchema: schema, property, nameComponents: [(0, codegen_1.getPascalIdentifier)(propertyName)], nameOptions: getNameOptions(schema.language.default.name, [propertyName]), private: true, description: property.summary || '', originalContainingSchema: schema, alias: [], required: property.required || property.language.default.required, }; 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 = (0, codegen_1.getPascalIdentifier)(`${propertyName === 'properties' || /*objectProperties.length === 1*/ propertyName === 'error' ? '' : (0, codegen_1.pascalCase)((0, codegen_1.fixLeadingNumber)((0, codegen_1.deconstruct)(propertyName)).map(each => singularize(each)))} ${inlinedProperty.name}`); const components = [...(0, codegen_1.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: proposedName, 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, }); } 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 = (0, codegen_1.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 = [...(0, codegen_1.removeSequentialDuplicates)([propertyName, ...inlinedProperty.nameComponents])]; virtualProperties.inlined.push({ name: proposedName, 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 }); } } 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) { const name = (0, codegen_1.getPascalIdentifier)(property.language.default.name); // 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 = (0, schema_1.getMutability)(property); const combinedDescription = getCombinedDescription(property.language.default.description, (_g = (_f = property.schema) === null || _f === void 0 ? void 0 : _f.externalDocs) === null || _g === void 0 ? void 0 : _g.url); property.language.default.description = combinedDescription; 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 }); } // resolve name collisions. const allProps = [...virtualProperties.owned, ...virtualProperties.inherited, ...virtualProperties.inlined]; const inlined = new Map(); 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) => (0, linq_1.length)(a.nameOptions) - (0, linq_1.length)(b.nameOptions))) { const ct = inlined.get(each.name); if (ct && ct > 1) { // console.error(`Fixing collision on name ${each.name} #${ct} `); each.name = (0, codegen_1.selectName)(each.nameOptions, usedNames); } } schema.language.default.inline = 'yes'; return true; } function createVirtualParameters(operation) { var _a; // dolauli expand body parameter // for virtual parameters, there are two keys, operation and body const virtualParameters = { operation: new Array(), body: new Array() }; const dropBodyParameter = !!operation.details.default.dropBodyParameter; // loop thru the parameters of the command operation, and if there is a body parameter, expand it if necessary. for (const parameter of (0, linq_1.values)(operation.parameters)) { if (parameter.details.default.constantValue) { // this parameter has a constant value -- SKIP IT continue; } // dolauli fromhost and apiversion are not exposed, this if block looks useless if (parameter.details.default.fromHost || parameter.details.default.apiversion) { // handled in the generator right now. Not exposed to the user directly. continue; } if (dropBodyParameter && parameter.details.default.isBodyParameter) { // the client will make a hidden body parameter for this, and we're expected to fill it. const vps = parameter.schema.language.default.virtualProperties; if (vps) { for (const virtualProperty of [...vps.inherited, ...vps.owned, ...vps.inlined]) { // dolauli add virtual parameter for virtual property if (virtualProperty.private || virtualProperty.readOnly || virtualProperty.property.readOnly || virtualProperty.property.language.default.constantValue !== undefined || virtualProperty.property.language.default.HeaderProperty === 'Header') { // private or readonly properties aren't needed as parameters. continue; } // Add support for x-ms-mutability if (operation.operationType === command_operation_2.OperationType.Create && !virtualProperty.create) { continue; } else if (operation.operationType === command_operation_2.OperationType.Update && !virtualProperty.update) { continue; } virtualParameters.body.push({ name: virtualProperty.name, description: virtualProperty.property.language.default.description, nameOptions: virtualProperty.nameOptions, required: parameter.required ? shouldBeRequired(operation, virtualProperty) : false, schema: virtualProperty.property.schema, origin: virtualProperty, alias: [] }); } } } else { // dolauli if not drop body or not body parameter add it to operation const combinedDescription = getCombinedDescription(parameter.schema.language.default.description, (_a = parameter.schema.externalDocs) === null || _a === void 0 ? void 0 : _a.url); parameter.schema.language.default.description = combinedDescription; virtualParameters.operation.push({ name: parameter.details.default.name, nameOptions: [parameter.details.default.name], description: parameter.details.default.description, required: parameter.details.default.isBodyParameter ? true : parameter.required, schema: parameter.schema, origin: parameter, alias: [] }); } } (0, resolve_conflicts_1.resolveParameterNames)([], virtualParameters); // dolauli see operation.details.default.virtualParameters operation.details.default.virtualParameters = virtualParameters; } async function createVirtuals(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 threshold = await state.getValue('inlining-threshold', 24); const conflicts = new Array(); for (const schema of (0, linq_1.values)(state.model.schemas.objects)) { // did we already inline this objecct if (schema.language.default.inlined) { continue; } // we have an object, let's process it. createVirtualProperties(schema, new Array(), threshold, conflicts); } if ((0, linq_1.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`'); } //dolauli update operations under commands for (const operation of (0, linq_1.values)(state.model.commands.operations)) { createVirtualParameters(operation); } return state.model; } exports.createVirtuals = createVirtuals; function shouldBeRequired(operation, virtualProperty) { const shouldBeOptional = operation.commandType === command_operation_1.CommandType.GetPut || operation.commandType === command_operation_1.CommandType.ManagedIdentityUpdate; if (!shouldBeOptional) { return virtualProperty.required; } if (!virtualProperty.read && (virtualProperty.create || virtualProperty.update)) { return virtualProperty.required; } return false; } async function createInlinedPropertiesPlugin(service) { //const session = await startSession<PwshModel>(service, {}, codeModelSchema); //const result = tweakModelV2(session); const state = await new model_state_1.ModelState(service).init(); await service.writeFile({ filename: 'code-model-v4-create-virtual-properties-v2.yaml', content: (0, codegen_1.serialize)(await createVirtuals(state)), sourceMap: undefined, artifactType: 'code-model-v4' }); //return processCodeModel(createVirtuals, service, 'create-virtual-properties-v2'); } exports.createInlinedPropertiesPlugin = createInlinedPropertiesPlugin; //# sourceMappingURL=plugin-create-inline-properties.js.map