From 38e42939b89fa072d2df299fc426f9ec45b27595 Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Wed, 8 May 2024 12:24:01 -0400 Subject: [PATCH] [tcgc] add generated names for constants (#766) fixes #553 --------- Co-authored-by: iscai-msft --- .../add_naming_constant-2024-3-30-17-2-35.md | 7 +++ .../src/http.ts | 7 ++- .../src/interfaces.ts | 2 + .../src/internal-utils.ts | 8 +++- .../src/public-utils.ts | 48 +++++++++++++------ .../src/types.ts | 10 ++-- .../test/types.test.ts | 6 +++ 7 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 .chronus/changes/add_naming_constant-2024-3-30-17-2-35.md diff --git a/.chronus/changes/add_naming_constant-2024-3-30-17-2-35.md b/.chronus/changes/add_naming_constant-2024-3-30-17-2-35.md new file mode 100644 index 0000000000..243bb1fab2 --- /dev/null +++ b/.chronus/changes/add_naming_constant-2024-3-30-17-2-35.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +add generated names for constants \ No newline at end of file diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 6cbbb73415..d6f62bac0d 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -187,7 +187,7 @@ function getSdkHttpParameters( if (retval.bodyParam && !headerParams.some((h) => isContentTypeHeader(h))) { // if we have a body param and no content type header, we add one const contentTypeBase = { - ...createContentTypeOrAcceptHeader(retval.bodyParam), + ...createContentTypeOrAcceptHeader(httpOperation, retval.bodyParam), description: `Body parameter's content type. Known values are ${retval.bodyParam.contentTypes}`, }; if (!methodParameters.some((m) => m.name === "contentType")) { @@ -206,7 +206,7 @@ function getSdkHttpParameters( if (responseBody && !headerParams.some((h) => isAcceptHeader(h))) { // If our operation returns a body, we add an accept header if none exist const acceptBase = { - ...createContentTypeOrAcceptHeader(responseBody), + ...createContentTypeOrAcceptHeader(httpOperation, responseBody), }; if (!methodParameters.some((m) => m.name === "accept")) { methodParameters.push({ @@ -230,6 +230,7 @@ function getSdkHttpParameters( } function createContentTypeOrAcceptHeader( + httpOperation: HttpOperation, bodyObject: SdkBodyParameter | SdkHttpResponse ): Omit { const name = bodyObject.kind === "body" ? "contentType" : "accept"; @@ -256,6 +257,8 @@ function createContentTypeOrAcceptHeader( kind: "constant", value: bodyObject.contentTypes[0], valueType: type, + name: `${httpOperation.operation.name}ContentType`, + isGeneratedName: true, }; } // No need for clientDefaultValue because it's a constant, it only has one value diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index ff810af9e3..d6f2550405 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -271,6 +271,8 @@ export interface SdkConstantType extends SdkTypeBase { kind: "constant"; value: string | number | boolean | null; valueType: SdkBuiltInType; + name: string; + isGeneratedName: boolean; } export interface SdkUnionType extends SdkTypeBase { diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 46329cd09d..52150c65a9 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -1,12 +1,15 @@ import { getUnionAsEnum } from "@azure-tools/typespec-azure-core"; import { + BooleanLiteral, Diagnostic, Interface, Model, Namespace, + NumericLiteral, Operation, Program, ProjectedProgram, + StringLiteral, Type, Union, createDiagnosticCollector, @@ -282,6 +285,9 @@ export function isMultipartOperation(context: TCGCContext, operation?: Operation export function isHttpOperation(context: TCGCContext, obj: any): obj is HttpOperation { return obj?.kind === "Operation" && getHttpOperationWithCache(context, obj) !== undefined; } + +export type TspLiteralType = StringLiteral | NumericLiteral | BooleanLiteral; + export interface TCGCContext { program: Program; emitterName: string; @@ -293,7 +299,7 @@ export interface TCGCContext { arm?: boolean; modelsMap?: Map; operationModelsMap?: Map>; - generatedNames?: Map; + generatedNames?: Map; httpOperationCache?: Map; unionsMap?: Map; __namespaceToApiVersionParameter: Map; diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index 88729d1088..bfad9920dd 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -27,7 +27,7 @@ import { isStatusCode, } from "@typespec/http"; import { Version, getVersions } from "@typespec/versioning"; -import { pascalCase } from "change-case"; +import { capitalCase, pascalCase } from "change-case"; import pluralize from "pluralize"; import { getClientNameOverride, @@ -35,7 +35,12 @@ import { listOperationGroups, listOperationsInOperationGroup, } from "./decorators.js"; -import { TCGCContext, getClientNamespaceStringHelper, parseEmitterName } from "./internal-utils.js"; +import { + TCGCContext, + TspLiteralType, + getClientNamespaceStringHelper, + parseEmitterName, +} from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; /** @@ -256,11 +261,11 @@ export function getCrossLanguagePackageId(context: TCGCContext): [string, readon */ export function getGeneratedName( context: TCGCContext, - type: Model | Union, + type: Model | Union | TspLiteralType, operation?: Operation ): string { if (!context.generatedNames) { - context.generatedNames = new Map(); + context.generatedNames = new Map(); } const generatedName = context.generatedNames.get(type); if (generatedName) return generatedName; @@ -278,7 +283,10 @@ export function getGeneratedName( * @param type * @returns */ -function findContextPath(context: TCGCContext, type: Model | Union): ContextNode[] { +function findContextPath( + context: TCGCContext, + type: Model | Union | TspLiteralType +): ContextNode[] { for (const client of listClients(context)) { for (const operation of listOperationsInOperationGroup(context, client)) { const result = getContextPath(context, operation, type); @@ -309,7 +317,7 @@ function findContextPath(context: TCGCContext, type: Model | Union): ContextNode interface ContextNode { displayName: string; - type?: Model | Union; + type?: Model | Union | TspLiteralType; } /** @@ -322,7 +330,7 @@ interface ContextNode { function getContextPath( context: TCGCContext, root: Operation | Model, - typeToFind: Model | Union + typeToFind: Model | Union | TspLiteralType ): ContextNode[] { // use visited set to avoid cycle model reference const visited: Set = new Set(); @@ -392,7 +400,7 @@ function getContextPath( * @returns */ function dfsModelProperties( - expectedType: Model | Union, + expectedType: Model | Union | TspLiteralType, currentType: Type, displayName: string ): boolean { @@ -401,7 +409,7 @@ function getContextPath( return false; } - if (!(currentType.kind === "Model" || currentType.kind === "Union")) { + if (!["Model", "Union", "String", "Number", "Boolean"].includes(currentType.kind)) { return false; } @@ -473,7 +481,7 @@ function getContextPath( if (result) return true; } return false; - } else { + } else if (currentType.kind === "Union") { // handle union for (const unionType of currentType.variants.values()) { // traverse union type @@ -482,6 +490,8 @@ function getContextPath( if (result) return true; } return false; + } else { + return false; } } } @@ -496,7 +506,7 @@ function getContextPath( */ function buildNameFromContextPaths( context: TCGCContext, - type: Union | Model, + type: Union | Model | TspLiteralType, contextPath: ContextNode[] ): string { // fallback to empty name for corner case @@ -507,9 +517,10 @@ function buildNameFromContextPaths( // 1. find the last nonanonymous model node let lastNonAnonymousModelNodeIndex = contextPath.length - 1; while (lastNonAnonymousModelNodeIndex >= 0) { + const currType = contextPath[lastNonAnonymousModelNodeIndex].type; if ( !contextPath[lastNonAnonymousModelNodeIndex].type || - contextPath[lastNonAnonymousModelNodeIndex].type?.name + (currType?.kind === "Model" && currType.name) ) { // it's nonanonymous model node (if no type defined, it's the operation node) break; @@ -520,12 +531,19 @@ function buildNameFromContextPaths( // 2. build name let createName: string = ""; for (let j = lastNonAnonymousModelNodeIndex; j < contextPath.length; j++) { - if (!contextPath[j]?.type?.name) { + const currContextPathType = contextPath[j]?.type; + if ( + currContextPathType?.kind === "String" || + currContextPathType?.kind === "Number" || + currContextPathType?.kind === "Boolean" + ) { + createName = `${createName}${contextPath[j].displayName}${capitalCase(String(currContextPathType.value))}`; + } else if (!currContextPathType?.name) { // is anonymous model node createName = `${createName}${contextPath[j].displayName}`; } else { // is non-anonymous model, use type name - createName = `${createName}${contextPath[j]!.type!.name!}`; + createName = `${createName}${currContextPathType!.name!}`; } } // 3. simplely handle duplication @@ -538,7 +556,7 @@ function buildNameFromContextPaths( if (context.generatedNames) { context.generatedNames.set(type, createName); } else { - context.generatedNames = new Map([[type, createName]]); + context.generatedNames = new Map([[type, createName]]); } return createName; } diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 401e57cce1..06ed55dd9a 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -389,7 +389,8 @@ export function getSdkUnionWithDiagnostics( function getSdkConstantWithDiagnostics( context: TCGCContext, - type: StringLiteral | NumericLiteral | BooleanLiteral + type: StringLiteral | NumericLiteral | BooleanLiteral, + operation?: Operation ): [SdkConstantType, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); switch (type.kind) { @@ -401,15 +402,18 @@ function getSdkConstantWithDiagnostics( ...getSdkTypeBaseHelper(context, type, "constant"), value: type.value, valueType, + name: getGeneratedName(context, type, operation), + isGeneratedName: true, }); } } export function getSdkConstant( context: TCGCContext, - type: StringLiteral | NumericLiteral | BooleanLiteral + type: StringLiteral | NumericLiteral | BooleanLiteral, + operation?: Operation ): SdkConstantType { - return ignoreDiagnostics(getSdkConstantWithDiagnostics(context, type)); + return ignoreDiagnostics(getSdkConstantWithDiagnostics(context, type, operation)); } function addDiscriminatorToModelType( diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index b96d4e71b8..e27e4bd31f 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -1941,6 +1941,8 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "constant"); strictEqual(sdkType.valueType.kind, "string"); strictEqual(sdkType.value, "json"); + strictEqual(sdkType.name, "TestPropJson"); + strictEqual(sdkType.isGeneratedName, true); }); it("boolean", async function () { await runner.compileWithBuiltInService(` @@ -1955,6 +1957,8 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "constant"); strictEqual(sdkType.valueType.kind, "boolean"); strictEqual(sdkType.value, true); + strictEqual(sdkType.name, "TestPropTrue"); + strictEqual(sdkType.isGeneratedName, true); }); it("number", async function () { await runner.compileWithBuiltInService(` @@ -1969,6 +1973,8 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "constant"); strictEqual(sdkType.valueType.kind, "int32"); strictEqual(sdkType.value, 4); + strictEqual(sdkType.name, "TestProp4"); + strictEqual(sdkType.isGeneratedName, true); }); }); describe("SdkModelType", () => {