powershell/plugins/ps-namer-v2.ts (149 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, CodeModel, Schema, ObjectSchema, GroupSchema, isObjectSchema, SchemaType, GroupProperty, ParameterLocation, Operation, Parameter, getAllProperties, ImplementationLocation, OperationGroup, Request, SchemaContext } from '@autorest/codemodel'; import { AutorestExtensionHost as Host, Channel, Session, startSession } from '@autorest/extension-base'; //import { allVirtualParameters, allVirtualProperties, resolveParameterNames, resolvePropertyNames } from '@azure-tools/codemodel-v3'; import { deconstruct, removeProhibitedPrefix, removeSequentialDuplicates, pascalCase, serialize } from '@azure-tools/codegen'; import { items, values, keys, Dictionary, length } from '@azure-tools/linq'; import * as linq from '@azure-tools/linq'; import { singularize } from '../internal/name-inferrer'; import { PwshModel } from '../utils/PwshModel'; import { ModelState } from '../utils/model-state'; import { allVirtualParameters, allVirtualProperties, resolveParameterNames, resolvePropertyNames } from '../utils/resolve-conflicts'; type State = ModelState<PwshModel>; function getCmdletName(verb: string, subjectPrefix: string, subject: string): string { return `${verb}-${subjectPrefix}${subject}`; } export function getDeduplicatedNoun(subjectPrefix: string, subject: string): { subjectPrefix: string; subject: string } { // dedup parts const dedupedPrefix = [...removeSequentialDuplicates(deconstruct(subjectPrefix))]; const dedupedSubject = [...removeSequentialDuplicates(deconstruct(subject))]; // dedup the noun const dedupedMerge = [...removeSequentialDuplicates([...dedupedPrefix, ...dedupedSubject])]; // figure out what belongs to the subject const reversedFinalSubject = new Array<string>(); for (let mCount = length(dedupedMerge) - 1, sCount = length(dedupedSubject) - 1; sCount >= 0 && mCount >= 0; mCount--, sCount--) { if (dedupedMerge[mCount] !== dedupedSubject[sCount]) { break; } reversedFinalSubject.push(<string>dedupedMerge.pop()); } // what's left belongs to the prefix const finalPrefix = new Array<string>(); for (const each of dedupedMerge) { finalPrefix.push(each); } return { subjectPrefix: pascalCase(finalPrefix), subject: pascalCase(reversedFinalSubject.reverse()) }; } export async function tweakModel(state: State): Promise<PwshModel> { // get the value const isAzure = await state.getValue('azure', false); // without setting snitize-names, isAzure is applied const shouldSanitize = await state.getValue('sanitize-names', isAzure); // make sure recursively that every details field has csharp for (const { index, instance } of linq.visitor(state.model)) { if ((index === 'details' || index === 'language') && instance && instance.default && !instance.csharp) { instance.csharp = linq.clone(instance.default, false, undefined, undefined, ['schema', 'origin']); } } // dolauli sanitize name if (shouldSanitize) { for (const operation of values(state.model.commands.operations)) { // clean the noun (i.e. subjectPrefix + subject) const prevSubjectPrefix = operation.details.csharp.subjectPrefix; const prevSubject = operation.details.csharp.subject; const dedupedNounParts = getDeduplicatedNoun(operation.details.csharp.subjectPrefix, operation.details.csharp.subject); if (prevSubjectPrefix !== dedupedNounParts.subjectPrefix || prevSubject !== dedupedNounParts.subject) { const verb = operation.details.csharp.verb; const variantName = operation.details.csharp.name; const prevCmdletName = getCmdletName(verb, prevSubjectPrefix, prevSubject); operation.details.csharp.subjectPrefix = dedupedNounParts.subjectPrefix; operation.details.csharp.subject = dedupedNounParts.subject; const newCmdletName = getCmdletName(verb, operation.details.csharp.subjectPrefix, operation.details.csharp.subject); state.message( { Channel: Channel.Debug, Text: `Sanitized cmdlet-name -> Changed cmdlet-name from ${prevCmdletName} to ${newCmdletName}: {subjectPrefix: ${operation.details.csharp.subjectPrefix}, subject: ${operation.details.csharp.subject}${variantName ? `, variant: ${variantName}}` : '}'}` } ); } const virtualParameters = [...allVirtualParameters(operation.details.csharp.virtualParameters)]; for (const parameter of virtualParameters) { let prevName = parameter.name; const otherParametersNames = values(virtualParameters) .select(each => each.name) .where(name => name !== parameter.name) .toArray(); // first try to singularize the parameter const singularName = singularize(parameter.name); if (prevName != singularName) { parameter.name = singularName; state.message({ Channel: Channel.Debug, Text: `Sanitized parameter-name -> Changed parameter-name from ${prevName} to singular ${parameter.name} from command ${operation.verb}-${operation.details.csharp.subjectPrefix}${operation.details.csharp.subject}` }); } // save the name again to compare in case it was modified prevName = parameter.name; // now remove the subject from the beginning of the parameter // to reduce naming redundancy, but just for path parameters // e.g. get-vm -vmname ---> get-vm -name if ((<any>parameter.origin).protocol?.http?.in === ParameterLocation.Path) { const sanitizedName = removeProhibitedPrefix( parameter.name, operation.details.csharp.subject, otherParametersNames ); if (prevName !== sanitizedName) { if (parameter.alias === undefined) { parameter.alias = []; } // saved the prev name as alias parameter.alias.push(parameter.name); // change name parameter.name = sanitizedName; state.message({ Channel: Channel.Debug, Text: `Sanitized parameter-name -> Changed parameter-name from ${prevName} to ${parameter.name} from command ${operation.verb}-${operation.details.csharp.subjectPrefix}${operation.details.csharp.subject}` }); state.message({ Channel: Channel.Debug, Text: ` -> And, added alias '${prevName}'` }); } } } } for (const schemaGroup of values(<Dictionary<Array<Schema>>><any>state.model.schemas)) { for (const schema of schemaGroup) { const virtualProperties = [...allVirtualProperties(schema.language.csharp?.virtualProperties)]; for (const property of virtualProperties) { let prevName = property.name; const otherPropertiesNames = values(virtualProperties) .select(each => each.name) .where(name => name !== property.name) .toArray(); // first try to singularize the property const singularName = singularize(property.name); if (prevName != singularName && !otherPropertiesNames.includes(singularName)) { property.name = singularName; state.message({ Channel: Channel.Debug, Text: `Sanitized property-name -> Changed property-name from ${prevName} to singular ${property.name} from model ${schema.language.csharp?.name}` }); } // save the name again to compare in case it was modified prevName = property.name; // now remove the model=name from the beginning of the property-name // to reduce naming redundancy const sanitizedName = removeProhibitedPrefix( property.name, schema.language.csharp?.name ? schema.language.csharp?.name : '', otherPropertiesNames ); if (prevName !== sanitizedName) { property.alias = property.alias || []; // saved the prev name as alias property.alias.push(property.name); // change name property.name = sanitizedName; state.message({ Channel: Channel.Debug, Text: `Sanitized property-name -> Changed property-name from ${prevName} to ${property.name} from model ${schema.language.csharp?.name}` }); state.message({ Channel: Channel.Debug, Text: ` -> And, added alias '${prevName}'` }); // update shared properties too if (property.sharedWith) { for (const sharedProperty of property.sharedWith) { if (sharedProperty.name !== sanitizedName) { state.message({ Channel: Channel.Debug, Text: `Changing shared property ${sharedProperty.name} to ${sanitizedName}` }); sharedProperty.alias = sharedProperty.alias || []; sharedProperty.alias.push(sharedProperty.name); sharedProperty.name = sanitizedName; } } } } } } } } // do collision detection work. for (const command of values(state.model.commands.operations)) { const vp = command.details.csharp?.virtualParameters; if (vp) { resolveParameterNames([], vp); } } for (const schemaGroup of values(<Dictionary<Array<Schema>>><any>state.model.schemas)) { for (const schema of schemaGroup) { const vp = schema.language.csharp?.virtualProperties; if (vp) { resolvePropertyNames(schema.language.csharp?.name ? [schema.language.csharp?.name] : [], vp); } } } return state.model; } export async function namerV2(service: Host) { // dolauli add csharp for cmdlets in the command->operation node //return processCodeModel(tweakModel, service, 'psnamer'); //const session = await startSession<PwshModel>(service, {}, codeModelSchema); //const result = tweakModelV2(session); const state = await new ModelState<PwshModel>(service).init(); await service.writeFile({ filename: 'code-model-v4-psnamer-v2.yaml', content: serialize(await tweakModel(state)), sourceMap: undefined, artifactType: 'code-model-v4' }); }