From d9e6747429aefd1969c4a85664060252b135b7dd Mon Sep 17 00:00:00 2001 From: Xiaogang Date: Tue, 8 Oct 2024 11:05:17 +0800 Subject: [PATCH] Add the config fixed-array, which is false by default (#1385) --- powershell/cmdlets/class.ts | 40 ++++++++++--------- powershell/internal/project.ts | 6 ++- powershell/llcsharp/model/namespace.ts | 6 +-- powershell/llcsharp/project.ts | 3 ++ powershell/llcsharp/schema/schema-resolver.ts | 6 ++- powershell/models/model-extensions.ts | 6 +-- powershell/plugins/cs-namer-v2.ts | 8 ++-- powershell/plugins/sdk-cs-namer.ts | 1 - powershell/plugins/sdk-tweak-model.ts | 1 - 9 files changed, 45 insertions(+), 32 deletions(-) diff --git a/powershell/cmdlets/class.ts b/powershell/cmdlets/class.ts index e13befd3cb..c78b39ed3d 100644 --- a/powershell/cmdlets/class.ts +++ b/powershell/cmdlets/class.ts @@ -496,7 +496,7 @@ export class CmdletClass extends Class { }; } - const httpParamTD = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration((httpParam.schema), httpParam.required, this.state); + const httpParamTD = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration((httpParam.schema), httpParam.required, this.state, this.state.project.fixedArray); return { name: each.param, expression: toExpression(`this.InvocationInformation.BoundParameters.ContainsKey("${each.param.value}") ? ${each.param.value} : ${httpParamTD.defaultOfType}`), @@ -586,11 +586,13 @@ export class CmdletClass extends Class { private WriteObjectWithViewControl(valueName: string, isEnumerable = false) { const $this = this; + const lengthFunc = $this.state.project.fixedArray ? 'Length' : 'Count'; + const listToArrayFunc = $this.state.project.fixedArray ? '.ToArray()' : ''; if ($this.state.project.autoSwitchView) { if (isEnumerable) { return function* () { yield If(`null != ${valueName}`, function* () { - yield If(`0 == _responseSize && 1 == ${valueName}.Count`, function* () { + yield If(`0 == _responseSize && 1 == ${valueName}.${lengthFunc}`, function* () { yield `_firstResponse = ${valueName}[0];`; yield '_responseSize = 1;'; }); @@ -600,7 +602,7 @@ export class CmdletClass extends Class { yield ForEach('value', valueName, function* () { yield 'values.Add(value.AddMultipleTypeNameIntoPSObject());'; }); - yield 'WriteObject(values, true); '; + yield `WriteObject(values${listToArrayFunc}, true); `; yield '_responseSize = 2;'; }); }); @@ -993,7 +995,7 @@ export class CmdletClass extends Class { const match = props.find(p => pascalCase(p.serializedName) === pascalName); if (match) { - const defaultOfType = $this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(match.schema, true, $this.state).defaultOfType; + const defaultOfType = $this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(match.schema, true, $this.state, $this.state.project.fixedArray).defaultOfType; // match up vp name const vp = allVPs.find(pp => pascalCase(pp.property.serializedName) === pascalName); //push path parameters that form current identity into allParams, idOpParamsFromIdentity and idOpParamsFromIdentityserializedName @@ -1404,7 +1406,7 @@ export class CmdletClass extends Class { if ('schema' in each) { const schema = (each).schema; const props = NewGetAllPublicVirtualProperties(schema.language.csharp?.virtualProperties); - const rType = $this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(schema, true, $this.state); + const rType = $this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(schema, true, $this.state, $this.state.project.fixedArray); const result = new LocalVariable('result', dotnet.Var, { initializer: new LiteralExpression('(await response)') }); yield `// (await response) // should be ${rType.declaration}`; @@ -1428,13 +1430,15 @@ export class CmdletClass extends Class { // write out the current contents const vp = NewGetVirtualPropertyFromPropertyName(schema.language.csharp?.virtualProperties, valueProperty.serializedName); if (vp) { + const lengthFunc = $this.state.project.fixedArray ? 'Length' : 'Count'; + const subArrayFunc = $this.state.project.fixedArray ? 'SubArray' : 'GetRange'; if ($this.clientsidePagination) { - yield (If('(ulong)result.Value.Count <= this.PagingParameters.Skip', function* () { - yield ('this.PagingParameters.Skip = this.PagingParameters.Skip - (ulong)result.Value.Count;'); + yield (If(`(ulong)result.Value.${lengthFunc} <= this.PagingParameters.Skip`, function* () { + yield (`this.PagingParameters.Skip = this.PagingParameters.Skip - (ulong)result.Value.${lengthFunc};`); })); yield Else(function* () { - yield ('ulong toRead = Math.Min(this.PagingParameters.First, (ulong)result.Value.Count - this.PagingParameters.Skip);'); - yield ('var requiredResult = result.Value.GetRange((int)this.PagingParameters.Skip, (int)toRead);'); + yield (`ulong toRead = Math.Min(this.PagingParameters.First, (ulong)result.Value.${lengthFunc} - this.PagingParameters.Skip);`); + yield (`var requiredResult = result.Value.${subArrayFunc}((int)this.PagingParameters.Skip, (int)toRead);`); yield $this.WriteObjectWithViewControl('requiredResult', true); yield ('this.PagingParameters.Skip = 0;'); yield ('this.PagingParameters.First = this.PagingParameters.First <= toRead ? 0 : this.PagingParameters.First - toRead;'); @@ -1772,7 +1776,7 @@ export class CmdletClass extends Class { for (const parameter of values(operation.parameters)) { // these are the parameters that this command expects parameter.schema; - const td = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(parameter.schema, true, this.state); + const td = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(parameter.schema, true, this.state, this.state.project.fixedArray); if (parameter.details.csharp.constantValue) { // this parameter has a constant value -- SKIP IT @@ -1831,12 +1835,12 @@ export class CmdletClass extends Class { for (const vParam of vps.body) { const vSchema = vParam.schema; vParam.origin; - const propertyType = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(vSchema, true, this.state); + const propertyType = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(vSchema, true, this.state, this.state.project.fixedArray); // we need to know if the actual underlying property is actually nullable. - const nullable = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(vSchema, !!(vParam.origin).required, this.state).isNullable; + const nullable = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(vSchema, !!(vParam.origin).required, this.state, this.state.project.fixedArray).isNullable; let cmdletParameter: Property; - if (propertyType.schema.type !== SchemaType.Array) { + if (propertyType.schema.type !== SchemaType.Array || this.state.project.fixedArray) { if (vParam.name === 'IdentityType' && !this.disableTransformIdentityType && (this.operation.commandType === CommandType.ManagedIdentityNew || this.operation.commandType === CommandType.ManagedIdentityUpdate)) { const enableSystemAssignedIdentity = new Property('EnableSystemAssignedIdentity', operation.details.csharp.verb.toLowerCase() === 'new' ? SwitchParameter : NullableBoolean, { @@ -1988,7 +1992,7 @@ export class CmdletClass extends Class { declaration: parameter.schema.language.csharp?.fullname }, valueType: (paramDictSchema).elementType.type === SchemaType.Any ? System.Object : - this.state.project.schemaDefinitionResolver.resolveTypeDeclaration((paramDictSchema).elementType, true, this.state) + this.state.project.schemaDefinitionResolver.resolveTypeDeclaration((paramDictSchema).elementType, true, this.state, this.state.project.fixedArray) }; } @@ -2002,7 +2006,7 @@ export class CmdletClass extends Class { this.inputObjectParameterName = `${this.name.split(viaIdentityRegex)[1].split('Expanded')[0]}InputObject`; // add in the pipeline parameter for the identity const idschema = values(this.state.project.model.schemas.objects).first(each => each.language.default.uid === 'universal-parameter-type'); - const idtd = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(idschema, true, this.state); + const idtd = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(idschema, true, this.state, this.state.project.fixedArray); const idParam = this.add(new BackedProperty(this.inputObjectParameterName, idtd, { description: 'Identity Parameter' })); @@ -2025,7 +2029,7 @@ export class CmdletClass extends Class { })); } else { const vSchema = vParam.schema; - propertyType = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(vSchema, true, this.state); + propertyType = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(vSchema, true, this.state, this.state.project.fixedArray); origin = vParam.origin; @@ -2198,7 +2202,7 @@ export class CmdletClass extends Class { (resultSchema.type === SchemaType.SealedChoice && (resultSchema).choiceType.type === SchemaType.Boolean && (resultSchema).choices.length === 1)) { outputTypes.add(`typeof(${dotnet.Bool})`); } else { - const typeDeclaration = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(resultSchema, true, this.state); + const typeDeclaration = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(resultSchema, true, this.state, this.state.project.fixedArray); if (typeDeclaration.declaration === System.IO.Stream.declaration || typeDeclaration.declaration === dotnet.Binary.declaration) { // if this is a stream, skip the output type. @@ -2219,7 +2223,7 @@ export class CmdletClass extends Class { } const nestedSchema = ((typeDeclaration.schema).properties?.find(p => p.serializedName === pageableInfo.itemName) || ((typeDeclaration.schema).parents?.all.find(s => isObjectSchema(s) && s.properties?.find((p => p.serializedName === pageableInfo.itemName)))).properties?.find((p => p.serializedName === pageableInfo.itemName)))?.schema; - const nestedTypeDeclaration = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(nestedSchema, true, this.state); + const nestedTypeDeclaration = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(nestedSchema, true, this.state, this.state.project.fixedArray); type = (nestedTypeDeclaration).elementTypeDeclaration; } else { type = typeDeclaration.declaration; diff --git a/powershell/internal/project.ts b/powershell/internal/project.ts index 73fa47f737..4d61fa74bf 100644 --- a/powershell/internal/project.ts +++ b/powershell/internal/project.ts @@ -188,6 +188,7 @@ export class Project extends codeDomProject { public metadata!: Metadata; public state!: State; public helpLinkPrefix!: string; + public fixedArray!: boolean; get model() { return this.state.model; } @@ -209,7 +210,7 @@ export class Project extends codeDomProject { this.state.model = state.model; } - this.schemaDefinitionResolver = new PSSchemaResolver(); + this.schemaDefinitionResolver = new PSSchemaResolver(this.state.project.fixedArray); this.projectNamespace = this.state.model.language.csharp?.namespace || ''; @@ -330,6 +331,9 @@ export class Project extends codeDomProject { this.resourcesFolder = await this.state.getValue('resources-folder'); this.uxFolder = await this.state.getValue('ux-folder'); + // configuration for whether to use fixed array in generated code of model, default is false + this.fixedArray = await this.state.getValue('fixed-array', false); + // File paths this.csproj = await this.state.getValue('csproj'); this.dll = await this.state.getValue('dll'); diff --git a/powershell/llcsharp/model/namespace.ts b/powershell/llcsharp/model/namespace.ts index a191b95cc8..39bfc2b83a 100644 --- a/powershell/llcsharp/model/namespace.ts +++ b/powershell/llcsharp/model/namespace.ts @@ -36,8 +36,8 @@ class ApiVersionNamespace extends Namespace { export class ModelsNamespace extends Namespace { private subNamespaces = new Dictionary(); - resolver = new SchemaDefinitionResolver(); - newResolver = new SchemaDefinitionResolver(); + resolver = new SchemaDefinitionResolver(this.state.project.fixedArray); + newResolver = new SchemaDefinitionResolver(this.state.project.fixedArray); constructor(parent: Namespace, private schemas: NewSchemas, private state: State, objectInitializer?: DeepPartial) { super('Models', parent); this.subNamespaces[this.fullName] = this; @@ -89,7 +89,7 @@ export class ModelsNamespace extends Namespace { throw new Error('SCHEMA MISSING?'); } - const td = this.newResolver.resolveTypeDeclaration(schema, required, state); + const td = this.newResolver.resolveTypeDeclaration(schema, required, state, state.project.fixedArray); if (!schema.language.csharp?.skip) { if (td instanceof ObjectImplementation) { diff --git a/powershell/llcsharp/project.ts b/powershell/llcsharp/project.ts index edb7f3fc23..9423093789 100644 --- a/powershell/llcsharp/project.ts +++ b/powershell/llcsharp/project.ts @@ -37,6 +37,7 @@ export class Project extends codeDomProject { identityCorrection!: boolean; resourceGroupAppend!: boolean; supportJsonInput!: boolean; + fixedArray!: boolean; formats!: Dictionary; constructor(protected service: Host | TspHost, objectInitializer?: DeepPartial) { @@ -67,6 +68,8 @@ export class Project extends codeDomProject { } this.enableApiRetry = await this.state.getValue('enable-api-retry', true); + // configuration for whether to use fixed array in generated code of model, default is false + this.fixedArray = await this.state.getValue('fixed-array', false); // add project namespace this.projectNamespace = this.state.model.language.csharp?.namespace; diff --git a/powershell/llcsharp/schema/schema-resolver.ts b/powershell/llcsharp/schema/schema-resolver.ts index 57a1c1184a..4e35cdb889 100644 --- a/powershell/llcsharp/schema/schema-resolver.ts +++ b/powershell/llcsharp/schema/schema-resolver.ts @@ -32,11 +32,15 @@ import { Password } from './password'; export class SchemaDefinitionResolver { private readonly cache = new Map(); + private fixedArrayConfig: boolean; private add(schema: NewSchema, value: EnhancedTypeDeclaration): EnhancedTypeDeclaration { this.cache.set(schema.language?.csharp?.fullname || '', value); return value; } + constructor(fixedArrayConfig: boolean) { + this.fixedArrayConfig = fixedArrayConfig; + } // isFixedArray is used to determine if we want to use a fixed array or not resolveTypeDeclaration(schema: NewSchema | undefined, required: boolean, state: ModelState, isFixedArray?: boolean): EnhancedTypeDeclaration { if (!schema) { @@ -49,7 +53,7 @@ export class SchemaDefinitionResolver { // can be recursive! // handle boolean arrays as booleans (powershell will try to turn it into switches!) const ar = schema; - const elementType = (ar.elementType.type === SchemaType.Boolean) ? new Boolean(schema, true) : this.resolveTypeDeclaration(ar.elementType, true, state.path('items')); + const elementType = (ar.elementType.type === SchemaType.Boolean) ? new Boolean(schema, true) : this.resolveTypeDeclaration(ar.elementType, true, state.path('items'), this.fixedArrayConfig); if (isFixedArray) { return new FixedArrayOf(schema, required, elementType, ar.minItems, ar.maxItems, ar.uniqueItems); } else { diff --git a/powershell/models/model-extensions.ts b/powershell/models/model-extensions.ts index f0c617b9bf..2b3c129d07 100644 --- a/powershell/models/model-extensions.ts +++ b/powershell/models/model-extensions.ts @@ -34,7 +34,7 @@ export class ModelExtensionsNamespace extends Namespace { public get outputFolder(): string { return join(this.state.project.apiFolder, 'Models'); } - resolver = new SchemaDefinitionResolver(); + resolver = new SchemaDefinitionResolver(this.state.project.fixedArray); constructor(parent: Namespace, private schemas: Dictionary>, private state: State, objectInitializer?: DeepPartial) { super('Models', parent); @@ -44,7 +44,7 @@ export class ModelExtensionsNamespace extends Namespace { const $this = this; - const resolver = (s: NewSchema, req: boolean) => this.resolver.resolveTypeDeclaration(s, req, state); + const resolver = (s: NewSchema, req: boolean) => this.resolver.resolveTypeDeclaration(s, req, state, state.project.fixedArray); // Add typeconverters to model classes (partial) for (const schemaGroup of values(schemas)) { @@ -53,7 +53,7 @@ export class ModelExtensionsNamespace extends Namespace { continue; } - const td = this.resolver.resolveTypeDeclaration(schema, true, state); + const td = this.resolver.resolveTypeDeclaration(schema, true, state, state.project.fixedArray); if (td instanceof ObjectImplementation) { // it's a class object. diff --git a/powershell/plugins/cs-namer-v2.ts b/powershell/plugins/cs-namer-v2.ts index b504edae33..f16981643a 100644 --- a/powershell/plugins/cs-namer-v2.ts +++ b/powershell/plugins/cs-namer-v2.ts @@ -6,7 +6,7 @@ import { codeModelSchema, SchemaResponse, CodeModel, Schema, ObjectSchema, GroupSchema, isObjectSchema, SchemaType, GroupProperty, ParameterLocation, Operation, Parameter, VirtualParameter, getAllProperties, ImplementationLocation, OperationGroup, Request, SchemaContext, StringSchema, ChoiceSchema, SealedChoiceSchema } from '@autorest/codemodel'; import { camelCase, deconstruct, excludeXDash, fixLeadingNumber, pascalCase, lowest, maximum, minimum, getPascalIdentifier, serialize } from '@azure-tools/codegen'; import { items, values, keys, Dictionary, length } from '@azure-tools/linq'; -import { System } from '@azure-tools/codegen-csharp'; +import { Project, System } from '@azure-tools/codegen-csharp'; import { Channel, AutorestExtensionHost as Host, Session, startSession } from '@autorest/extension-base'; import { SchemaDetails } from '../llcsharp/code-model'; @@ -210,9 +210,9 @@ async function setOperationNames(state: State, resolver: SchemaDefinitionResolve for (const rsp of responses) { // per responseCode const response = rsp; - const responseTypeDefinition = response.schema ? resolver.resolveTypeDeclaration(response.schema, true, state) : undefined; + const responseTypeDefinition = response.schema ? resolver.resolveTypeDeclaration(response.schema, true, state, await state.getValue('fixed-array', false)) : undefined; const headerSchema = response.language.default.headerSchema; - const headerTypeDefinition = headerSchema ? resolver.resolveTypeDeclaration(headerSchema, true, state.path('schemas', headerSchema.language.default.name)) : undefined; + const headerTypeDefinition = headerSchema ? resolver.resolveTypeDeclaration(headerSchema, true, state.path('schemas', headerSchema.language.default.name), await state.getValue('fixed-array', false)) : undefined; let code = (System.Net.HttpStatusCode[response.protocol.http?.statusCodes[0]] ? System.Net.HttpStatusCode[response.protocol.http?.statusCodes[0]].value : (response.protocol.http?.statusCodes[0] || '')).replace('global::System.Net.HttpStatusCode', ''); let rawValue = code.replace(/\./, ''); if (response.protocol.http?.statusCodes[0] === 'default' || rawValue === 'default' || '') { @@ -238,7 +238,7 @@ async function setOperationNames(state: State, resolver: SchemaDefinitionResolve } export async function nameStuffRight(state: State): Promise { - const resolver = new SchemaDefinitionResolver(); + const resolver = new SchemaDefinitionResolver(await state.getValue('fixed-array', false)); const model = state.model; // set the namespace for the service diff --git a/powershell/plugins/sdk-cs-namer.ts b/powershell/plugins/sdk-cs-namer.ts index f11082c6b8..5444f31f31 100644 --- a/powershell/plugins/sdk-cs-namer.ts +++ b/powershell/plugins/sdk-cs-namer.ts @@ -419,7 +419,6 @@ function correctParameterNames(model: SdkModel) { async function nameStuffRight(state: State): Promise { const useDateTimeOffset = await state.getValue('useDateTimeOffset', false); const helper = new Helper(useDateTimeOffset); - const resolver = new SchemaDefinitionResolver(); const model = state.model; // set the namespace for the service diff --git a/powershell/plugins/sdk-tweak-model.ts b/powershell/plugins/sdk-tweak-model.ts index 4a58cf59d9..3ded67f504 100644 --- a/powershell/plugins/sdk-tweak-model.ts +++ b/powershell/plugins/sdk-tweak-model.ts @@ -165,7 +165,6 @@ function addNormalMethodParameterDeclaration(operation: Operation, state: State) const requiredDeclarations: Array = []; const requiredArgs: Array = []; const optionalArgs: Array = []; - const schemaDefinitionResolver = new SchemaDefinitionResolver(); const args: Array = []; let bodyParameters: Array = []; if (operation.requests && operation.requests.length > 0) {